crashpad/snapshot/elf/elf_image_reader_test.cc

364 lines
12 KiB
C++
Raw Normal View History

// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// 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.
#include "snapshot/elf/elf_image_reader.h"
#include <dlfcn.h>
Read either DT_HASH or DT_GNU_HASH to determine the size of DT_SYMTAB Without the section headers for the symbol table, there's no direct way to calculate the number of entries in the table. DT_HASH and DT_GNU_HASH are auxiliary tables that are designed to make symbol lookup faster. DT_HASH is the original and is theoretically mandatory. DT_GNU_HASH is the new-and-improved, but is more complex. In practice, however, an Android build (at least vs. API 16) has only DT_HASH, and not DT_GNU_HASH, and a Fuchsia build has only DT_GNU_HASH but not DT_HASH. So, both are tried. This change does not actually use the data in these tables to improve the speed of symbol lookup, but instead only uses them to correctly terminate the linear search. DT_HASH contains the total number of symbols in the symbol table fairly directly because there is an entry for each symbol table entry in the hash table, so the number is the same. DT_GNU_HASH regrettably does not. Instead, it's necessary to walk the buckets and chain structure to find the largest entry. DT_GNU_HASH doesn't appear in any "real" documentation that I'm aware of, other than the binutils code (at least as far as I know). Some more-and-less-useful references: - https://flapenguin.me/2017/04/24/elf-lookup-dt-hash/ - https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/ - http://deroko.phearless.org/dt_gnu_hash.txt - https://sourceware.org/ml/binutils/2006-10/msg00377.html Change-Id: I7cfc4372f29efc37446f0931d22a1f790e44076f Bug: crashpad:213, crashpad:196 Reviewed-on: https://chromium-review.googlesource.com/876879 Commit-Queue: Scott Graham <scottmg@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2018-01-30 14:30:50 -08:00
#include <link.h>
#include <unistd.h>
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "test/multiprocess_exec.h"
#include "test/process_type.h"
Read either DT_HASH or DT_GNU_HASH to determine the size of DT_SYMTAB Without the section headers for the symbol table, there's no direct way to calculate the number of entries in the table. DT_HASH and DT_GNU_HASH are auxiliary tables that are designed to make symbol lookup faster. DT_HASH is the original and is theoretically mandatory. DT_GNU_HASH is the new-and-improved, but is more complex. In practice, however, an Android build (at least vs. API 16) has only DT_HASH, and not DT_GNU_HASH, and a Fuchsia build has only DT_GNU_HASH but not DT_HASH. So, both are tried. This change does not actually use the data in these tables to improve the speed of symbol lookup, but instead only uses them to correctly terminate the linear search. DT_HASH contains the total number of symbols in the symbol table fairly directly because there is an entry for each symbol table entry in the hash table, so the number is the same. DT_GNU_HASH regrettably does not. Instead, it's necessary to walk the buckets and chain structure to find the largest entry. DT_GNU_HASH doesn't appear in any "real" documentation that I'm aware of, other than the binutils code (at least as far as I know). Some more-and-less-useful references: - https://flapenguin.me/2017/04/24/elf-lookup-dt-hash/ - https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/ - http://deroko.phearless.org/dt_gnu_hash.txt - https://sourceware.org/ml/binutils/2006-10/msg00377.html Change-Id: I7cfc4372f29efc37446f0931d22a1f790e44076f Bug: crashpad:213, crashpad:196 Reviewed-on: https://chromium-review.googlesource.com/876879 Commit-Queue: Scott Graham <scottmg@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2018-01-30 14:30:50 -08:00
#include "test/scoped_module_handle.h"
#include "test/test_paths.h"
#include "util/file/file_io.h"
#include "util/misc/address_types.h"
#include "util/misc/elf_note_types.h"
#include "util/misc/from_pointer_cast.h"
#include "util/process/process_memory_native.h"
#if defined(OS_FUCHSIA)
#include <lib/zx/process.h>
#include "base/fuchsia/fuchsia_logging.h"
#elif defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
#include "test/linux/fake_ptrace_connection.h"
#include "util/linux/auxiliary_vector.h"
#include "util/linux/memory_map.h"
#else
#error Port.
#endif // OS_FUCHSIA
extern "C" {
__attribute__((visibility("default"))) void ElfImageReaderTestExportedSymbol() {
}
} // extern "C"
namespace crashpad {
namespace test {
namespace {
#if defined(OS_FUCHSIA)
void LocateExecutable(const ProcessType& process,
ProcessMemory* memory,
VMAddress* elf_address) {
uintptr_t debug_address;
zx_status_t status = process->get_property(
ZX_PROP_PROCESS_DEBUG_ADDR, &debug_address, sizeof(debug_address));
ASSERT_EQ(status, ZX_OK)
<< "zx_object_get_property: ZX_PROP_PROCESS_DEBUG_ADDR";
// Can be 0 if requested before the loader has loaded anything.
EXPECT_NE(debug_address, 0u);
constexpr auto k_r_debug_map_offset = offsetof(r_debug, r_map);
uintptr_t map;
ASSERT_TRUE(
memory->Read(debug_address + k_r_debug_map_offset, sizeof(map), &map))
<< "read link_map";
constexpr auto k_link_map_addr_offset = offsetof(link_map, l_addr);
uintptr_t base;
ASSERT_TRUE(memory->Read(map + k_link_map_addr_offset, sizeof(base), &base))
<< "read base";
*elf_address = base;
}
#elif defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
void LocateExecutable(PtraceConnection* connection,
ProcessMemory* memory,
VMAddress* elf_address) {
AuxiliaryVector aux;
ASSERT_TRUE(aux.Initialize(connection));
VMAddress phdrs;
ASSERT_TRUE(aux.GetValue(AT_PHDR, &phdrs));
MemoryMap memory_map;
ASSERT_TRUE(memory_map.Initialize(connection));
const MemoryMap::Mapping* phdr_mapping = memory_map.FindMapping(phdrs);
ASSERT_TRUE(phdr_mapping);
auto possible_mappings = memory_map.FindFilePossibleMmapStarts(*phdr_mapping);
ASSERT_EQ(possible_mappings->Count(), 1u);
*elf_address = possible_mappings->Next()->range.Base();
}
#endif // OS_FUCHSIA
void ExpectSymbol(ElfImageReader* reader,
const std::string& symbol_name,
VMAddress expected_symbol_address) {
VMAddress symbol_address;
VMSize symbol_size;
ASSERT_TRUE(
reader->GetDynamicSymbol(symbol_name, &symbol_address, &symbol_size));
EXPECT_EQ(symbol_address, expected_symbol_address);
EXPECT_FALSE(
reader->GetDynamicSymbol("notasymbol", &symbol_address, &symbol_size));
}
void ReadThisExecutableInTarget(ProcessType process,
VMAddress exported_symbol_address) {
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif // ARCH_CPU_64_BITS
VMAddress elf_address;
#if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_ANDROID)
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(process));
ProcessMemoryLinux memory(&connection);
LocateExecutable(&connection, &memory, &elf_address);
#elif defined(OS_FUCHSIA)
ProcessMemoryFuchsia memory;
ASSERT_TRUE(memory.Initialize(process));
LocateExecutable(process, &memory, &elf_address);
#endif
ASSERT_NO_FATAL_FAILURE();
ProcessMemoryRange range;
ASSERT_TRUE(range.Initialize(&memory, am_64_bit));
ElfImageReader reader;
ASSERT_TRUE(reader.Initialize(range, elf_address));
ExpectSymbol(
&reader, "ElfImageReaderTestExportedSymbol", exported_symbol_address);
ElfImageReader::NoteReader::Result result;
std::string note_name;
std::string note_desc;
ElfImageReader::NoteReader::NoteType note_type;
Reland "Use a relative address in .note.crashpad.info" This is a reland of 95e97a32eba4d505ab9591e683d2147c441eea48 Original change's description: > Use a relative address in .note.crashpad.info > > The desc value in the note is now the offset of CRASHPAD_INFO_SYMBOL > from desc. > > Making this note writable can trigger a linker error resulting in > the binary embedding .note.crashpad.info to be rejected by the > kernel during program loading. > > The error was observed with: > GNU ld (GNU Binutils for Debian) 2.30 > clang version 4.0.1-10 (tags/RELEASE_401/final) > Debian 4.17.17-1rodete2 > > When the note is made writable, crashpad_snapshot_test contains two > PT_LOAD segments which map to the same page. > > LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 > 0x0000000000000258 0x0000000000000258 R 0x200000 > LOAD 0x0000000000000258 0x0000000000000258 0x0000000000000258 > 0x00000000002b84d8 0x00000000002b8950 RWE 0x200000 > > Executing this binary with the execv system call triggers a segfault > during program loading (an error can't be returned because the original > process vm has already been discarded). > > I suspect (I haven't set up a debuggable kernel) the failure occurs > while attempting to map the second load segment because its virtual > address, 0x258, is in the same page as the first load segment. > https://elixir.bootlin.com/linux/v4.17.17/source/fs/binfmt_elf.c#L380 > > The linker normally produces consecutive load segments where the second > segment is loaded 0x200000 bytes after the first, which I think is the > maximum expected page size. Modifying the test executable to load the > second segment at 0x1258 (4096 byte page size) allows program loading > to succeed (but of course crashes after control is given to it). > > Bug: crashpad:260 > Change-Id: I2b9f1e66e98919138baef3da991a9710bd970dc4 > Reviewed-on: https://chromium-review.googlesource.com/c/1292232 > Reviewed-by: Scott Graham <scottmg@chromium.org> > Reviewed-by: Mark Mentovai <mark@chromium.org> > Commit-Queue: Joshua Peraza <jperaza@chromium.org> Bug: crashpad:260 Change-Id: I66713de84cc26c9119e0454d19c9c189263fe054 Reviewed-on: https://chromium-review.googlesource.com/c/1318066 Commit-Queue: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Scott Graham <scottmg@chromium.org>
2018-11-06 13:19:06 -08:00
VMAddress desc_addr;
std::unique_ptr<ElfImageReader::NoteReader> notes = reader.Notes(10000);
Reland "Use a relative address in .note.crashpad.info" This is a reland of 95e97a32eba4d505ab9591e683d2147c441eea48 Original change's description: > Use a relative address in .note.crashpad.info > > The desc value in the note is now the offset of CRASHPAD_INFO_SYMBOL > from desc. > > Making this note writable can trigger a linker error resulting in > the binary embedding .note.crashpad.info to be rejected by the > kernel during program loading. > > The error was observed with: > GNU ld (GNU Binutils for Debian) 2.30 > clang version 4.0.1-10 (tags/RELEASE_401/final) > Debian 4.17.17-1rodete2 > > When the note is made writable, crashpad_snapshot_test contains two > PT_LOAD segments which map to the same page. > > LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 > 0x0000000000000258 0x0000000000000258 R 0x200000 > LOAD 0x0000000000000258 0x0000000000000258 0x0000000000000258 > 0x00000000002b84d8 0x00000000002b8950 RWE 0x200000 > > Executing this binary with the execv system call triggers a segfault > during program loading (an error can't be returned because the original > process vm has already been discarded). > > I suspect (I haven't set up a debuggable kernel) the failure occurs > while attempting to map the second load segment because its virtual > address, 0x258, is in the same page as the first load segment. > https://elixir.bootlin.com/linux/v4.17.17/source/fs/binfmt_elf.c#L380 > > The linker normally produces consecutive load segments where the second > segment is loaded 0x200000 bytes after the first, which I think is the > maximum expected page size. Modifying the test executable to load the > second segment at 0x1258 (4096 byte page size) allows program loading > to succeed (but of course crashes after control is given to it). > > Bug: crashpad:260 > Change-Id: I2b9f1e66e98919138baef3da991a9710bd970dc4 > Reviewed-on: https://chromium-review.googlesource.com/c/1292232 > Reviewed-by: Scott Graham <scottmg@chromium.org> > Reviewed-by: Mark Mentovai <mark@chromium.org> > Commit-Queue: Joshua Peraza <jperaza@chromium.org> Bug: crashpad:260 Change-Id: I66713de84cc26c9119e0454d19c9c189263fe054 Reviewed-on: https://chromium-review.googlesource.com/c/1318066 Commit-Queue: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Scott Graham <scottmg@chromium.org>
2018-11-06 13:19:06 -08:00
while ((result = notes->NextNote(
&note_name, &note_type, &note_desc, &desc_addr)) ==
ElfImageReader::NoteReader::Result::kSuccess) {
}
EXPECT_EQ(result, ElfImageReader::NoteReader::Result::kNoMoreNotes);
notes = reader.Notes(0);
Reland "Use a relative address in .note.crashpad.info" This is a reland of 95e97a32eba4d505ab9591e683d2147c441eea48 Original change's description: > Use a relative address in .note.crashpad.info > > The desc value in the note is now the offset of CRASHPAD_INFO_SYMBOL > from desc. > > Making this note writable can trigger a linker error resulting in > the binary embedding .note.crashpad.info to be rejected by the > kernel during program loading. > > The error was observed with: > GNU ld (GNU Binutils for Debian) 2.30 > clang version 4.0.1-10 (tags/RELEASE_401/final) > Debian 4.17.17-1rodete2 > > When the note is made writable, crashpad_snapshot_test contains two > PT_LOAD segments which map to the same page. > > LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 > 0x0000000000000258 0x0000000000000258 R 0x200000 > LOAD 0x0000000000000258 0x0000000000000258 0x0000000000000258 > 0x00000000002b84d8 0x00000000002b8950 RWE 0x200000 > > Executing this binary with the execv system call triggers a segfault > during program loading (an error can't be returned because the original > process vm has already been discarded). > > I suspect (I haven't set up a debuggable kernel) the failure occurs > while attempting to map the second load segment because its virtual > address, 0x258, is in the same page as the first load segment. > https://elixir.bootlin.com/linux/v4.17.17/source/fs/binfmt_elf.c#L380 > > The linker normally produces consecutive load segments where the second > segment is loaded 0x200000 bytes after the first, which I think is the > maximum expected page size. Modifying the test executable to load the > second segment at 0x1258 (4096 byte page size) allows program loading > to succeed (but of course crashes after control is given to it). > > Bug: crashpad:260 > Change-Id: I2b9f1e66e98919138baef3da991a9710bd970dc4 > Reviewed-on: https://chromium-review.googlesource.com/c/1292232 > Reviewed-by: Scott Graham <scottmg@chromium.org> > Reviewed-by: Mark Mentovai <mark@chromium.org> > Commit-Queue: Joshua Peraza <jperaza@chromium.org> Bug: crashpad:260 Change-Id: I66713de84cc26c9119e0454d19c9c189263fe054 Reviewed-on: https://chromium-review.googlesource.com/c/1318066 Commit-Queue: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Scott Graham <scottmg@chromium.org>
2018-11-06 13:19:06 -08:00
EXPECT_EQ(notes->NextNote(&note_name, &note_type, &note_desc, &desc_addr),
ElfImageReader::NoteReader::Result::kNoMoreNotes);
// Find the note defined in elf_image_reader_test_note.S.
constexpr uint32_t kCrashpadNoteDesc = 42;
notes = reader.NotesWithNameAndType(
CRASHPAD_ELF_NOTE_NAME, CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST, 10000);
Reland "Use a relative address in .note.crashpad.info" This is a reland of 95e97a32eba4d505ab9591e683d2147c441eea48 Original change's description: > Use a relative address in .note.crashpad.info > > The desc value in the note is now the offset of CRASHPAD_INFO_SYMBOL > from desc. > > Making this note writable can trigger a linker error resulting in > the binary embedding .note.crashpad.info to be rejected by the > kernel during program loading. > > The error was observed with: > GNU ld (GNU Binutils for Debian) 2.30 > clang version 4.0.1-10 (tags/RELEASE_401/final) > Debian 4.17.17-1rodete2 > > When the note is made writable, crashpad_snapshot_test contains two > PT_LOAD segments which map to the same page. > > LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 > 0x0000000000000258 0x0000000000000258 R 0x200000 > LOAD 0x0000000000000258 0x0000000000000258 0x0000000000000258 > 0x00000000002b84d8 0x00000000002b8950 RWE 0x200000 > > Executing this binary with the execv system call triggers a segfault > during program loading (an error can't be returned because the original > process vm has already been discarded). > > I suspect (I haven't set up a debuggable kernel) the failure occurs > while attempting to map the second load segment because its virtual > address, 0x258, is in the same page as the first load segment. > https://elixir.bootlin.com/linux/v4.17.17/source/fs/binfmt_elf.c#L380 > > The linker normally produces consecutive load segments where the second > segment is loaded 0x200000 bytes after the first, which I think is the > maximum expected page size. Modifying the test executable to load the > second segment at 0x1258 (4096 byte page size) allows program loading > to succeed (but of course crashes after control is given to it). > > Bug: crashpad:260 > Change-Id: I2b9f1e66e98919138baef3da991a9710bd970dc4 > Reviewed-on: https://chromium-review.googlesource.com/c/1292232 > Reviewed-by: Scott Graham <scottmg@chromium.org> > Reviewed-by: Mark Mentovai <mark@chromium.org> > Commit-Queue: Joshua Peraza <jperaza@chromium.org> Bug: crashpad:260 Change-Id: I66713de84cc26c9119e0454d19c9c189263fe054 Reviewed-on: https://chromium-review.googlesource.com/c/1318066 Commit-Queue: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Scott Graham <scottmg@chromium.org>
2018-11-06 13:19:06 -08:00
ASSERT_EQ(notes->NextNote(&note_name, &note_type, &note_desc, &desc_addr),
ElfImageReader::NoteReader::Result::kSuccess);
EXPECT_EQ(note_name, CRASHPAD_ELF_NOTE_NAME);
EXPECT_EQ(note_type,
implicit_cast<unsigned int>(CRASHPAD_ELF_NOTE_TYPE_SNAPSHOT_TEST));
EXPECT_EQ(note_desc.size(), sizeof(kCrashpadNoteDesc));
EXPECT_EQ(*reinterpret_cast<decltype(kCrashpadNoteDesc)*>(&note_desc[0]),
kCrashpadNoteDesc);
Reland "Use a relative address in .note.crashpad.info" This is a reland of 95e97a32eba4d505ab9591e683d2147c441eea48 Original change's description: > Use a relative address in .note.crashpad.info > > The desc value in the note is now the offset of CRASHPAD_INFO_SYMBOL > from desc. > > Making this note writable can trigger a linker error resulting in > the binary embedding .note.crashpad.info to be rejected by the > kernel during program loading. > > The error was observed with: > GNU ld (GNU Binutils for Debian) 2.30 > clang version 4.0.1-10 (tags/RELEASE_401/final) > Debian 4.17.17-1rodete2 > > When the note is made writable, crashpad_snapshot_test contains two > PT_LOAD segments which map to the same page. > > LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 > 0x0000000000000258 0x0000000000000258 R 0x200000 > LOAD 0x0000000000000258 0x0000000000000258 0x0000000000000258 > 0x00000000002b84d8 0x00000000002b8950 RWE 0x200000 > > Executing this binary with the execv system call triggers a segfault > during program loading (an error can't be returned because the original > process vm has already been discarded). > > I suspect (I haven't set up a debuggable kernel) the failure occurs > while attempting to map the second load segment because its virtual > address, 0x258, is in the same page as the first load segment. > https://elixir.bootlin.com/linux/v4.17.17/source/fs/binfmt_elf.c#L380 > > The linker normally produces consecutive load segments where the second > segment is loaded 0x200000 bytes after the first, which I think is the > maximum expected page size. Modifying the test executable to load the > second segment at 0x1258 (4096 byte page size) allows program loading > to succeed (but of course crashes after control is given to it). > > Bug: crashpad:260 > Change-Id: I2b9f1e66e98919138baef3da991a9710bd970dc4 > Reviewed-on: https://chromium-review.googlesource.com/c/1292232 > Reviewed-by: Scott Graham <scottmg@chromium.org> > Reviewed-by: Mark Mentovai <mark@chromium.org> > Commit-Queue: Joshua Peraza <jperaza@chromium.org> Bug: crashpad:260 Change-Id: I66713de84cc26c9119e0454d19c9c189263fe054 Reviewed-on: https://chromium-review.googlesource.com/c/1318066 Commit-Queue: Joshua Peraza <jperaza@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Scott Graham <scottmg@chromium.org>
2018-11-06 13:19:06 -08:00
EXPECT_EQ(notes->NextNote(&note_name, &note_type, &note_desc, &desc_addr),
ElfImageReader::NoteReader::Result::kNoMoreNotes);
}
void ReadLibcInTarget(ProcessType process,
VMAddress elf_address,
VMAddress getpid_address) {
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif // ARCH_CPU_64_BITS
#if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS)
FakePtraceConnection connection;
ASSERT_TRUE(connection.Initialize(process));
ProcessMemoryLinux memory(&connection);
#else
ProcessMemoryNative memory;
ASSERT_TRUE(memory.Initialize(process));
#endif
ProcessMemoryRange range;
ASSERT_TRUE(range.Initialize(&memory, am_64_bit));
ElfImageReader reader;
ASSERT_TRUE(reader.Initialize(range, elf_address));
ExpectSymbol(&reader, "getpid", getpid_address);
}
TEST(ElfImageReader, MainExecutableSelf) {
ReadThisExecutableInTarget(
GetSelfProcess(),
FromPointerCast<VMAddress>(ElfImageReaderTestExportedSymbol));
}
CRASHPAD_CHILD_TEST_MAIN(ReadExecutableChild) {
VMAddress exported_symbol_address =
FromPointerCast<VMAddress>(ElfImageReaderTestExportedSymbol);
CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput),
&exported_symbol_address,
sizeof(exported_symbol_address));
CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
return 0;
}
class ReadExecutableChildTest : public MultiprocessExec {
public:
ReadExecutableChildTest() : MultiprocessExec() {}
private:
void MultiprocessParent() {
// This read serves two purposes -- on Fuchsia, the loader may have not
// filled in debug address as soon as the child process handle is valid, so
// this causes a wait at least until the main() of the child, at which point
// it will always be valid. Secondarily, the address of the symbol to be
// looked up needs to be communicated.
VMAddress exported_symbol_address;
CheckedReadFileExactly(ReadPipeHandle(),
&exported_symbol_address,
sizeof(exported_symbol_address));
ReadThisExecutableInTarget(ChildProcess(), exported_symbol_address);
}
};
TEST(ElfImageReader, MainExecutableChild) {
ReadExecutableChildTest test;
test.SetChildTestMainFunction("ReadExecutableChild");
test.Run();
}
TEST(ElfImageReader, OneModuleSelf) {
Dl_info info;
ASSERT_TRUE(dladdr(reinterpret_cast<void*>(getpid), &info)) << "dladdr:"
<< dlerror();
VMAddress elf_address = FromPointerCast<VMAddress>(info.dli_fbase);
ReadLibcInTarget(
GetSelfProcess(), elf_address, FromPointerCast<VMAddress>(getpid));
}
CRASHPAD_CHILD_TEST_MAIN(ReadLibcChild) {
// Get the address of libc (by using getpid() as a representative member),
// and also the address of getpid() itself, and write them to the parent, so
// it can validate reading this information back out.
Dl_info info;
EXPECT_TRUE(dladdr(reinterpret_cast<void*>(getpid), &info))
<< "dladdr:" << dlerror();
VMAddress elf_address = FromPointerCast<VMAddress>(info.dli_fbase);
VMAddress getpid_address = FromPointerCast<VMAddress>(getpid);
CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput),
&elf_address,
sizeof(elf_address));
CheckedWriteFile(StdioFileHandle(StdioStream::kStandardOutput),
&getpid_address,
sizeof(getpid_address));
CheckedReadFileAtEOF(StdioFileHandle(StdioStream::kStandardInput));
return 0;
}
class ReadLibcChildTest : public MultiprocessExec {
public:
ReadLibcChildTest() : MultiprocessExec() {}
~ReadLibcChildTest() {}
private:
void MultiprocessParent() {
VMAddress elf_address, getpid_address;
CheckedReadFileExactly(ReadPipeHandle(), &elf_address, sizeof(elf_address));
CheckedReadFileExactly(
ReadPipeHandle(), &getpid_address, sizeof(getpid_address));
ReadLibcInTarget(ChildProcess(), elf_address, getpid_address);
}
};
TEST(ElfImageReader, OneModuleChild) {
ReadLibcChildTest test;
test.SetChildTestMainFunction("ReadLibcChild");
test.Run();
}
Read either DT_HASH or DT_GNU_HASH to determine the size of DT_SYMTAB Without the section headers for the symbol table, there's no direct way to calculate the number of entries in the table. DT_HASH and DT_GNU_HASH are auxiliary tables that are designed to make symbol lookup faster. DT_HASH is the original and is theoretically mandatory. DT_GNU_HASH is the new-and-improved, but is more complex. In practice, however, an Android build (at least vs. API 16) has only DT_HASH, and not DT_GNU_HASH, and a Fuchsia build has only DT_GNU_HASH but not DT_HASH. So, both are tried. This change does not actually use the data in these tables to improve the speed of symbol lookup, but instead only uses them to correctly terminate the linear search. DT_HASH contains the total number of symbols in the symbol table fairly directly because there is an entry for each symbol table entry in the hash table, so the number is the same. DT_GNU_HASH regrettably does not. Instead, it's necessary to walk the buckets and chain structure to find the largest entry. DT_GNU_HASH doesn't appear in any "real" documentation that I'm aware of, other than the binutils code (at least as far as I know). Some more-and-less-useful references: - https://flapenguin.me/2017/04/24/elf-lookup-dt-hash/ - https://flapenguin.me/2017/05/10/elf-lookup-dt-gnu-hash/ - http://deroko.phearless.org/dt_gnu_hash.txt - https://sourceware.org/ml/binutils/2006-10/msg00377.html Change-Id: I7cfc4372f29efc37446f0931d22a1f790e44076f Bug: crashpad:213, crashpad:196 Reviewed-on: https://chromium-review.googlesource.com/876879 Commit-Queue: Scott Graham <scottmg@chromium.org> Reviewed-by: Mark Mentovai <mark@chromium.org> Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2018-01-30 14:30:50 -08:00
#if defined(OS_FUCHSIA)
// crashpad_snapshot_test_both_dt_hash_styles is specially built and forced to
// include both .hash and .gnu.hash sections. Linux, Android, and Fuchsia have
// different defaults for which of these sections should be included; this test
// confirms that we get the same count from both sections.
//
// TODO(scottmg): Investigation in https://crrev.com/c/876879 resulted in
// realizing that ld.bfd does not emit a .gnu.hash that is very useful for this
// purpose when there's 0 exported entries in the module. This is not likely to
// be too important, as there's little need to look up non-exported symbols.
// However, it makes this test not work on Linux, where the default build uses
// ld.bfd. On Fuchsia, the only linker in use is lld, and it generates the
// expected .gnu.hash. So, for now, this test is only run on Fuchsia, not Linux.
//
// TODO(scottmg): Separately, the location of the ELF on Android needs some
// work, and then the test could also be enabled there.
TEST(ElfImageReader, DtHashAndDtGnuHashMatch) {
base::FilePath module_path =
TestPaths::BuildArtifact(FILE_PATH_LITERAL("snapshot"),
FILE_PATH_LITERAL("both_dt_hash_styles"),
TestPaths::FileType::kLoadableModule);
// TODO(scottmg): Remove this when upstream Fuchsia bug ZX-1619 is resolved.
// See also explanation in build/run_tests.py for Fuchsia .so files.
module_path = module_path.BaseName();
ScopedModuleHandle module(
dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL));
ASSERT_TRUE(module.valid()) << "dlopen " << module_path.value() << ": "
<< dlerror();
#if defined(ARCH_CPU_64_BITS)
constexpr bool am_64_bit = true;
#else
constexpr bool am_64_bit = false;
#endif // ARCH_CPU_64_BITS
ProcessMemoryNative memory;
ASSERT_TRUE(memory.Initialize(GetSelfProcess()));
ProcessMemoryRange range;
ASSERT_TRUE(range.Initialize(&memory, am_64_bit));
struct link_map* lm = reinterpret_cast<struct link_map*>(module.get());
ElfImageReader reader;
ASSERT_TRUE(reader.Initialize(range, lm->l_addr));
VMSize from_dt_hash;
ASSERT_TRUE(reader.GetNumberOfSymbolEntriesFromDtHash(&from_dt_hash));
VMSize from_dt_gnu_hash;
ASSERT_TRUE(reader.GetNumberOfSymbolEntriesFromDtGnuHash(&from_dt_gnu_hash));
EXPECT_EQ(from_dt_hash, from_dt_gnu_hash);
}
#endif // OS_FUCHSIA
} // namespace
} // namespace test
} // namespace crashpad