mirror of
https://github.com/chromium/crashpad.git
synced 2025-03-10 06:36:02 +00:00
On Android P, Bionic mistakenly places the vdso first in the list where the executable should be. Also correctly set the section size in the section headers for test module string tables. Bug: chromium:1050178 Change-Id: I83581d05c5ed3e25a237d1ce4a27c45755a3ab3c Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2231525 Reviewed-by: Mark Mentovai <mark@chromium.org> Commit-Queue: Joshua Peraza <jperaza@chromium.org>
860 lines
27 KiB
C++
860 lines
27 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/linux/process_reader_linux.h"
|
|
|
|
#include <dlfcn.h>
|
|
#include <elf.h>
|
|
#include <errno.h>
|
|
#include <link.h>
|
|
#include <pthread.h>
|
|
#include <sched.h>
|
|
#include <stdlib.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/resource.h>
|
|
#include <sys/syscall.h>
|
|
#include <unistd.h>
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "base/format_macros.h"
|
|
#include "base/memory/free_deleter.h"
|
|
#include "base/stl_util.h"
|
|
#include "base/strings/stringprintf.h"
|
|
#include "build/build_config.h"
|
|
#include "gtest/gtest.h"
|
|
#include "test/errors.h"
|
|
#include "test/linux/fake_ptrace_connection.h"
|
|
#include "test/linux/get_tls.h"
|
|
#include "test/multiprocess.h"
|
|
#include "test/scoped_module_handle.h"
|
|
#include "test/test_paths.h"
|
|
#include "util/file/file_io.h"
|
|
#include "util/file/file_writer.h"
|
|
#include "util/file/filesystem.h"
|
|
#include "util/linux/direct_ptrace_connection.h"
|
|
#include "util/misc/address_sanitizer.h"
|
|
#include "util/misc/from_pointer_cast.h"
|
|
#include "util/misc/memory_sanitizer.h"
|
|
#include "util/synchronization/semaphore.h"
|
|
|
|
#if defined(OS_ANDROID)
|
|
#include <android/api-level.h>
|
|
#include <android/set_abort_message.h>
|
|
#include "dlfcn_internal.h"
|
|
|
|
// Normally this comes from set_abort_message.h, but only at API level 21.
|
|
extern "C" void android_set_abort_message(const char* msg)
|
|
__attribute__((weak));
|
|
#endif
|
|
|
|
namespace crashpad {
|
|
namespace test {
|
|
namespace {
|
|
|
|
pid_t gettid() {
|
|
return syscall(SYS_gettid);
|
|
}
|
|
|
|
TEST(ProcessReaderLinux, SelfBasic) {
|
|
FakePtraceConnection connection;
|
|
connection.Initialize(getpid());
|
|
|
|
ProcessReaderLinux process_reader;
|
|
ASSERT_TRUE(process_reader.Initialize(&connection));
|
|
|
|
#if defined(ARCH_CPU_64_BITS)
|
|
EXPECT_TRUE(process_reader.Is64Bit());
|
|
#else
|
|
EXPECT_FALSE(process_reader.Is64Bit());
|
|
#endif
|
|
|
|
EXPECT_EQ(process_reader.ProcessID(), getpid());
|
|
EXPECT_EQ(process_reader.ParentProcessID(), getppid());
|
|
|
|
static constexpr char kTestMemory[] = "Some test memory";
|
|
char buffer[base::size(kTestMemory)];
|
|
ASSERT_TRUE(process_reader.Memory()->Read(
|
|
reinterpret_cast<LinuxVMAddress>(kTestMemory),
|
|
sizeof(kTestMemory),
|
|
&buffer));
|
|
EXPECT_STREQ(kTestMemory, buffer);
|
|
|
|
EXPECT_EQ("", process_reader.AbortMessage());
|
|
}
|
|
|
|
constexpr char kTestMemory[] = "Read me from another process";
|
|
|
|
class BasicChildTest : public Multiprocess {
|
|
public:
|
|
BasicChildTest() : Multiprocess() {}
|
|
~BasicChildTest() {}
|
|
|
|
private:
|
|
void MultiprocessParent() override {
|
|
DirectPtraceConnection connection;
|
|
ASSERT_TRUE(connection.Initialize(ChildPID()));
|
|
|
|
ProcessReaderLinux process_reader;
|
|
ASSERT_TRUE(process_reader.Initialize(&connection));
|
|
|
|
#if !defined(ARCH_CPU_64_BITS)
|
|
EXPECT_FALSE(process_reader.Is64Bit());
|
|
#else
|
|
EXPECT_TRUE(process_reader.Is64Bit());
|
|
#endif
|
|
|
|
EXPECT_EQ(process_reader.ParentProcessID(), getpid());
|
|
EXPECT_EQ(process_reader.ProcessID(), ChildPID());
|
|
|
|
std::string read_string;
|
|
ASSERT_TRUE(process_reader.Memory()->ReadCString(
|
|
reinterpret_cast<LinuxVMAddress>(kTestMemory), &read_string));
|
|
EXPECT_EQ(read_string, kTestMemory);
|
|
}
|
|
|
|
void MultiprocessChild() override { CheckedReadFileAtEOF(ReadPipeHandle()); }
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(BasicChildTest);
|
|
};
|
|
|
|
TEST(ProcessReaderLinux, ChildBasic) {
|
|
BasicChildTest test;
|
|
test.Run();
|
|
}
|
|
|
|
class TestThreadPool {
|
|
public:
|
|
struct ThreadExpectation {
|
|
LinuxVMAddress tls = 0;
|
|
LinuxVMAddress stack_address = 0;
|
|
LinuxVMSize max_stack_size = 0;
|
|
int sched_policy = 0;
|
|
int static_priority = 0;
|
|
int nice_value = 0;
|
|
};
|
|
|
|
TestThreadPool() : threads_() {}
|
|
|
|
~TestThreadPool() {
|
|
for (const auto& thread : threads_) {
|
|
thread->exit_semaphore.Signal();
|
|
}
|
|
|
|
for (const auto& thread : threads_) {
|
|
EXPECT_EQ(pthread_join(thread->pthread, nullptr), 0)
|
|
<< ErrnoMessage("pthread_join");
|
|
}
|
|
}
|
|
|
|
void StartThreads(size_t thread_count, size_t stack_size = 0) {
|
|
for (size_t thread_index = 0; thread_index < thread_count; ++thread_index) {
|
|
threads_.push_back(std::make_unique<Thread>());
|
|
Thread* thread = threads_.back().get();
|
|
|
|
pthread_attr_t attr;
|
|
ASSERT_EQ(pthread_attr_init(&attr), 0)
|
|
<< ErrnoMessage("pthread_attr_init");
|
|
|
|
if (stack_size > 0) {
|
|
void* stack_ptr;
|
|
errno = posix_memalign(&stack_ptr, getpagesize(), stack_size);
|
|
ASSERT_EQ(errno, 0) << ErrnoMessage("posix_memalign");
|
|
|
|
thread->stack.reset(reinterpret_cast<char*>(stack_ptr));
|
|
|
|
ASSERT_EQ(pthread_attr_setstack(&attr, thread->stack.get(), stack_size),
|
|
0)
|
|
<< ErrnoMessage("pthread_attr_setstack");
|
|
thread->expectation.max_stack_size = stack_size;
|
|
}
|
|
|
|
ASSERT_EQ(pthread_attr_setschedpolicy(&attr, SCHED_OTHER), 0)
|
|
<< ErrnoMessage("pthread_attr_setschedpolicy");
|
|
thread->expectation.sched_policy = SCHED_OTHER;
|
|
|
|
sched_param param;
|
|
param.sched_priority = 0;
|
|
ASSERT_EQ(pthread_attr_setschedparam(&attr, ¶m), 0)
|
|
<< ErrnoMessage("pthread_attr_setschedparam");
|
|
thread->expectation.static_priority = 0;
|
|
|
|
thread->expectation.nice_value = thread_index % 20;
|
|
|
|
ASSERT_EQ(pthread_create(&thread->pthread, &attr, ThreadMain, thread), 0)
|
|
<< ErrnoMessage("pthread_create");
|
|
}
|
|
|
|
for (const auto& thread : threads_) {
|
|
thread->ready_semaphore.Wait();
|
|
}
|
|
}
|
|
|
|
pid_t GetThreadExpectation(size_t thread_index,
|
|
ThreadExpectation* expectation) {
|
|
CHECK_LT(thread_index, threads_.size());
|
|
|
|
const Thread* thread = threads_[thread_index].get();
|
|
*expectation = thread->expectation;
|
|
return thread->tid;
|
|
}
|
|
|
|
private:
|
|
struct Thread {
|
|
Thread()
|
|
: pthread(),
|
|
expectation(),
|
|
ready_semaphore(0),
|
|
exit_semaphore(0),
|
|
tid(-1) {}
|
|
~Thread() {}
|
|
|
|
pthread_t pthread;
|
|
ThreadExpectation expectation;
|
|
std::unique_ptr<char[], base::FreeDeleter> stack;
|
|
Semaphore ready_semaphore;
|
|
Semaphore exit_semaphore;
|
|
pid_t tid;
|
|
};
|
|
|
|
static void* ThreadMain(void* argument) {
|
|
Thread* thread = static_cast<Thread*>(argument);
|
|
|
|
CHECK_EQ(setpriority(PRIO_PROCESS, 0, thread->expectation.nice_value), 0)
|
|
<< ErrnoMessage("setpriority");
|
|
|
|
thread->expectation.tls = GetTLS();
|
|
thread->expectation.stack_address =
|
|
reinterpret_cast<LinuxVMAddress>(&thread);
|
|
thread->tid = gettid();
|
|
|
|
thread->ready_semaphore.Signal();
|
|
thread->exit_semaphore.Wait();
|
|
|
|
CHECK_EQ(pthread_self(), thread->pthread);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<std::unique_ptr<Thread>> threads_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(TestThreadPool);
|
|
};
|
|
|
|
using ThreadMap = std::map<pid_t, TestThreadPool::ThreadExpectation>;
|
|
|
|
void ExpectThreads(const ThreadMap& thread_map,
|
|
const std::vector<ProcessReaderLinux::Thread>& threads,
|
|
PtraceConnection* connection) {
|
|
ASSERT_EQ(threads.size(), thread_map.size());
|
|
|
|
MemoryMap memory_map;
|
|
ASSERT_TRUE(memory_map.Initialize(connection));
|
|
|
|
for (const auto& thread : threads) {
|
|
SCOPED_TRACE(
|
|
base::StringPrintf("Thread id %d, tls 0x%" PRIx64
|
|
", stack addr 0x%" PRIx64 ", stack size 0x%" PRIx64,
|
|
thread.tid,
|
|
thread.thread_info.thread_specific_data_address,
|
|
thread.stack_region_address,
|
|
thread.stack_region_size));
|
|
|
|
const auto& iterator = thread_map.find(thread.tid);
|
|
ASSERT_NE(iterator, thread_map.end());
|
|
|
|
EXPECT_EQ(thread.thread_info.thread_specific_data_address,
|
|
iterator->second.tls);
|
|
|
|
ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address));
|
|
ASSERT_TRUE(memory_map.FindMapping(thread.stack_region_address +
|
|
thread.stack_region_size - 1));
|
|
|
|
#if !defined(ADDRESS_SANITIZER)
|
|
// AddressSanitizer causes stack variables to be stored separately from the
|
|
// call stack.
|
|
EXPECT_LE(thread.stack_region_address, iterator->second.stack_address);
|
|
EXPECT_GE(thread.stack_region_address + thread.stack_region_size,
|
|
iterator->second.stack_address);
|
|
#endif // !defined(ADDRESS_SANITIZER)
|
|
|
|
if (iterator->second.max_stack_size) {
|
|
EXPECT_LT(thread.stack_region_size, iterator->second.max_stack_size);
|
|
}
|
|
|
|
EXPECT_EQ(thread.sched_policy, iterator->second.sched_policy);
|
|
EXPECT_EQ(thread.static_priority, iterator->second.static_priority);
|
|
EXPECT_EQ(thread.nice_value, iterator->second.nice_value);
|
|
}
|
|
}
|
|
|
|
class ChildThreadTest : public Multiprocess {
|
|
public:
|
|
ChildThreadTest(size_t stack_size = 0)
|
|
: Multiprocess(), stack_size_(stack_size) {}
|
|
~ChildThreadTest() {}
|
|
|
|
private:
|
|
void MultiprocessParent() override {
|
|
ThreadMap thread_map;
|
|
for (size_t thread_index = 0; thread_index < kThreadCount + 1;
|
|
++thread_index) {
|
|
pid_t tid;
|
|
TestThreadPool::ThreadExpectation expectation;
|
|
|
|
CheckedReadFileExactly(ReadPipeHandle(), &tid, sizeof(tid));
|
|
CheckedReadFileExactly(
|
|
ReadPipeHandle(), &expectation, sizeof(expectation));
|
|
thread_map[tid] = expectation;
|
|
}
|
|
|
|
DirectPtraceConnection connection;
|
|
ASSERT_TRUE(connection.Initialize(ChildPID()));
|
|
|
|
ProcessReaderLinux process_reader;
|
|
ASSERT_TRUE(process_reader.Initialize(&connection));
|
|
const std::vector<ProcessReaderLinux::Thread>& threads =
|
|
process_reader.Threads();
|
|
ExpectThreads(thread_map, threads, &connection);
|
|
}
|
|
|
|
void MultiprocessChild() override {
|
|
TestThreadPool thread_pool;
|
|
thread_pool.StartThreads(kThreadCount, stack_size_);
|
|
|
|
TestThreadPool::ThreadExpectation expectation;
|
|
#if defined(MEMORY_SANITIZER)
|
|
// memset() + re-initialization is required to zero padding bytes for MSan.
|
|
memset(&expectation, 0, sizeof(expectation));
|
|
#endif // defined(MEMORY_SANITIZER)
|
|
expectation = {};
|
|
expectation.tls = GetTLS();
|
|
expectation.stack_address = reinterpret_cast<LinuxVMAddress>(&thread_pool);
|
|
|
|
int res = sched_getscheduler(0);
|
|
ASSERT_GE(res, 0) << ErrnoMessage("sched_getscheduler");
|
|
expectation.sched_policy = res;
|
|
|
|
sched_param param;
|
|
ASSERT_EQ(sched_getparam(0, ¶m), 0) << ErrnoMessage("sched_getparam");
|
|
expectation.static_priority = param.sched_priority;
|
|
|
|
errno = 0;
|
|
res = getpriority(PRIO_PROCESS, 0);
|
|
ASSERT_FALSE(res == -1 && errno) << ErrnoMessage("getpriority");
|
|
expectation.nice_value = res;
|
|
|
|
pid_t tid = gettid();
|
|
|
|
CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid));
|
|
CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation));
|
|
|
|
for (size_t thread_index = 0; thread_index < kThreadCount; ++thread_index) {
|
|
tid = thread_pool.GetThreadExpectation(thread_index, &expectation);
|
|
CheckedWriteFile(WritePipeHandle(), &tid, sizeof(tid));
|
|
CheckedWriteFile(WritePipeHandle(), &expectation, sizeof(expectation));
|
|
}
|
|
|
|
CheckedReadFileAtEOF(ReadPipeHandle());
|
|
}
|
|
|
|
static constexpr size_t kThreadCount = 3;
|
|
const size_t stack_size_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ChildThreadTest);
|
|
};
|
|
|
|
TEST(ProcessReaderLinux, ChildWithThreads) {
|
|
ChildThreadTest test;
|
|
test.Run();
|
|
}
|
|
|
|
TEST(ProcessReaderLinux, ChildThreadsWithSmallUserStacks) {
|
|
ChildThreadTest test(PTHREAD_STACK_MIN);
|
|
test.Run();
|
|
}
|
|
|
|
// Tests a thread with a stack that spans multiple mappings.
|
|
class ChildWithSplitStackTest : public Multiprocess {
|
|
public:
|
|
ChildWithSplitStackTest() : Multiprocess(), page_size_(getpagesize()) {}
|
|
~ChildWithSplitStackTest() {}
|
|
|
|
private:
|
|
void MultiprocessParent() override {
|
|
LinuxVMAddress stack_addr1;
|
|
LinuxVMAddress stack_addr2;
|
|
LinuxVMAddress stack_addr3;
|
|
|
|
CheckedReadFileExactly(ReadPipeHandle(), &stack_addr1, sizeof(stack_addr1));
|
|
CheckedReadFileExactly(ReadPipeHandle(), &stack_addr2, sizeof(stack_addr2));
|
|
CheckedReadFileExactly(ReadPipeHandle(), &stack_addr3, sizeof(stack_addr3));
|
|
|
|
DirectPtraceConnection connection;
|
|
ASSERT_TRUE(connection.Initialize(ChildPID()));
|
|
|
|
ProcessReaderLinux process_reader;
|
|
ASSERT_TRUE(process_reader.Initialize(&connection));
|
|
|
|
const std::vector<ProcessReaderLinux::Thread>& threads =
|
|
process_reader.Threads();
|
|
ASSERT_EQ(threads.size(), 1u);
|
|
|
|
LinuxVMAddress thread_stack_start = threads[0].stack_region_address;
|
|
EXPECT_LE(thread_stack_start, stack_addr1);
|
|
EXPECT_LE(thread_stack_start, stack_addr2);
|
|
EXPECT_LE(thread_stack_start, stack_addr3);
|
|
|
|
LinuxVMAddress thread_stack_end =
|
|
thread_stack_start + threads[0].stack_region_size;
|
|
EXPECT_GE(thread_stack_end, stack_addr1);
|
|
EXPECT_GE(thread_stack_end, stack_addr2);
|
|
EXPECT_GE(thread_stack_end, stack_addr3);
|
|
}
|
|
|
|
void MultiprocessChild() override {
|
|
const LinuxVMSize stack_size = page_size_ * 4;
|
|
GrowStack(stack_size, reinterpret_cast<LinuxVMAddress>(&stack_size));
|
|
}
|
|
|
|
void GrowStack(LinuxVMSize stack_size, LinuxVMAddress bottom_of_stack) {
|
|
char stack_contents[4096];
|
|
auto stack_address = reinterpret_cast<LinuxVMAddress>(&stack_contents);
|
|
|
|
if (bottom_of_stack - stack_address < stack_size) {
|
|
GrowStack(stack_size, bottom_of_stack);
|
|
} else {
|
|
// Write-protect a page on our stack to split up the mapping
|
|
LinuxVMAddress page_addr =
|
|
stack_address - (stack_address % page_size_) + 2 * page_size_;
|
|
ASSERT_EQ(
|
|
mprotect(reinterpret_cast<void*>(page_addr), page_size_, PROT_READ),
|
|
0)
|
|
<< ErrnoMessage("mprotect");
|
|
|
|
CheckedWriteFile(
|
|
WritePipeHandle(), &bottom_of_stack, sizeof(bottom_of_stack));
|
|
CheckedWriteFile(WritePipeHandle(), &page_addr, sizeof(page_addr));
|
|
CheckedWriteFile(
|
|
WritePipeHandle(), &stack_address, sizeof(stack_address));
|
|
|
|
// Wait for parent to read us
|
|
CheckedReadFileAtEOF(ReadPipeHandle());
|
|
|
|
ASSERT_EQ(mprotect(reinterpret_cast<void*>(page_addr),
|
|
page_size_,
|
|
PROT_READ | PROT_WRITE),
|
|
0)
|
|
<< ErrnoMessage("mprotect");
|
|
}
|
|
}
|
|
|
|
const size_t page_size_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ChildWithSplitStackTest);
|
|
};
|
|
|
|
// AddressSanitizer with use-after-return detection causes stack variables to
|
|
// be allocated on the heap.
|
|
#if defined(ADDRESS_SANITIZER)
|
|
#define MAYBE_ChildWithSplitStack DISABLED_ChildWithSplitStack
|
|
#else
|
|
#define MAYBE_ChildWithSplitStack ChildWithSplitStack
|
|
#endif
|
|
TEST(ProcessReaderLinux, MAYBE_ChildWithSplitStack) {
|
|
ChildWithSplitStackTest test;
|
|
test.Run();
|
|
}
|
|
|
|
// Android doesn't provide dl_iterate_phdr on ARM until API 21.
|
|
#if !defined(OS_ANDROID) || !defined(ARCH_CPU_ARMEL) || __ANDROID_API__ >= 21
|
|
int ExpectFindModule(dl_phdr_info* info, size_t size, void* data) {
|
|
SCOPED_TRACE(
|
|
base::StringPrintf("module %s at 0x%" PRIx64 " phdrs 0x%" PRIx64,
|
|
info->dlpi_name,
|
|
LinuxVMAddress{info->dlpi_addr},
|
|
FromPointerCast<LinuxVMAddress>(info->dlpi_phdr)));
|
|
auto modules =
|
|
reinterpret_cast<const std::vector<ProcessReaderLinux::Module>*>(data);
|
|
|
|
|
|
#if defined(OS_ANDROID)
|
|
// Prior to API 27, Bionic includes a null entry for /system/bin/linker.
|
|
if (!info->dlpi_name) {
|
|
EXPECT_EQ(info->dlpi_addr, 0u);
|
|
EXPECT_EQ(info->dlpi_phnum, 0u);
|
|
EXPECT_EQ(info->dlpi_phdr, nullptr);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
// Bionic doesn't always set both of these addresses for the vdso and
|
|
// /system/bin/linker, but it does always set one of them.
|
|
VMAddress module_addr = info->dlpi_phdr
|
|
? FromPointerCast<LinuxVMAddress>(info->dlpi_phdr)
|
|
: info->dlpi_addr;
|
|
|
|
// TODO(jperaza): This can use a range map when one is available.
|
|
bool found = false;
|
|
for (const auto& module : *modules) {
|
|
if (module.elf_reader && module_addr >= module.elf_reader->Address() &&
|
|
module_addr <
|
|
module.elf_reader->Address() + module.elf_reader->Size()) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
EXPECT_TRUE(found);
|
|
return 0;
|
|
}
|
|
#endif // !OS_ANDROID || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21
|
|
|
|
void ExpectModulesFromSelf(
|
|
const std::vector<ProcessReaderLinux::Module>& modules) {
|
|
for (const auto& module : modules) {
|
|
EXPECT_FALSE(module.name.empty());
|
|
EXPECT_NE(module.type, ModuleSnapshot::kModuleTypeUnknown);
|
|
}
|
|
|
|
// Android doesn't provide dl_iterate_phdr on ARM until API 21.
|
|
#if !defined(OS_ANDROID) || !defined(ARCH_CPU_ARMEL) || __ANDROID_API__ >= 21
|
|
EXPECT_EQ(
|
|
dl_iterate_phdr(
|
|
ExpectFindModule,
|
|
reinterpret_cast<void*>(
|
|
const_cast<std::vector<ProcessReaderLinux::Module>*>(&modules))),
|
|
0);
|
|
#endif // !OS_ANDROID || !ARCH_CPU_ARMEL || __ANDROID_API__ >= 21
|
|
}
|
|
|
|
bool WriteTestModule(const base::FilePath& module_path,
|
|
const std::string& soname) {
|
|
#if defined(ARCH_CPU_64_BITS)
|
|
using Ehdr = Elf64_Ehdr;
|
|
using Phdr = Elf64_Phdr;
|
|
using Shdr = Elf64_Shdr;
|
|
using Dyn = Elf64_Dyn;
|
|
using Sym = Elf64_Sym;
|
|
unsigned char elf_class = ELFCLASS64;
|
|
#else
|
|
using Ehdr = Elf32_Ehdr;
|
|
using Phdr = Elf32_Phdr;
|
|
using Shdr = Elf32_Shdr;
|
|
using Dyn = Elf32_Dyn;
|
|
using Sym = Elf32_Sym;
|
|
unsigned char elf_class = ELFCLASS32;
|
|
#endif
|
|
|
|
struct {
|
|
Ehdr ehdr;
|
|
struct {
|
|
Phdr load1;
|
|
Phdr load2;
|
|
Phdr dynamic;
|
|
} phdr_table;
|
|
struct {
|
|
Dyn hash;
|
|
Dyn strtab;
|
|
Dyn symtab;
|
|
Dyn strsz;
|
|
Dyn syment;
|
|
Dyn soname;
|
|
Dyn null;
|
|
} dynamic_array;
|
|
struct {
|
|
Elf32_Word nbucket;
|
|
Elf32_Word nchain;
|
|
Elf32_Word bucket;
|
|
Elf32_Word chain;
|
|
} hash_table;
|
|
char string_table[32];
|
|
struct {
|
|
} section_header_string_table;
|
|
struct {
|
|
Sym und_symbol;
|
|
} symbol_table;
|
|
struct {
|
|
Shdr null;
|
|
Shdr dynamic;
|
|
Shdr string_table;
|
|
Shdr section_header_string_table;
|
|
} shdr_table;
|
|
} module = {};
|
|
|
|
module.ehdr.e_ident[EI_MAG0] = ELFMAG0;
|
|
module.ehdr.e_ident[EI_MAG1] = ELFMAG1;
|
|
module.ehdr.e_ident[EI_MAG2] = ELFMAG2;
|
|
module.ehdr.e_ident[EI_MAG3] = ELFMAG3;
|
|
|
|
module.ehdr.e_ident[EI_CLASS] = elf_class;
|
|
|
|
#if defined(ARCH_CPU_LITTLE_ENDIAN)
|
|
module.ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
|
|
#else
|
|
module.ehdr.e_ident[EI_DATA] = ELFDATA2MSB;
|
|
#endif // ARCH_CPU_LITTLE_ENDIAN
|
|
|
|
module.ehdr.e_ident[EI_VERSION] = EV_CURRENT;
|
|
|
|
module.ehdr.e_type = ET_DYN;
|
|
|
|
#if defined(ARCH_CPU_X86)
|
|
module.ehdr.e_machine = EM_386;
|
|
#elif defined(ARCH_CPU_X86_64)
|
|
module.ehdr.e_machine = EM_X86_64;
|
|
#elif defined(ARCH_CPU_ARMEL)
|
|
module.ehdr.e_machine = EM_ARM;
|
|
#elif defined(ARCH_CPU_ARM64)
|
|
module.ehdr.e_machine = EM_AARCH64;
|
|
#elif defined(ARCH_CPU_MIPSEL) || defined(ARCH_CPU_MIPS64EL)
|
|
module.ehdr.e_machine = EM_MIPS;
|
|
#endif
|
|
|
|
module.ehdr.e_version = EV_CURRENT;
|
|
module.ehdr.e_ehsize = sizeof(module.ehdr);
|
|
|
|
module.ehdr.e_phoff = offsetof(decltype(module), phdr_table);
|
|
module.ehdr.e_phnum = sizeof(module.phdr_table) / sizeof(Phdr);
|
|
module.ehdr.e_phentsize = sizeof(Phdr);
|
|
|
|
module.ehdr.e_shoff = offsetof(decltype(module), shdr_table);
|
|
module.ehdr.e_shentsize = sizeof(Shdr);
|
|
module.ehdr.e_shnum = sizeof(module.shdr_table) / sizeof(Shdr);
|
|
module.ehdr.e_shstrndx =
|
|
offsetof(decltype(module.shdr_table), section_header_string_table) /
|
|
sizeof(Shdr);
|
|
|
|
constexpr size_t load2_vaddr = 0x200000;
|
|
|
|
module.phdr_table.load1.p_type = PT_LOAD;
|
|
module.phdr_table.load1.p_offset = 0;
|
|
module.phdr_table.load1.p_vaddr = 0;
|
|
module.phdr_table.load1.p_filesz = sizeof(module);
|
|
module.phdr_table.load1.p_memsz = sizeof(module);
|
|
module.phdr_table.load1.p_flags = PF_R;
|
|
module.phdr_table.load1.p_align = load2_vaddr;
|
|
|
|
module.phdr_table.load2.p_type = PT_LOAD;
|
|
module.phdr_table.load2.p_offset = 0;
|
|
module.phdr_table.load2.p_vaddr = load2_vaddr;
|
|
module.phdr_table.load2.p_filesz = sizeof(module);
|
|
module.phdr_table.load2.p_memsz = sizeof(module);
|
|
module.phdr_table.load2.p_flags = PF_R | PF_W;
|
|
module.phdr_table.load2.p_align = load2_vaddr;
|
|
|
|
module.phdr_table.dynamic.p_type = PT_DYNAMIC;
|
|
module.phdr_table.dynamic.p_offset =
|
|
offsetof(decltype(module), dynamic_array);
|
|
module.phdr_table.dynamic.p_vaddr =
|
|
load2_vaddr + module.phdr_table.dynamic.p_offset;
|
|
module.phdr_table.dynamic.p_filesz = sizeof(module.dynamic_array);
|
|
module.phdr_table.dynamic.p_memsz = sizeof(module.dynamic_array);
|
|
module.phdr_table.dynamic.p_flags = PF_R | PF_W;
|
|
module.phdr_table.dynamic.p_align = 8;
|
|
|
|
module.dynamic_array.hash.d_tag = DT_HASH;
|
|
module.dynamic_array.hash.d_un.d_ptr = offsetof(decltype(module), hash_table);
|
|
module.dynamic_array.strtab.d_tag = DT_STRTAB;
|
|
module.dynamic_array.strtab.d_un.d_ptr =
|
|
offsetof(decltype(module), string_table);
|
|
module.dynamic_array.symtab.d_tag = DT_SYMTAB;
|
|
module.dynamic_array.symtab.d_un.d_ptr =
|
|
offsetof(decltype(module), symbol_table);
|
|
module.dynamic_array.strsz.d_tag = DT_STRSZ;
|
|
module.dynamic_array.strsz.d_un.d_val = sizeof(module.string_table);
|
|
module.dynamic_array.syment.d_tag = DT_SYMENT;
|
|
module.dynamic_array.syment.d_un.d_val = sizeof(Sym);
|
|
constexpr size_t kSonameOffset = 1;
|
|
module.dynamic_array.soname.d_tag = DT_SONAME;
|
|
module.dynamic_array.soname.d_un.d_val = kSonameOffset;
|
|
|
|
module.dynamic_array.null.d_tag = DT_NULL;
|
|
|
|
module.hash_table.nbucket = 1;
|
|
module.hash_table.nchain = 1;
|
|
module.hash_table.bucket = 0;
|
|
module.hash_table.chain = 0;
|
|
|
|
CHECK_GE(sizeof(module.string_table), soname.size() + 2);
|
|
module.string_table[0] = '\0';
|
|
memcpy(&module.string_table[kSonameOffset], soname.c_str(), soname.size());
|
|
|
|
module.shdr_table.null.sh_type = SHT_NULL;
|
|
|
|
module.shdr_table.dynamic.sh_name = 0;
|
|
module.shdr_table.dynamic.sh_type = SHT_DYNAMIC;
|
|
module.shdr_table.dynamic.sh_flags = SHF_WRITE | SHF_ALLOC;
|
|
module.shdr_table.dynamic.sh_addr = module.phdr_table.dynamic.p_vaddr;
|
|
module.shdr_table.dynamic.sh_offset = module.phdr_table.dynamic.p_offset;
|
|
module.shdr_table.dynamic.sh_size = module.phdr_table.dynamic.p_filesz;
|
|
module.shdr_table.dynamic.sh_link =
|
|
offsetof(decltype(module.shdr_table), string_table) / sizeof(Shdr);
|
|
|
|
module.shdr_table.string_table.sh_name = 0;
|
|
module.shdr_table.string_table.sh_type = SHT_STRTAB;
|
|
module.shdr_table.string_table.sh_offset =
|
|
offsetof(decltype(module), string_table);
|
|
module.shdr_table.string_table.sh_size = sizeof(module.string_table);
|
|
|
|
module.shdr_table.section_header_string_table.sh_name = 0;
|
|
module.shdr_table.section_header_string_table.sh_type = SHT_STRTAB;
|
|
module.shdr_table.section_header_string_table.sh_offset =
|
|
offsetof(decltype(module), section_header_string_table);
|
|
module.shdr_table.section_header_string_table.sh_size =
|
|
sizeof(module.section_header_string_table);
|
|
|
|
FileWriter writer;
|
|
if (!writer.Open(module_path,
|
|
FileWriteMode::kCreateOrFail,
|
|
FilePermissions::kWorldReadable)) {
|
|
ADD_FAILURE();
|
|
return false;
|
|
}
|
|
|
|
if (!writer.Write(&module, sizeof(module))) {
|
|
ADD_FAILURE();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ScopedModuleHandle LoadTestModule(const std::string& module_name,
|
|
const std::string& module_soname) {
|
|
base::FilePath module_path(
|
|
TestPaths::Executable().DirName().Append(module_name));
|
|
|
|
if (!WriteTestModule(module_path, module_soname)) {
|
|
return ScopedModuleHandle(nullptr);
|
|
}
|
|
EXPECT_TRUE(IsRegularFile(module_path));
|
|
|
|
ScopedModuleHandle handle(
|
|
dlopen(module_path.value().c_str(), RTLD_LAZY | RTLD_LOCAL));
|
|
EXPECT_TRUE(handle.valid())
|
|
<< "dlopen: " << module_path.value() << " " << dlerror();
|
|
|
|
EXPECT_TRUE(LoggingRemoveFile(module_path));
|
|
|
|
return handle;
|
|
}
|
|
|
|
void ExpectTestModule(ProcessReaderLinux* reader,
|
|
const std::string& module_name) {
|
|
for (const auto& module : reader->Modules()) {
|
|
if (module.name.find(module_name) != std::string::npos) {
|
|
ASSERT_TRUE(module.elf_reader);
|
|
|
|
VMAddress dynamic_addr;
|
|
ASSERT_TRUE(module.elf_reader->GetDynamicArrayAddress(&dynamic_addr));
|
|
|
|
auto dynamic_mapping = reader->GetMemoryMap()->FindMapping(dynamic_addr);
|
|
auto mappings =
|
|
reader->GetMemoryMap()->FindFilePossibleMmapStarts(*dynamic_mapping);
|
|
EXPECT_EQ(mappings->Count(), 2u);
|
|
return;
|
|
}
|
|
}
|
|
ADD_FAILURE() << "Test module not found";
|
|
}
|
|
|
|
TEST(ProcessReaderLinux, SelfModules) {
|
|
const std::string module_name = "test_module.so";
|
|
const std::string module_soname = "test_module_soname";
|
|
ScopedModuleHandle empty_test_module(
|
|
LoadTestModule(module_name, module_soname));
|
|
ASSERT_TRUE(empty_test_module.valid());
|
|
|
|
FakePtraceConnection connection;
|
|
connection.Initialize(getpid());
|
|
|
|
ProcessReaderLinux process_reader;
|
|
ASSERT_TRUE(process_reader.Initialize(&connection));
|
|
|
|
ExpectModulesFromSelf(process_reader.Modules());
|
|
ExpectTestModule(&process_reader, module_soname);
|
|
}
|
|
|
|
class ChildModuleTest : public Multiprocess {
|
|
public:
|
|
ChildModuleTest() : Multiprocess(), module_soname_("test_module_soname") {}
|
|
~ChildModuleTest() = default;
|
|
|
|
private:
|
|
void MultiprocessParent() override {
|
|
char c;
|
|
ASSERT_TRUE(LoggingReadFileExactly(ReadPipeHandle(), &c, sizeof(c)));
|
|
|
|
DirectPtraceConnection connection;
|
|
ASSERT_TRUE(connection.Initialize(ChildPID()));
|
|
|
|
ProcessReaderLinux process_reader;
|
|
ASSERT_TRUE(process_reader.Initialize(&connection));
|
|
|
|
ExpectModulesFromSelf(process_reader.Modules());
|
|
ExpectTestModule(&process_reader, module_soname_);
|
|
}
|
|
|
|
void MultiprocessChild() override {
|
|
ScopedModuleHandle empty_test_module(
|
|
LoadTestModule("test_module.so", module_soname_));
|
|
ASSERT_TRUE(empty_test_module.valid());
|
|
|
|
char c = 0;
|
|
ASSERT_TRUE(LoggingWriteFile(WritePipeHandle(), &c, sizeof(c)));
|
|
|
|
CheckedReadFileAtEOF(ReadPipeHandle());
|
|
}
|
|
|
|
const std::string module_soname_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ChildModuleTest);
|
|
};
|
|
|
|
TEST(ProcessReaderLinux, ChildModules) {
|
|
ChildModuleTest test;
|
|
test.Run();
|
|
}
|
|
|
|
#if defined(OS_ANDROID)
|
|
const char kTestAbortMessage[] = "test abort message";
|
|
|
|
TEST(ProcessReaderLinux, AbortMessage) {
|
|
// This test requires Q. The API level on Q devices will be 28 until the API
|
|
// is finalized, so we can't check API level yet. For now, test for the
|
|
// presence of a libc symbol which was introduced in Q.
|
|
if (!crashpad::internal::Dlsym(RTLD_DEFAULT,
|
|
"android_fdsan_close_with_tag")) {
|
|
GTEST_SKIP();
|
|
}
|
|
|
|
android_set_abort_message(kTestAbortMessage);
|
|
|
|
FakePtraceConnection connection;
|
|
connection.Initialize(getpid());
|
|
|
|
ProcessReaderLinux process_reader;
|
|
ASSERT_TRUE(process_reader.Initialize(&connection));
|
|
|
|
EXPECT_EQ(kTestAbortMessage, process_reader.AbortMessage());
|
|
}
|
|
#endif
|
|
|
|
} // namespace
|
|
} // namespace test
|
|
} // namespace crashpad
|