blob: f0050f6ecbfb41d3600f0b98f4e0e9581776887d [file]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2022 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
// https://llvm.org/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Aleksei Vetrov
#include "elf_reader.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include "dwarf_processor.h"
#include "dwarf_wrappers.h"
#include "elf_loader.h"
#include "graph.h"
#include "naming.h"
namespace stg {
namespace elf {
namespace {
using SymbolTable = std::vector<SymbolTableEntry>;
using CRCValuesMap = std::unordered_map<std::string, ElfSymbol::CRC>;
ElfSymbol::SymbolType ConvertSymbolType(
SymbolTableEntry::SymbolType symbol_type) {
switch (symbol_type) {
case SymbolTableEntry::SymbolType::OBJECT:
return ElfSymbol::SymbolType::OBJECT;
case SymbolTableEntry::SymbolType::FUNCTION:
return ElfSymbol::SymbolType::FUNCTION;
case SymbolTableEntry::SymbolType::COMMON:
return ElfSymbol::SymbolType::COMMON;
case SymbolTableEntry::SymbolType::TLS:
return ElfSymbol::SymbolType::TLS;
default:
Die() << "Unsupported ELF symbol type: " << symbol_type;
}
}
CRCValuesMap GetCRCValuesMap(const SymbolTable& symbols, const ElfLoader& elf) {
constexpr std::string_view kCRCPrefix = "__crc_";
CRCValuesMap crc_values;
for (const auto& symbol : symbols) {
const std::string_view name = symbol.name;
if (name.substr(0, kCRCPrefix.size()) == kCRCPrefix) {
std::string_view name_suffix = name.substr(kCRCPrefix.size());
if (!crc_values.emplace(name_suffix, elf.GetElfSymbolCRC(symbol))
.second) {
Die() << "Multiple CRC values for symbol '" << name_suffix << '\'';
}
}
}
return crc_values;
}
template <typename M, typename K>
std::optional<typename M::mapped_type> MaybeGet(const M& map, const K& key) {
const auto it = map.find(key);
if (it == map.end()) {
return {};
}
return {it->second};
}
ElfSymbol SymbolTableEntryToElfSymbol(const SymbolTableEntry& symbol,
const CRCValuesMap& crc_values) {
return ElfSymbol(
/* symbol_name = */ std::string(symbol.name),
/* version_info = */ std::nullopt,
/* is_defined = */ symbol.value_type !=
SymbolTableEntry::ValueType::UNDEFINED,
/* symbol_type = */ ConvertSymbolType(symbol.symbol_type),
/* binding = */ symbol.binding,
/* visibility = */ symbol.visibility,
/* crc = */ MaybeGet(crc_values, std::string(symbol.name)),
/* ns = */ std::nullopt, // TODO: Linux namespace
/* type_id = */ std::nullopt, // TODO: fill type ids
/* full_name = */ std::nullopt // TODO: fill full names
);
}
bool IsPublicFunctionOrVariable(const SymbolTableEntry& symbol) {
const auto symbol_type = symbol.symbol_type;
// Reject symbols that are not functions or variables.
if (symbol_type != SymbolTableEntry::SymbolType::FUNCTION &&
symbol_type != SymbolTableEntry::SymbolType::OBJECT &&
symbol_type != SymbolTableEntry::SymbolType::TLS) {
return false;
}
// Function or variable of ValueType::ABSOLUTE is not expected in any binary,
// but GNU `ld` adds object of such type for every version name defined in
// file. Such symbol should be rejected, because in fact it is not variable.
if (symbol.value_type == SymbolTableEntry::ValueType::ABSOLUTE) {
Check(symbol_type == SymbolTableEntry::SymbolType::OBJECT)
<< "Unexpected function or variable with ABSOLUTE value type";
return false;
}
// Undefined symbol is dependency of the binary but is not part of ABI
// provided by binary and should be rejected.
if (symbol.value_type == SymbolTableEntry::ValueType::UNDEFINED) {
return false;
}
// Local symbol is not visible outside the binary, so it is not public
// and should be rejected.
if (symbol.binding == SymbolTableEntry::Binding::LOCAL) {
return false;
}
// "Hidden" and "internal" visibility values mean that symbol is not public
// and should be rejected.
if (symbol.visibility == SymbolTableEntry::Visibility::HIDDEN ||
symbol.visibility == SymbolTableEntry::Visibility::INTERNAL) {
return false;
}
return true;
}
class Typing {
public:
Typing(Graph& graph, dwarf::Types types)
: graph_(graph), types_(std::move(types)) {
}
Id JoinAllIds() {
return graph_.Add<Function>(graph_.Add<Void>(), types_.all_ids);
}
// This hack is used to attach all processed DWARF entries to symbols map.
// This is temporary solution, until entries are matched to real ELF symbols.
// TODO: match STG from DWARF with ELF symbols
void AddFakeSymbols(std::map<std::string, Id>& symbols_map) {
std::unordered_map<std::string, size_t> keys_counter;
NameCache name_cache;
Describe describe(graph_, name_cache);
for (const auto& id : types_.all_ids) {
std::string key = describe(id).ToString();
std::string unique_key = key + "_" + std::to_string(keys_counter[key]++);
symbols_map.emplace(unique_key, id);
}
}
private:
Graph& graph_;
dwarf::Types types_;
};
Id Read(Graph& graph, elf::ElfLoader&& elf,
std::optional<dwarf::Handler>&& dwarf, bool verbose) {
const auto all_symbols = elf.GetElfSymbols();
if (verbose) {
std::cout << "Parsed " << all_symbols.size() << " symbols\n";
}
std::vector<SymbolTableEntry> public_functions_and_variables;
public_functions_and_variables.reserve(all_symbols.size());
std::copy_if(all_symbols.begin(), all_symbols.end(),
std::back_inserter(public_functions_and_variables),
IsPublicFunctionOrVariable);
public_functions_and_variables.shrink_to_fit();
const CRCValuesMap crc_values = elf.IsLinuxKernelBinary()
? GetCRCValuesMap(all_symbols, elf)
: CRCValuesMap{};
if (verbose) {
std::cout << "File has " << public_functions_and_variables.size()
<< " public functions and variables:\n";
for (const auto& symbol : public_functions_and_variables) {
std::cout << " " << symbol.binding << ' ' << symbol.symbol_type << " '"
<< symbol.name << "'\n visibility=" << symbol.visibility
<< " size=" << symbol.size << " value=" << symbol.value << "["
<< symbol.value_type << "]\n";
}
}
Typing typing(graph, dwarf ? dwarf::Process(*dwarf, graph) : dwarf::Types{});
std::map<std::string, Id> symbols_map;
for (const auto& symbol : public_functions_and_variables) {
// TODO: add VersionInfoToString to SymbolKey name
// TODO: check for uniqueness of SymbolKey in map after support
// for version info
symbols_map.emplace(std::string(symbol.name),
graph.Add<ElfSymbol>(
SymbolTableEntryToElfSymbol(symbol, crc_values)));
}
typing.AddFakeSymbols(symbols_map);
return graph.Add<Symbols>(std::move(symbols_map));
}
} // namespace
Id Read(Graph& graph, const std::string& path, bool process_dwarf,
bool verbose) {
return Read(graph, elf::ElfLoader(path, verbose),
process_dwarf ? dwarf::Handler(path)
: std::optional<dwarf::Handler>(),
verbose);
}
Id Read(Graph& graph, char* data, size_t size, bool process_dwarf,
bool verbose) {
return Read(graph, elf::ElfLoader(data, size, verbose),
process_dwarf ? dwarf::Handler(data, size)
: std::optional<dwarf::Handler>(),
verbose);
}
} // namespace elf
} // namespace stg