mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 15:32:10 +08:00
f55a8d4ff3
Packaged test running seems to be a ways off, but with a bit of path fiddling in test_paths.cc we can actually use the paths where the tests are copied, so do that instead to get all the tests re-enabled. The setup in BUILD.gn should be mostly-useful once packaging is working as all helper/data files will need to specified there anyway. Also, attempted fix to flaky behaviour in ProcessReaderFuchsia.ChildThreads exposed because the tests are now being run. zx_object_wait_many() waits on *any* of the objects, not *all* of them. Derp! And finally, for the same test, work around some unintuitive behaviour in zx_task_suspend(), in particular that the thread will not be suspended for the purpose of reading registers right away, but instead only "sometime later", which appears in pratice to be after the next context switch. Have ScopedTaskSuspend block for a while to try to ensure the registers become readble, and if they don't, at least fail noisily at that point. Bug: crashpad:196 Change-Id: I01fb3590ede96301c941c2a88eba47fdbfe74ea7 Reviewed-on: https://chromium-review.googlesource.com/1053797 Reviewed-by: Joshua Peraza <jperaza@chromium.org> Commit-Queue: Scott Graham <scottmg@chromium.org>
359 lines
12 KiB
C++
359 lines
12 KiB
C++
// 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>
|
|
#include <link.h>
|
|
#include <unistd.h>
|
|
|
|
#include "base/logging.h"
|
|
#include "build/build_config.h"
|
|
#include "gtest/gtest.h"
|
|
#include "test/multiprocess_exec.h"
|
|
#include "test/process_type.h"
|
|
#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 <zircon/syscalls.h>
|
|
|
|
#include "base/fuchsia/fuchsia_logging.h"
|
|
|
|
#elif defined(OS_LINUX) || 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(ProcessType process,
|
|
ProcessMemory* memory,
|
|
VMAddress* elf_address) {
|
|
uintptr_t debug_address;
|
|
zx_status_t status = zx_object_get_property(process,
|
|
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_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);
|
|
const MemoryMap::Mapping* exe_mapping =
|
|
memory_map.FindFileMmapStart(*phdr_mapping);
|
|
ASSERT_TRUE(exe_mapping);
|
|
*elf_address = exe_mapping->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
|
|
|
|
ProcessMemoryNative memory;
|
|
ASSERT_TRUE(memory.Initialize(process));
|
|
ProcessMemoryRange range;
|
|
ASSERT_TRUE(range.Initialize(&memory, am_64_bit));
|
|
|
|
VMAddress elf_address;
|
|
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
|
FakePtraceConnection connection;
|
|
ASSERT_TRUE(connection.Initialize(process));
|
|
LocateExecutable(&connection, &memory, &elf_address);
|
|
#elif defined(OS_FUCHSIA)
|
|
LocateExecutable(process, &memory, &elf_address);
|
|
#endif
|
|
ASSERT_NO_FATAL_FAILURE();
|
|
|
|
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;
|
|
|
|
std::unique_ptr<ElfImageReader::NoteReader> notes = reader.Notes(-1);
|
|
while ((result = notes->NextNote(¬e_name, ¬e_type, ¬e_desc)) ==
|
|
ElfImageReader::NoteReader::Result::kSuccess) {
|
|
}
|
|
EXPECT_EQ(result, ElfImageReader::NoteReader::Result::kNoMoreNotes);
|
|
|
|
notes = reader.Notes(0);
|
|
EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc),
|
|
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, -1);
|
|
ASSERT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc),
|
|
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)*>(¬e_desc[0]),
|
|
kCrashpadNoteDesc);
|
|
|
|
EXPECT_EQ(notes->NextNote(¬e_name, ¬e_type, ¬e_desc),
|
|
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
|
|
|
|
ProcessMemoryNative memory;
|
|
ASSERT_TRUE(memory.Initialize(process));
|
|
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();
|
|
}
|
|
|
|
#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
|