mirror of
https://git.suyu.dev/suyu/breakpad.git
synced 2025-12-28 10:15:10 +01:00
Minidumps never contain MD_LINUX_DSO_DEBUG info when breakpad is in a shared library
A=Mike Hommey <mh@glandium.org> R=ted at http://breakpad.appspot.com/422002/ git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1044 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
67364c1326
commit
0e91d185ca
10 changed files with 668 additions and 572 deletions
|
|
@ -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;
|
||||
if (!BuildProcPath(auxv_path, pid_, "auxv")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return false;
|
||||
}
|
||||
|
||||
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_SYSINFO_EHDR) {
|
||||
sys_close(fd);
|
||||
return reinterpret_cast<void*>(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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Find the AT_ENTRY entry
|
||||
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_ENTRY) {
|
||||
sys_close(fd);
|
||||
return reinterpret_cast<void*>(one_aux_entry.a_un.a_val);
|
||||
}
|
||||
}
|
||||
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<void *>(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<void *>(auxv_[AT_ENTRY]);
|
||||
|
||||
const int fd = sys_open(maps_path, O_RDONLY, 0);
|
||||
if (fd < 0)
|
||||
|
|
|
|||
|
|
@ -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<pid_t> &threads() { return threads_; }
|
||||
const wasteful_vector<MappingInfo*> &mappings() { return mappings_; }
|
||||
const MappingInfo* FindMapping(const void* address) const;
|
||||
const wasteful_vector<elf_aux_val_t>& 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/<pid>/maps.
|
||||
wasteful_vector<MappingInfo*> mappings_;
|
||||
|
||||
// Info from /proc/<pid>/auxv
|
||||
wasteful_vector<elf_aux_val_t> auxv_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
|
|
|||
|
|
@ -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<void *>(dumper.auxv()[AT_SYSINFO_EHDR]);
|
||||
ASSERT_TRUE(linux_gate_loc);
|
||||
bool found_linux_gate = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<MDRawHeader> header(&minidump_writer_);
|
||||
TypedMDRVA<MDRawDirectory> 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<ElfW(Phdr) *>(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<char *>(reinterpret_cast<uintptr_t>(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<ElfW(Dyn) *>(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<struct r_debug*>(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;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include <sys/types.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
#include <ucontext.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
|
|
@ -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]);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue