From 0e91d185cac51ed1b2c9163afca998660ebda08e Mon Sep 17 00:00:00 2001 From: "ted.mielczarek@gmail.com" Date: Wed, 19 Sep 2012 12:55:16 +0000 Subject: Minidumps never contain MD_LINUX_DSO_DEBUG info when breakpad is in a shared library A=Mike Hommey R=ted at http://breakpad.appspot.com/422002/ git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1044 4c0a9323-5329-0410-9bdc-e9ce6186880e --- src/client/linux/minidump_writer/linux_dumper.cc | 68 +++++--------- src/client/linux/minidump_writer/linux_dumper.h | 18 ++-- .../linux_ptrace_dumper_unittest.cc | 3 +- .../linux/minidump_writer/minidump_writer.cc | 100 ++++++++++++--------- .../minidump_writer/minidump_writer_unittest.cc | 18 +++- 5 files changed, 107 insertions(+), 100 deletions(-) (limited to 'src') diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc index 6e2bbc8b..2124224e 100644 --- a/src/client/linux/minidump_writer/linux_dumper.cc +++ b/src/client/linux/minidump_writer/linux_dumper.cc @@ -66,20 +66,24 @@ inline static bool IsMappedFileOpenUnsafe( namespace google_breakpad { +// All interesting auvx entry types are below AT_SYSINFO_EHDR +#define AT_MAX AT_SYSINFO_EHDR + LinuxDumper::LinuxDumper(pid_t pid) : pid_(pid), crash_address_(0), crash_signal_(0), crash_thread_(0), threads_(&allocator_, 8), - mappings_(&allocator_) { + mappings_(&allocator_), + auxv_(&allocator_, AT_MAX + 1) { } LinuxDumper::~LinuxDumper() { } bool LinuxDumper::Init() { - return EnumerateThreads() && EnumerateMappings(); + return ReadAuxv() && EnumerateThreads() && EnumerateMappings(); } bool @@ -131,58 +135,30 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, return success; } -void* -LinuxDumper::FindBeginningOfLinuxGateSharedLibrary(pid_t pid) const { +bool LinuxDumper::ReadAuxv() { char auxv_path[NAME_MAX]; - if (!BuildProcPath(auxv_path, pid, "auxv")) - return NULL; - - // Find the AT_SYSINFO_EHDR entry for linux-gate.so - // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more - // information. - int fd = sys_open(auxv_path, O_RDONLY, 0); - if (fd < 0) { - return NULL; - } - - elf_aux_entry one_aux_entry; - while (sys_read(fd, - &one_aux_entry, - sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) && - one_aux_entry.a_type != AT_NULL) { - if (one_aux_entry.a_type == AT_SYSINFO_EHDR) { - sys_close(fd); - return reinterpret_cast(one_aux_entry.a_un.a_val); - } + if (!BuildProcPath(auxv_path, pid_, "auxv")) { + return false; } - sys_close(fd); - return NULL; -} - -void* -LinuxDumper::FindEntryPoint(pid_t pid) const { - char auxv_path[NAME_MAX]; - if (!BuildProcPath(auxv_path, pid, "auxv")) - return NULL; int fd = sys_open(auxv_path, O_RDONLY, 0); if (fd < 0) { - return NULL; + return false; } - // Find the AT_ENTRY entry elf_aux_entry one_aux_entry; + bool res = false; while (sys_read(fd, &one_aux_entry, sizeof(elf_aux_entry)) == sizeof(elf_aux_entry) && one_aux_entry.a_type != AT_NULL) { - if (one_aux_entry.a_type == AT_ENTRY) { - sys_close(fd); - return reinterpret_cast(one_aux_entry.a_un.a_val); + if (one_aux_entry.a_type <= AT_MAX) { + auxv_[one_aux_entry.a_type] = one_aux_entry.a_un.a_val; + res = true; } } sys_close(fd); - return NULL; + return res; } bool LinuxDumper::EnumerateMappings() { @@ -192,15 +168,17 @@ bool LinuxDumper::EnumerateMappings() { // linux_gate_loc is the beginning of the kernel's mapping of // linux-gate.so in the process. It doesn't actually show up in the - // maps list as a filename, so we use the aux vector to find it's - // load location and special case it's entry when creating the list - // of mappings. - const void* linux_gate_loc; - linux_gate_loc = FindBeginningOfLinuxGateSharedLibrary(pid_); + // maps list as a filename, but it can be found using the AT_SYSINFO_EHDR + // aux vector entry, which gives the information necessary to special + // case its entry when creating the list of mappings. + // See http://www.trilithium.com/johan/2005/08/linux-gate/ for more + // information. + const void* linux_gate_loc = + reinterpret_cast(auxv_[AT_SYSINFO_EHDR]); // Although the initial executable is usually the first mapping, it's not // guaranteed (see http://crosbug.com/25355); therefore, try to use the // actual entry point to find the mapping. - const void* entry_point_loc = FindEntryPoint(pid_); + const void* entry_point_loc = reinterpret_cast(auxv_[AT_ENTRY]); const int fd = sys_open(maps_path, O_RDONLY, 0); if (fd < 0) diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h index 0fca9937..2ed3e14d 100644 --- a/src/client/linux/minidump_writer/linux_dumper.h +++ b/src/client/linux/minidump_writer/linux_dumper.h @@ -59,6 +59,9 @@ typedef Elf32_auxv_t elf_aux_entry; #elif defined(__x86_64) typedef Elf64_auxv_t elf_aux_entry; #endif + +typedef typeof(((elf_aux_entry*) 0)->a_un.a_val) elf_aux_val_t; + // When we find the VDSO mapping in the process's address space, this // is the name we use for it when writing it to the minidump. // This should always be less than NAME_MAX! @@ -124,6 +127,7 @@ class LinuxDumper { const wasteful_vector &threads() { return threads_; } const wasteful_vector &mappings() { return mappings_; } const MappingInfo* FindMapping(const void* address) const; + const wasteful_vector& auxv() { return auxv_; } // Find a block of memory to take as the stack given the top of stack pointer. // stack: (output) the lowest address in the memory area @@ -151,15 +155,6 @@ class LinuxDumper { unsigned int mapping_id, uint8_t identifier[sizeof(MDGUID)]); - // Utility method to find the location of where the kernel has - // mapped linux-gate.so in memory(shows up in /proc/pid/maps as - // [vdso], but we can't guarantee that it's the only virtual dynamic - // shared object. Parsing the auxilary vector for AT_SYSINFO_EHDR - // is the safest way to go.) - void* FindBeginningOfLinuxGateSharedLibrary(pid_t pid) const; - // Utility method to find the entry point location. - void* FindEntryPoint(pid_t pid) const; - uintptr_t crash_address() const { return crash_address_; } void set_crash_address(uintptr_t crash_address) { crash_address_ = crash_address; @@ -172,6 +167,8 @@ class LinuxDumper { void set_crash_thread(pid_t crash_thread) { crash_thread_ = crash_thread; } protected: + bool ReadAuxv(); + virtual bool EnumerateMappings(); virtual bool EnumerateThreads() = 0; @@ -206,6 +203,9 @@ class LinuxDumper { // Info from /proc//maps. wasteful_vector mappings_; + + // Info from /proc//auxv + wasteful_vector auxv_; }; } // namespace google_breakpad diff --git a/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc index 61f21b86..ba3548c5 100644 --- a/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc +++ b/src/client/linux/minidump_writer/linux_ptrace_dumper_unittest.cc @@ -287,7 +287,8 @@ TEST(LinuxPtraceDumperTest, MappingsIncludeLinuxGate) { LinuxPtraceDumper dumper(getpid()); ASSERT_TRUE(dumper.Init()); - void* linux_gate_loc = dumper.FindBeginningOfLinuxGateSharedLibrary(getpid()); + void* linux_gate_loc = + reinterpret_cast(dumper.auxv()[AT_SYSINFO_EHDR]); ASSERT_TRUE(linux_gate_loc); bool found_linux_gate = false; diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc index 02055640..add2d3f5 100644 --- a/src/client/linux/minidump_writer/minidump_writer.cc +++ b/src/client/linux/minidump_writer/minidump_writer.cc @@ -423,40 +423,9 @@ class MinidumpWriter { } bool Dump() { - // The dynamic linker makes information available that helps gdb find all - // DSOs loaded into the program. If we can access this information, we dump - // it to a MD_LINUX_DSO_DEBUG stream. - struct r_debug* r_debug = NULL; - uint32_t dynamic_length = 0; -#if !defined(__ANDROID__) - // This code assumes the crashing process is the same as this process and - // may hang or take a long time to complete if not so. - // Thus, we skip this code for a post-mortem based dump. - if (!dumper_->IsPostMortem()) { - // The Android NDK is missing structure definitions for most of this. - // For now, it's simpler just to skip it. - for (int i = 0;;) { - ElfW(Dyn) dyn; - dynamic_length += sizeof(dyn); - // NOTE: Use of _DYNAMIC assumes this is the same process as the - // crashing process. This loop will go forever if it's out of bounds. - dumper_->CopyFromProcess(&dyn, GetCrashThread(), _DYNAMIC+i++, - sizeof(dyn)); - if (dyn.d_tag == DT_DEBUG) { - r_debug = (struct r_debug*)dyn.d_un.d_ptr; - continue; - } else if (dyn.d_tag == DT_NULL) { - break; - } - } - } -#endif - // A minidump file contains a number of tagged streams. This is the number // of stream which we write. - unsigned kNumWriters = 12; - if (r_debug) - ++kNumWriters; + unsigned kNumWriters = 13; TypedMDRVA header(&minidump_writer_); TypedMDRVA dir(&minidump_writer_); @@ -533,12 +502,10 @@ class MinidumpWriter { NullifyDirectoryEntry(&dirent); dir.CopyIndex(dir_index++, &dirent); - if (r_debug) { - dirent.stream_type = MD_LINUX_DSO_DEBUG; - if (!WriteDSODebugStream(&dirent, r_debug, dynamic_length)) - NullifyDirectoryEntry(&dirent); - dir.CopyIndex(dir_index++, &dirent); - } + dirent.stream_type = MD_LINUX_DSO_DEBUG; + if (!WriteDSODebugStream(&dirent)) + NullifyDirectoryEntry(&dirent); + dir.CopyIndex(dir_index++, &dirent); // If you add more directory entries, don't forget to update kNumWriters, // above. @@ -1000,13 +967,58 @@ class MinidumpWriter { return true; } - bool WriteDSODebugStream(MDRawDirectory* dirent, struct r_debug* r_debug, - uint32_t dynamic_length) { + bool WriteDSODebugStream(MDRawDirectory* dirent) { #if defined(__ANDROID__) return false; #else - // The caller provided us with a pointer to "struct r_debug". We can - // look up the "r_map" field to get a linked list of all loaded DSOs. + ElfW(Phdr)* phdr = reinterpret_cast(dumper_->auxv()[AT_PHDR]); + char* base; + int phnum = dumper_->auxv()[AT_PHNUM]; + if (!phnum || !phdr) + return false; + + // Assume the program base is at the beginning of the same page as the PHDR + base = reinterpret_cast(reinterpret_cast(phdr) & ~0xfff); + + // Search for the program PT_DYNAMIC segment + ElfW(Addr) dyn_addr = 0; + for (; phnum >= 0; phnum--, phdr++) { + ElfW(Phdr) ph; + dumper_->CopyFromProcess(&ph, GetCrashThread(), phdr, sizeof(ph)); + // Adjust base address with the virtual address of the PT_LOAD segment + // corresponding to offset 0 + if (ph.p_type == PT_LOAD && ph.p_offset == 0) { + base -= ph.p_vaddr; + } + if (ph.p_type == PT_DYNAMIC) { + dyn_addr = ph.p_vaddr; + } + } + if (!dyn_addr) + return false; + + ElfW(Dyn) *dynamic = reinterpret_cast(dyn_addr + base); + + // The dynamic linker makes information available that helps gdb find all + // DSOs loaded into the program. If this information is indeed available, + // dump it to a MD_LINUX_DSO_DEBUG stream. + struct r_debug* r_debug = NULL; + uint32_t dynamic_length = 0; + + for (int i = 0;;) { + ElfW(Dyn) dyn; + dynamic_length += sizeof(dyn); + dumper_->CopyFromProcess(&dyn, GetCrashThread(), dynamic+i++, sizeof(dyn)); + if (dyn.d_tag == DT_DEBUG) { + r_debug = reinterpret_cast(dyn.d_un.d_ptr); + continue; + } else if (dyn.d_tag == DT_NULL) { + break; + } + } + + // The "r_map" field of that r_debug struct contains a linked list of all + // loaded DSOs. // Our list of DSOs potentially is different from the ones in the crashing // process. So, we have to be careful to never dereference pointers // directly. Instead, we use CopyFromProcess() everywhere. @@ -1069,10 +1081,10 @@ class MinidumpWriter { debug.get()->dso_count = dso_count; debug.get()->brk = (void*)debug_entry.r_brk; debug.get()->ldbase = (void*)debug_entry.r_ldbase; - debug.get()->dynamic = (void*)&_DYNAMIC; + debug.get()->dynamic = dynamic; char *dso_debug_data = new char[dynamic_length]; - dumper_->CopyFromProcess(dso_debug_data, GetCrashThread(), &_DYNAMIC, + dumper_->CopyFromProcess(dso_debug_data, GetCrashThread(), dynamic, dynamic_length); debug.CopyIndexAfterObject(0, dso_debug_data, dynamic_length); delete[] dso_debug_data; diff --git a/src/client/linux/minidump_writer/minidump_writer_unittest.cc b/src/client/linux/minidump_writer/minidump_writer_unittest.cc index 0a6b07d1..e0d0fa9c 100644 --- a/src/client/linux/minidump_writer/minidump_writer_unittest.cc +++ b/src/client/linux/minidump_writer/minidump_writer_unittest.cc @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -172,7 +173,8 @@ TEST(MinidumpWriterTest, MappingInfo) { ExceptionHandler::CrashContext context; memset(&context, 0, sizeof(context)); - context.tid = 1; + ASSERT_EQ(0, getcontext(&context.context)); + context.tid = child; AutoTempDir temp_dir; string templ = temp_dir.path() + "/minidump-writer-unittest"; @@ -210,6 +212,20 @@ TEST(MinidumpWriterTest, MappingInfo) { EXPECT_EQ(kMemoryName, module->code_file()); EXPECT_EQ(module_identifier, module->debug_identifier()); + u_int32_t len; + // These streams are expected to be there + EXPECT_TRUE(minidump.SeekToStreamType(MD_THREAD_LIST_STREAM, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_MEMORY_LIST_STREAM, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_EXCEPTION_STREAM, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_SYSTEM_INFO_STREAM, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CPU_INFO, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_PROC_STATUS, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_CMD_LINE, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_ENVIRON, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_AUXV, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_MAPS, &len)); + EXPECT_TRUE(minidump.SeekToStreamType(MD_LINUX_DSO_DEBUG, &len)); + close(fds[1]); } -- cgit v1.2.1