Merge "Add proper support for embedded elf files."

am: 537c68c8ed

Change-Id: Ia28dbb1bbd02d54602a6256295cccf2def9caf04
This commit is contained in:
Christopher Ferris 2017-08-31 02:25:05 +00:00 committed by android-build-merger
commit 863fcdb0ca
8 changed files with 210 additions and 40 deletions

View File

@ -124,6 +124,28 @@ bool Elf::IsValidElf(Memory* memory) {
return true;
}
void Elf::GetInfo(Memory* memory, bool* valid, uint64_t* size) {
if (!IsValidElf(memory)) {
*valid = false;
return;
}
*size = 0;
*valid = true;
// Now read the section header information.
uint8_t class_type;
if (!memory->Read(EI_CLASS, &class_type, 1)) {
return;
}
if (class_type == ELFCLASS32) {
ElfInterface32::GetMaxSize(memory, size);
} else if (class_type == ELFCLASS64) {
ElfInterface64::GetMaxSize(memory, size);
} else {
*valid = false;
}
}
ElfInterface* Elf::CreateInterfaceFromMemory(Memory* memory) {
if (!IsValidElf(memory)) {
return nullptr;

View File

@ -370,6 +370,22 @@ bool ElfInterface::Step(uint64_t pc, Regs* regs, Memory* process_memory) {
return false;
}
// This is an estimation of the size of the elf file using the location
// of the section headers and size. This assumes that the section headers
// are at the end of the elf file. If the elf has a load bias, the size
// will be too large, but this is acceptable.
template <typename EhdrType>
void ElfInterface::GetMaxSizeWithTemplate(Memory* memory, uint64_t* size) {
EhdrType ehdr;
if (!memory->Read(0, &ehdr, sizeof(ehdr))) {
return;
}
if (ehdr.e_shnum == 0) {
return;
}
*size = ehdr.e_shoff + ehdr.e_shentsize * ehdr.e_shnum;
}
// Instantiate all of the needed template functions.
template void ElfInterface::InitHeadersWithTemplate<uint32_t>();
template void ElfInterface::InitHeadersWithTemplate<uint64_t>();
@ -391,4 +407,7 @@ template bool ElfInterface::GetFunctionNameWithTemplate<Elf32_Sym>(uint64_t, std
template bool ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(uint64_t, std::string*,
uint64_t*);
template void ElfInterface::GetMaxSizeWithTemplate<Elf32_Ehdr>(Memory*, uint64_t*);
template void ElfInterface::GetMaxSizeWithTemplate<Elf64_Ehdr>(Memory*, uint64_t*);
} // namespace unwindstack

View File

@ -27,6 +27,55 @@
namespace unwindstack {
Memory* MapInfo::GetFileMemory() {
std::unique_ptr<MemoryFileAtOffset> memory(new MemoryFileAtOffset);
if (offset == 0) {
if (memory->Init(name, 0)) {
return memory.release();
}
return nullptr;
}
// There are two possibilities when the offset is non-zero.
// - There is an elf file embedded in a file.
// - The whole file is an elf file, and the offset needs to be saved.
//
// Map in just the part of the file for the map. If this is not
// a valid elf, then reinit as if the whole file is an elf file.
// If the offset is a valid elf, then determine the size of the map
// and reinit to that size. This is needed because the dynamic linker
// only maps in a portion of the original elf, and never the symbol
// file data.
uint64_t map_size = end - start;
if (!memory->Init(name, offset, map_size)) {
return nullptr;
}
bool valid;
uint64_t max_size;
Elf::GetInfo(memory.get(), &valid, &max_size);
if (!valid) {
// Init as if the whole file is an elf.
if (memory->Init(name, 0)) {
elf_offset = offset;
return memory.release();
}
return nullptr;
}
if (max_size > map_size) {
if (memory->Init(name, offset, max_size)) {
return memory.release();
}
// Try to reinit using the default map_size.
if (memory->Init(name, offset, map_size)) {
return memory.release();
}
return nullptr;
}
return memory.release();
}
Memory* MapInfo::CreateMemory(pid_t pid) {
if (end <= start) {
return nullptr;
@ -40,33 +89,13 @@ Memory* MapInfo::CreateMemory(pid_t pid) {
if (flags & MAPS_FLAGS_DEVICE_MAP) {
return nullptr;
}
std::unique_ptr<MemoryFileAtOffset> file_memory(new MemoryFileAtOffset);
uint64_t map_size;
if (offset != 0) {
// Only map in a piece of the file.
map_size = end - start;
} else {
map_size = UINT64_MAX;
}
if (file_memory->Init(name, offset, map_size)) {
// It's possible that a non-zero offset might not be pointing to
// valid elf data. Check if this is a valid elf, and if not assume
// that this was meant to incorporate the entire file.
if (offset != 0 && !Elf::IsValidElf(file_memory.get())) {
// Don't bother checking the validity that will happen on the elf init.
if (file_memory->Init(name, 0)) {
elf_offset = offset;
return file_memory.release();
}
// Fall through if the init fails.
} else {
return file_memory.release();
}
Memory* memory = GetFileMemory();
if (memory != nullptr) {
return memory;
}
}
Memory* memory = nullptr;
Memory* memory;
if (pid == getpid()) {
memory = new MemoryLocal();
} else {

View File

@ -70,6 +70,8 @@ class Elf {
static bool IsValidElf(Memory* memory);
static void GetInfo(Memory* memory, bool* valid, uint64_t* size);
protected:
bool valid_ = false;
std::unique_ptr<ElfInterface> interface_;

View File

@ -102,6 +102,9 @@ class ElfInterface {
virtual bool HandleType(uint64_t, uint32_t) { return false; }
template <typename EhdrType>
static void GetMaxSizeWithTemplate(Memory* memory, uint64_t* size);
Memory* memory_;
std::unordered_map<uint64_t, LoadInfo> pt_loads_;
uint64_t load_bias_ = 0;
@ -146,6 +149,10 @@ class ElfInterface32 : public ElfInterface {
bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset) override {
return ElfInterface::GetFunctionNameWithTemplate<Elf32_Sym>(addr, name, func_offset);
}
static void GetMaxSize(Memory* memory, uint64_t* size) {
GetMaxSizeWithTemplate<Elf32_Ehdr>(memory, size);
}
};
class ElfInterface64 : public ElfInterface {
@ -166,6 +173,10 @@ class ElfInterface64 : public ElfInterface {
bool GetFunctionName(uint64_t addr, std::string* name, uint64_t* func_offset) override {
return ElfInterface::GetFunctionNameWithTemplate<Elf64_Sym>(addr, name, func_offset);
}
static void GetMaxSize(Memory* memory, uint64_t* size) {
GetMaxSizeWithTemplate<Elf64_Ehdr>(memory, size);
}
};
} // namespace unwindstack

View File

@ -40,6 +40,7 @@ struct MapInfo {
// instead of a portion of the file.
uint64_t elf_offset;
Memory* GetFileMemory();
Memory* CreateMemory(pid_t pid);
// This function guarantees it will never return nullptr.
Elf* GetElf(pid_t pid, bool init_gnu_debugdata = false);

View File

@ -38,30 +38,50 @@ namespace unwindstack {
class MapInfoCreateMemoryTest : public ::testing::Test {
protected:
template <typename Ehdr, typename Shdr>
static void InitElf(int fd, uint64_t file_offset, uint64_t sh_offset, uint8_t class_type) {
std::vector<uint8_t> buffer(20000);
memset(buffer.data(), 0, buffer.size());
Ehdr ehdr;
memset(&ehdr, 0, sizeof(ehdr));
memcpy(ehdr.e_ident, ELFMAG, SELFMAG);
ehdr.e_ident[EI_CLASS] = class_type;
ehdr.e_shoff = sh_offset;
ehdr.e_shentsize = sizeof(Shdr) + 100;
ehdr.e_shnum = 4;
memcpy(&buffer[file_offset], &ehdr, sizeof(ehdr));
ASSERT_TRUE(android::base::WriteFully(fd, buffer.data(), buffer.size()));
}
static void SetUpTestCase() {
std::vector<uint8_t> buffer(1024);
memset(buffer.data(), 0, buffer.size());
memcpy(buffer.data(), ELFMAG, SELFMAG);
for (size_t i = SELFMAG; i < buffer.size(); i++) {
buffer[i] = i / 256 + 1;
}
buffer[EI_CLASS] = ELFCLASS32;
ASSERT_TRUE(android::base::WriteFully(elf_.fd, buffer.data(), buffer.size()));
for (size_t i = 0; i < 0x100; i++) {
buffer[i] = i / 256 + 1;
}
memset(buffer.data(), 0, buffer.size());
memcpy(&buffer[0x100], ELFMAG, SELFMAG);
for (size_t i = 0x100 + SELFMAG; i < buffer.size(); i++) {
buffer[i] = i / 256 + 1;
}
buffer[0x100 + EI_CLASS] = ELFCLASS64;
ASSERT_TRUE(android::base::WriteFully(elf_at_100_.fd, buffer.data(), buffer.size()));
InitElf<Elf32_Ehdr, Elf32_Shdr>(elf32_at_map_.fd, 0x1000, 0x2000, ELFCLASS32);
InitElf<Elf64_Ehdr, Elf64_Shdr>(elf64_at_map_.fd, 0x2000, 0x3000, ELFCLASS64);
}
static TemporaryFile elf_;
static TemporaryFile elf_at_100_;
static TemporaryFile elf32_at_map_;
static TemporaryFile elf64_at_map_;
};
TemporaryFile MapInfoCreateMemoryTest::elf_;
TemporaryFile MapInfoCreateMemoryTest::elf_at_100_;
TemporaryFile MapInfoCreateMemoryTest::elf32_at_map_;
TemporaryFile MapInfoCreateMemoryTest::elf64_at_map_;
TEST_F(MapInfoCreateMemoryTest, end_le_start) {
MapInfo info{.start = 0x100, .end = 0x100, .offset = 0, .name = elf_.path};
@ -93,8 +113,9 @@ TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_full_file) {
std::vector<uint8_t> buffer(1024);
ASSERT_TRUE(memory->Read(0, buffer.data(), 1024));
ASSERT_TRUE(memcmp(buffer.data(), ELFMAG, SELFMAG) == 0);
for (size_t i = SELFMAG; i < buffer.size(); i++) {
ASSERT_EQ(i / 256 + 1, buffer[i]) << "Failed at byte " << i;
ASSERT_EQ(ELFCLASS32, buffer[EI_CLASS]);
for (size_t i = EI_CLASS + 1; i < buffer.size(); i++) {
ASSERT_EQ(0, buffer[i]) << "Failed at byte " << i;
}
ASSERT_FALSE(memory->Read(1024, buffer.data(), 1));
@ -113,13 +134,50 @@ TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file) {
std::vector<uint8_t> buffer(0x100);
ASSERT_TRUE(memory->Read(0, buffer.data(), 0x100));
ASSERT_TRUE(memcmp(buffer.data(), ELFMAG, SELFMAG) == 0);
for (size_t i = SELFMAG; i < buffer.size(); i++) {
ASSERT_EQ(2, buffer[i]) << "Failed at byte " << i;
ASSERT_EQ(ELFCLASS64, buffer[EI_CLASS]);
for (size_t i = EI_CLASS + 1; i < buffer.size(); i++) {
ASSERT_EQ(0, buffer[i]) << "Failed at byte " << i;
}
ASSERT_FALSE(memory->Read(0x100, buffer.data(), 1));
}
// Verify that if the offset is non-zero and there is an elf at that
// offset, that only part of the file is used. Further verify that if the
// embedded elf is bigger than the initial map, the new object is larger
// than the original map size. Do this for a 32 bit elf and a 64 bit elf.
TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file_whole_elf32) {
MapInfo info{.start = 0x5000, .end = 0x6000, .offset = 0x1000, .name = elf32_at_map_.path};
std::unique_ptr<Memory> memory(info.CreateMemory(getpid()));
ASSERT_TRUE(memory.get() != nullptr);
ASSERT_EQ(0U, info.elf_offset);
// Verify the memory is a valid elf.
uint8_t e_ident[SELFMAG + 1];
ASSERT_TRUE(memory->Read(0, e_ident, SELFMAG));
ASSERT_EQ(0, memcmp(e_ident, ELFMAG, SELFMAG));
// Read past the end of what would normally be the size of the map.
ASSERT_TRUE(memory->Read(0x1000, e_ident, 1));
}
TEST_F(MapInfoCreateMemoryTest, file_backed_non_zero_offset_partial_file_whole_elf64) {
MapInfo info{.start = 0x7000, .end = 0x8000, .offset = 0x2000, .name = elf64_at_map_.path};
std::unique_ptr<Memory> memory(info.CreateMemory(getpid()));
ASSERT_TRUE(memory.get() != nullptr);
ASSERT_EQ(0U, info.elf_offset);
// Verify the memory is a valid elf.
uint8_t e_ident[SELFMAG + 1];
ASSERT_TRUE(memory->Read(0, e_ident, SELFMAG));
ASSERT_EQ(0, memcmp(e_ident, ELFMAG, SELFMAG));
// Read past the end of what would normally be the size of the map.
ASSERT_TRUE(memory->Read(0x1000, e_ident, 1));
}
// Verify that device file names will never result in Memory object creation.
TEST_F(MapInfoCreateMemoryTest, check_device_maps) {
// Set up some memory so that a valid local memory object would

View File

@ -28,8 +28,11 @@
#include <unwindstack/Memory.h>
int main(int argc, char** argv) {
if (argc != 2) {
printf("Need to pass the name of an elf file to the program.\n");
if (argc != 2 && argc != 3) {
printf("Usage: unwind_symbols <ELF_FILE> [<FUNC_ADDRESS>]\n");
printf(" Dump all function symbols in ELF_FILE. If FUNC_ADDRESS is\n");
printf(" specified, then get the function at that address.\n");
printf(" FUNC_ADDRESS must be a hex number.\n");
return 1;
}
@ -43,6 +46,16 @@ int main(int argc, char** argv) {
return 1;
}
uint64_t func_addr;
if (argc == 3) {
char* name;
func_addr = strtoull(argv[2], &name, 16);
if (*name != '\0') {
printf("%s is not a hex number.\n", argv[2]);
return 1;
}
}
// Send all log messages to stdout.
unwindstack::log_to_stdout(true);
@ -76,9 +89,24 @@ int main(int argc, char** argv) {
return 1;
}
// This is a crude way to get the symbols in order.
std::string name;
uint64_t load_bias = elf.interface()->load_bias();
if (argc == 3) {
std::string cur_name;
uint64_t func_offset;
if (!elf.GetFunctionName(func_addr, &cur_name, &func_offset)) {
printf("No known function at 0x%" PRIx64 "\n", func_addr);
return 1;
}
printf("<0x%" PRIx64 ">", func_addr - func_offset);
if (func_offset != 0) {
printf("+%" PRId64, func_offset);
}
printf(": %s\n", cur_name.c_str());
return 0;
}
// This is a crude way to get the symbols in order.
for (const auto& entry : elf.interface()->pt_loads()) {
uint64_t start = entry.second.offset + load_bias;
uint64_t end = entry.second.table_size + load_bias;