From 561f81873562d407ac1c39144b30e26163e0045d Mon Sep 17 00:00:00 2001 From: "rmcilroy@chromium.org" Date: Tue, 22 Jul 2014 11:34:11 +0000 Subject: Chrome on Android now supports loading the shared library directly from the APK file. This patch makes two changes to breakpad to enable crash reporting to work correctly when the library is inside another file (an archive): - Do not filter mappings which map an executable at a non-zero offset. - If such an executable is mapped look in the ELF information for the shared object name and use that name in the minidump. Note this change doesn't care about the archive format and isn't Android specific (though loading the shared library this way is currently only done on Android). BUG=390618 R=thestig@chromium.org Review URL: https://breakpad.appspot.com/7684002 Patch from Anton Carver . git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@1355 4c0a9323-5329-0410-9bdc-e9ce6186880e --- .../linux/minidump_writer/linux_core_dumper.cc | 2 +- src/client/linux/minidump_writer/linux_dumper.cc | 87 +++++++++++++++++++++- src/client/linux/minidump_writer/linux_dumper.h | 8 ++ .../linux/minidump_writer/minidump_writer.cc | 35 ++++++++- 4 files changed, 123 insertions(+), 9 deletions(-) (limited to 'src/client/linux/minidump_writer') diff --git a/src/client/linux/minidump_writer/linux_core_dumper.cc b/src/client/linux/minidump_writer/linux_core_dumper.cc index aa6aa70f..3eab44db 100644 --- a/src/client/linux/minidump_writer/linux_core_dumper.cc +++ b/src/client/linux/minidump_writer/linux_core_dumper.cc @@ -124,7 +124,7 @@ bool LinuxCoreDumper::ThreadsResume() { } bool LinuxCoreDumper::EnumerateThreads() { - if (!mapped_core_file_.Map(core_path_)) { + if (!mapped_core_file_.Map(core_path_, 0)) { fprintf(stderr, "Could not map core dump file into memory\n"); return false; } diff --git a/src/client/linux/minidump_writer/linux_dumper.cc b/src/client/linux/minidump_writer/linux_dumper.cc index e09da916..c1e77c96 100644 --- a/src/client/linux/minidump_writer/linux_dumper.cc +++ b/src/client/linux/minidump_writer/linux_dumper.cc @@ -38,12 +38,14 @@ #include "client/linux/minidump_writer/linux_dumper.h" #include +#include #include #include #include #include #include "client/linux/minidump_writer/line_reader.h" +#include "common/linux/elfutils.h" #include "common/linux/file_id.h" #include "common/linux/linux_libc_support.h" #include "common/linux/memory_mapped_file.h" @@ -115,15 +117,16 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, char filename[NAME_MAX]; size_t filename_len = my_strlen(mapping.name); - assert(filename_len < NAME_MAX); - if (filename_len >= NAME_MAX) + if (filename_len >= NAME_MAX) { + assert(false); return false; + } my_memcpy(filename, mapping.name, filename_len); filename[filename_len] = '\0'; bool filename_modified = HandleDeletedFileInMapping(filename); - MemoryMappedFile mapped_file(filename); - if (!mapped_file.data()) // Should probably check if size >= ElfW(Ehdr)? + MemoryMappedFile mapped_file(filename, mapping.offset); + if (!mapped_file.data() || mapped_file.size() < SELFMAG) return false; bool success = @@ -136,6 +139,80 @@ LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, return success; } +namespace { +bool ElfFileSoNameFromMappedFile( + const void* elf_base, char* soname, size_t soname_size) { + if (!IsValidElf(elf_base)) { + // Not ELF + return false; + } + + const void* segment_start; + size_t segment_size; + int elf_class; + if (!FindElfSection(elf_base, ".dynamic", SHT_DYNAMIC, + &segment_start, &segment_size, &elf_class)) { + // No dynamic section + return false; + } + + const void* dynstr_start; + size_t dynstr_size; + if (!FindElfSection(elf_base, ".dynstr", SHT_STRTAB, + &dynstr_start, &dynstr_size, &elf_class)) { + // No dynstr section + return false; + } + + const ElfW(Dyn)* dynamic = static_cast(segment_start); + size_t dcount = segment_size / sizeof(ElfW(Dyn)); + for (const ElfW(Dyn)* dyn = dynamic; dyn < dynamic + dcount; ++dyn) { + if (dyn->d_tag == DT_SONAME) { + const char* dynstr = static_cast(dynstr_start); + if (dyn->d_un.d_val >= dynstr_size) { + // Beyond the end of the dynstr section + return false; + } + const char* str = dynstr + dyn->d_un.d_val; + const size_t maxsize = dynstr_size - dyn->d_un.d_val; + my_strlcpy(soname, str, maxsize < soname_size ? maxsize : soname_size); + return true; + } + } + + // Did not find SONAME + return false; +} +} // namespace + +// static +bool LinuxDumper::ElfFileSoName( + const MappingInfo& mapping, char* soname, size_t soname_size) { + if (IsMappedFileOpenUnsafe(mapping)) { + // Not safe + return false; + } + + char filename[NAME_MAX]; + size_t filename_len = my_strlen(mapping.name); + if (filename_len >= NAME_MAX) { + assert(false); + // name too long + return false; + } + + my_memcpy(filename, mapping.name, filename_len); + filename[filename_len] = '\0'; + + MemoryMappedFile mapped_file(filename, mapping.offset); + if (!mapped_file.data() || mapped_file.size() < SELFMAG) { + // mmap failed + return false; + } + + return ElfFileSoNameFromMappedFile(mapped_file.data(), soname, soname_size); +} + bool LinuxDumper::ReadAuxv() { char auxv_path[NAME_MAX]; if (!BuildProcPath(auxv_path, pid_, "auxv")) { @@ -195,6 +272,7 @@ bool LinuxDumper::EnumerateMappings() { if (*i1 == '-') { const char* i2 = my_read_hex_ptr(&end_addr, i1 + 1); if (*i2 == ' ') { + bool exec = (*(i2 + 3) == 'x'); const char* i3 = my_read_hex_ptr(&offset, i2 + 6 /* skip ' rwxp ' */); if (*i3 == ' ') { const char* name = NULL; @@ -223,6 +301,7 @@ bool LinuxDumper::EnumerateMappings() { module->start_addr = start_addr; module->size = end_addr - start_addr; module->offset = offset; + module->exec = exec; if (name != NULL) { const unsigned l = my_strlen(name); if (l < sizeof(module->name)) diff --git a/src/client/linux/minidump_writer/linux_dumper.h b/src/client/linux/minidump_writer/linux_dumper.h index 335a2ce9..ad2af018 100644 --- a/src/client/linux/minidump_writer/linux_dumper.h +++ b/src/client/linux/minidump_writer/linux_dumper.h @@ -107,6 +107,7 @@ struct MappingInfo { uintptr_t start_addr; size_t size; size_t offset; // offset into the backed file. + bool exec; // true if the mapping has the execute bit set. char name[NAME_MAX]; }; @@ -162,6 +163,13 @@ class LinuxDumper { unsigned int mapping_id, uint8_t identifier[sizeof(MDGUID)]); + // Find the shared object name (SONAME) by examining the ELF information + // for |mapping|. If the SONAME is found copy it into the passed buffer + // |soname| and return true. The size of the buffer is |soname_size|. + // The SONAME will be truncated if it is too long to fit in the buffer. + static bool ElfFileSoName( + const MappingInfo& mapping, char* soname, size_t soname_size); + uintptr_t crash_address() const { return crash_address_; } void set_crash_address(uintptr_t crash_address) { crash_address_ = crash_address; diff --git a/src/client/linux/minidump_writer/minidump_writer.cc b/src/client/linux/minidump_writer/minidump_writer.cc index fa2ad9e1..86cec55b 100644 --- a/src/client/linux/minidump_writer/minidump_writer.cc +++ b/src/client/linux/minidump_writer/minidump_writer.cc @@ -938,7 +938,9 @@ class MinidumpWriter { static bool ShouldIncludeMapping(const MappingInfo& mapping) { if (mapping.name[0] == 0 || // only want modules with filenames. - mapping.offset || // only want to include one mapping per shared lib. + // Only want to include one mapping per shared lib. + // Avoid filtering executable mappings. + (mapping.offset != 0 && !mapping.exec) || mapping.size < 4096) { // too small to get a signature for. return false; } @@ -1029,7 +1031,8 @@ class MinidumpWriter { mod.base_of_image = mapping.start_addr; mod.size_of_image = mapping.size; - const size_t filepath_len = my_strlen(mapping.name); + const char* filepath_ptr = mapping.name; + size_t filepath_len = my_strlen(mapping.name); // Figure out file name from path const char* filename_ptr = mapping.name + filepath_len - 1; @@ -1040,7 +1043,31 @@ class MinidumpWriter { } filename_ptr++; - const size_t filename_len = mapping.name + filepath_len - filename_ptr; + size_t filename_len = mapping.name + filepath_len - filename_ptr; + + // If an executable is mapped from a non-zero offset, this is likely + // because the executable was loaded directly from inside an archive + // file. We try to find the name of the shared object (SONAME) by + // looking in the file for ELF sections. + + char soname[NAME_MAX]; + char pathname[NAME_MAX]; + if (mapping.exec && mapping.offset != 0 && + LinuxDumper::ElfFileSoName(mapping, soname, sizeof(soname))) { + filename_ptr = soname; + filename_len = my_strlen(soname); + + if (filepath_len + filename_len + 1 < NAME_MAX) { + // It doesn't have a real pathname, but tools such as stackwalk + // extract the basename, so simulating a pathname is helpful. + my_memcpy(pathname, filepath_ptr, filepath_len); + pathname[filepath_len] = '/'; + my_memcpy(pathname + filepath_len + 1, filename_ptr, filename_len); + pathname[filepath_len + filename_len + 1] = '\0'; + filepath_ptr = pathname; + filepath_len = filepath_len + filename_len + 1; + } + } uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX]; uint8_t* cv_ptr = cv_buf; @@ -1070,7 +1097,7 @@ class MinidumpWriter { mod.cv_record = cv.location(); MDLocationDescriptor ld; - if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld)) + if (!minidump_writer_.WriteString(filepath_ptr, filepath_len, &ld)) return false; mod.module_name_rva = ld.rva; return true; -- cgit v1.2.1