diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc index 2284284c8e1f..8aea159464a6 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.cc @@ -88,11 +88,13 @@ #include #include +#include #include #include "common/linux/linux_libc_support.h" #include "common/linux/linux_syscall_support.h" #include "common/memory.h" +#include "client/linux/minidump_writer/linux_dumper.h" #include "client/linux/minidump_writer/minidump_writer.h" #include "common/linux/guid_creator.h" #include "common/linux/eintr_wrapper.h" @@ -437,8 +439,11 @@ void ExceptionHandler::WaitForContinueSignal() { // Runs on the cloned process. bool ExceptionHandler::DoDump(pid_t crashing_process, const void* context, size_t context_size) { - return google_breakpad::WriteMinidump( - next_minidump_path_c_, crashing_process, context, context_size); + return google_breakpad::WriteMinidump(next_minidump_path_c_, + crashing_process, + context, + context_size, + mapping_info_); } // static @@ -515,4 +520,21 @@ bool ExceptionHandler::WriteMinidumpForChild(pid_t child, callback_context, true) : true; } +void ExceptionHandler::AddMappingInfo(const std::string& name, + const u_int8_t identifier[sizeof(MDGUID)], + uintptr_t start_address, + size_t mapping_size, + size_t file_offset) { + MappingInfo info; + info.start_addr = start_address; + info.size = mapping_size; + info.offset = file_offset; + strncpy(info.name, name.c_str(), std::min(name.size() + 1, sizeof(info))); + + std::pair mapping; + mapping.first = info; + memcpy(mapping.second, identifier, sizeof(MDGUID)); + mapping_info_.push_back(mapping); +} + } // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.h b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.h index d2d8e234229d..8ac797319e20 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.h +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler.h @@ -30,15 +30,18 @@ #ifndef CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ #define CLIENT_LINUX_HANDLER_EXCEPTION_HANDLER_H_ -#include #include +#include #include #include +#include #include #include "client/linux/android_ucontext.h" #include "client/linux/crash_generation/crash_generation_client.h" +#include "client/linux/minidump_writer/minidump_writer.h" +#include "google_breakpad/common/minidump_format.h" #include "processor/scoped_ptr.h" struct sigaction; @@ -207,6 +210,15 @@ class ExceptionHandler { return crash_generation_client_.get() != NULL; } + // Add information about a memory mapping. This can be used if + // a custom library loader is used that maps things in a way + // that the linux dumper can't handle by reading the maps file. + void AddMappingInfo(const std::string& name, + const u_int8_t identifier[sizeof(MDGUID)], + uintptr_t start_address, + size_t mapping_size, + size_t file_offset); + private: void Init(const std::string &dump_path, const int server_fd); @@ -262,6 +274,10 @@ class ExceptionHandler { // cloned process after creating it, until we have explicitly enabled // ptrace. This is used to store the file descriptors for the pipe int fdes[2]; + + // Callers can add extra info about mappings for cases where the + // dumper code cannot extract enough information from /proc//maps. + MappingList mapping_info_; }; } // namespace google_breakpad diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc index 1ef4ae78c523..3aa77e8b2316 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/handler/exception_handler_unittest.cc @@ -567,6 +567,87 @@ TEST(ExceptionHandlerTest, InstructionPointerMemoryNullPointer) { free(filename); } +static bool SimpleCallback(const char* dump_path, + const char* minidump_id, + void* context, + bool succeeded) { + if (!succeeded) + return succeeded; + + string* minidump_file = reinterpret_cast(context); + minidump_file->append(dump_path); + minidump_file->append("/"); + minidump_file->append(minidump_id); + minidump_file->append(".dmp"); + return true; +} + +// Test that anonymous memory maps can be annotated with names and IDs. +TEST(ExceptionHandlerTest, ModuleInfo) { + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE); + const char* kMemoryName = "a fake module"; + const u_int8_t kModuleGUID[sizeof(MDGUID)] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }; + char module_identifier_buffer[37]; + FileID::ConvertIdentifierToString(kModuleGUID, + module_identifier_buffer, + sizeof(module_identifier_buffer)); + string module_identifier(module_identifier_buffer); + // Strip out dashes + size_t pos; + while ((pos = module_identifier.find('-')) != string::npos) { + module_identifier.erase(pos, 1); + } + // And append a zero, because module IDs include an "age" field + // which is always zero on Linux. + module_identifier += "0"; + + // Get some memory. + char* memory = + reinterpret_cast(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + const u_int64_t kMemoryAddress = reinterpret_cast(memory); + ASSERT_TRUE(memory); + + string minidump_filename; + ExceptionHandler handler(TEMPDIR, NULL, SimpleCallback, + (void*)&minidump_filename, true); + // Add info about the anonymous memory mapping. + handler.AddMappingInfo(kMemoryName, + kModuleGUID, + kMemoryAddress, + kMemorySize, + 0); + handler.WriteMinidump(); + + // Read the minidump. Load the module list, and ensure that + // the mmap'ed |memory| is listed with the given module name + // and debug ID. + Minidump minidump(minidump_filename); + ASSERT_TRUE(minidump.Read()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* module = + module_list->GetModuleForAddress(kMemoryAddress); + ASSERT_TRUE(module); + + EXPECT_EQ(kMemoryAddress, module->base_address()); + EXPECT_EQ(kMemorySize, module->size()); + EXPECT_EQ(kMemoryName, module->code_file()); + EXPECT_EQ(module_identifier, module->debug_identifier()); + + unlink(minidump_filename.c_str()); +} + static const unsigned kControlMsgSize = CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(struct ucred)); diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.cc index 160bb67f5501..be716b1c4f92 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.cc @@ -106,12 +106,12 @@ bool DetachThread(pid_t pid) { } inline bool IsMappedFileOpenUnsafe( - const google_breakpad::MappingInfo* mapping) { + const google_breakpad::MappingInfo& mapping) { // It is unsafe to attempt to open a mapped file that lives under /dev, // because the semantics of the open may be driver-specific so we'd risk // hanging the crash dumper. And a file in /dev/ almost certainly has no // ELF file identifier anyways. - return my_strncmp(mapping->name, + return my_strncmp(mapping.name, kMappedFileUnsafePrefix, sizeof(kMappedFileUnsafePrefix) - 1) == 0; } @@ -237,16 +237,14 @@ LinuxDumper::BuildProcPath(char* path, pid_t pid, const char* node) const { } bool -LinuxDumper::ElfFileIdentifierForMapping(unsigned int mapping_id, +LinuxDumper::ElfFileIdentifierForMapping(const MappingInfo& mapping, uint8_t identifier[sizeof(MDGUID)]) { - assert(mapping_id < mappings_.size()); my_memset(identifier, 0, sizeof(MDGUID)); - const MappingInfo* mapping = mappings_[mapping_id]; if (IsMappedFileOpenUnsafe(mapping)) { return false; } - int fd = sys_open(mapping->name, O_RDONLY, 0); + int fd = sys_open(mapping.name, O_RDONLY, 0); if (fd < 0) return false; struct kernel_stat st; diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h index 1a085d2e3e2d..097f7fc5b991 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper.h @@ -163,7 +163,7 @@ class LinuxDumper { void BuildProcPath(char* path, pid_t pid, const char* node) const; // Generate a File ID from the .text section of a mapped entry - bool ElfFileIdentifierForMapping(unsigned int mapping_id, + bool ElfFileIdentifierForMapping(const MappingInfo& mapping, uint8_t identifier[sizeof(MDGUID)]); // Utility method to find the location of where the kernel has diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest.cc index 8cfb6004cf9b..32c357cd1736 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/linux_dumper_unittest.cc @@ -222,7 +222,7 @@ TEST(LinuxDumperTest, FileIDsMatch) { uint8_t identifier1[sizeof(MDGUID)]; uint8_t identifier2[sizeof(MDGUID)]; - EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(i, identifier1)); + EXPECT_TRUE(dumper.ElfFileIdentifierForMapping(*mappings[i], identifier1)); FileID fileid(exe_name); EXPECT_TRUE(fileid.ElfFileIdentifier(identifier2)); char identifier_string1[37]; diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc index 4ca1dd9456a4..a0438fa4356e 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.cc @@ -416,7 +416,8 @@ class MinidumpWriter { // case (1) above MinidumpWriter(const char* filename, pid_t crashing_pid, - const ExceptionHandler::CrashContext* context) + const ExceptionHandler::CrashContext* context, + const MappingList& mappings) : filename_(filename), siginfo_(&context->siginfo), ucontext_(&context->context), @@ -429,7 +430,8 @@ class MinidumpWriter { crashing_tid_(context->tid), crashing_tid_pc_(0), dumper_(crashing_pid), - memory_blocks_(dumper_.allocator()) { + memory_blocks_(dumper_.allocator()), + mapping_info_(mappings) { } // case (2) above @@ -829,13 +831,27 @@ class MinidumpWriter { return true; } + // If there is caller-provided information about this mapping + // in the mapping_info_ list, return true. Otherwise, return false. + bool HaveMappingInfo(const MappingInfo& mapping) { + for (MappingList::const_iterator iter = mapping_info_.begin(); + iter != mapping_info_.end(); + ++iter) { + if (iter->first.start_addr == mapping.start_addr && + iter->first.size == mapping.size) { + return true; + } + } + return false; + } + // Write information about the mappings in effect. Because we are using the // minidump format, the information about the mappings is pretty limited. // Because of this, we also include the full, unparsed, /proc/$x/maps file in // another stream in the file. bool WriteMappings(MDRawDirectory* dirent) { const unsigned num_mappings = dumper_.mappings().size(); - unsigned num_output_mappings = 0; + unsigned num_output_mappings = mapping_info_.size(); for (unsigned i = 0; i < dumper_.mappings().size(); ++i) { const MappingInfo& mapping = *dumper_.mappings()[i]; @@ -851,58 +867,87 @@ class MinidumpWriter { dirent->location = list.location(); *list.get() = num_output_mappings; - for (unsigned i = 0, j = 0; i < num_mappings; ++i) { + // First write all the mappings from the dumper + unsigned int j = 0; + for (unsigned i = 0; i < num_mappings; ++i) { const MappingInfo& mapping = *dumper_.mappings()[i]; - if (!ShouldIncludeMapping(mapping)) + if (!ShouldIncludeMapping(mapping) || HaveMappingInfo(mapping)) continue; MDRawModule mod; - my_memset(&mod, 0, MD_MODULE_SIZE); - mod.base_of_image = mapping.start_addr; - mod.size_of_image = mapping.size; - const size_t filepath_len = my_strlen(mapping.name); - - // Figure out file name from path - const char* filename_ptr = mapping.name + filepath_len - 1; - while (filename_ptr >= mapping.name) { - if (*filename_ptr == '/') - break; - filename_ptr--; - } - filename_ptr++; - const size_t filename_len = mapping.name + filepath_len - filename_ptr; - uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX]; - uint8_t* cv_ptr = cv_buf; - UntypedMDRVA cv(&minidump_writer_); - if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1)) + if (!FillRawModule(mapping, mod, NULL)) return false; - - const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE; - memcpy(cv_ptr, &cv_signature, sizeof(cv_signature)); - cv_ptr += sizeof(cv_signature); - uint8_t* signature = cv_ptr; - cv_ptr += sizeof(MDGUID); - dumper_.ElfFileIdentifierForMapping(i, signature); - my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux. - cv_ptr += sizeof(uint32_t); - - // Write pdb_file_name - memcpy(cv_ptr, filename_ptr, filename_len + 1); - cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1); - - mod.cv_record = cv.location(); - - MDLocationDescriptor ld; - if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld)) - return false; - mod.module_name_rva = ld.rva; - list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); } + // Next write all the mappings provided by the caller + for (MappingList::const_iterator iter = mapping_info_.begin(); + iter != mapping_info_.end(); + ++iter) { + MDRawModule mod; + if (!FillRawModule(iter->first, mod, iter->second)) + return false; + list.CopyIndexAfterObject(j++, &mod, MD_MODULE_SIZE); + } return true; } + // Fill the MDRawModule mod with information about the provided + // mapping. If identifier is non-NULL, use it instead of calculating + // a file ID from the mapping. + bool FillRawModule(const MappingInfo& mapping, + MDRawModule& mod, + const u_int8_t* identifier) { + my_memset(&mod, 0, MD_MODULE_SIZE); + + mod.base_of_image = mapping.start_addr; + mod.size_of_image = mapping.size; + const size_t filepath_len = my_strlen(mapping.name); + + // Figure out file name from path + const char* filename_ptr = mapping.name + filepath_len - 1; + while (filename_ptr >= mapping.name) { + if (*filename_ptr == '/') + break; + filename_ptr--; + } + filename_ptr++; + + size_t filename_len = mapping.name + filepath_len - filename_ptr; + + uint8_t cv_buf[MDCVInfoPDB70_minsize + NAME_MAX]; + uint8_t* cv_ptr = cv_buf; + UntypedMDRVA cv(&minidump_writer_); + if (!cv.Allocate(MDCVInfoPDB70_minsize + filename_len + 1)) + return false; + + const uint32_t cv_signature = MD_CVINFOPDB70_SIGNATURE; + memcpy(cv_ptr, &cv_signature, sizeof(cv_signature)); + cv_ptr += sizeof(cv_signature); + uint8_t* signature = cv_ptr; + cv_ptr += sizeof(MDGUID); + if (identifier) { + // GUID was provided by caller. + memcpy(signature, identifier, sizeof(MDGUID)); + } else { + dumper_.ElfFileIdentifierForMapping(mapping, signature); + } + my_memset(cv_ptr, 0, sizeof(uint32_t)); // Set age to 0 on Linux. + cv_ptr += sizeof(uint32_t); + + // Write pdb_file_name + memcpy(cv_ptr, filename_ptr, filename_len + 1); + cv.Copy(cv_buf, MDCVInfoPDB70_minsize + filename_len + 1); + + mod.cv_record = cv.location(); + + MDLocationDescriptor ld; + if (!minidump_writer_.WriteString(mapping.name, filepath_len, &ld)) + return false; + mod.module_name_rva = ld.rva; + return true; + } + bool WriteMemoryListStream(MDRawDirectory* dirent) { TypedMDRVA list(&minidump_writer_); if (!list.AllocateObjectAndArray(memory_blocks_.size(), @@ -1309,15 +1354,24 @@ popline: // written while writing the thread list stream, but saved here // so a memory list stream can be written afterwards. wasteful_vector memory_blocks_; + // Additional information about some mappings provided by the caller. + const MappingList& mapping_info_; }; bool WriteMinidump(const char* filename, pid_t crashing_process, const void* blob, size_t blob_size) { + MappingList m; + return WriteMinidump(filename, crashing_process, blob, blob_size, m); +} + +bool WriteMinidump(const char* filename, pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings) { if (blob_size != sizeof(ExceptionHandler::CrashContext)) return false; const ExceptionHandler::CrashContext* context = reinterpret_cast(blob); - MinidumpWriter writer(filename, crashing_process, context); + MinidumpWriter writer(filename, crashing_process, context, mappings); if (!writer.Init()) return false; return writer.Dump(); diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.h b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.h index 92a7f11f15ca..ba112a596e91 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.h +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer.h @@ -30,11 +30,20 @@ #ifndef CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ #define CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ +#include +#include + #include #include +#include "google_breakpad/common/minidump_format.h" + namespace google_breakpad { +// A list of +typedef std::pair MappingEntry; +typedef std::list MappingList; + // Write a minidump to the filesystem. This function does not malloc nor use // libc functions which may. Thus, it can be used in contexts where the state // of the heap may be corrupt. @@ -56,6 +65,11 @@ bool WriteMinidump(const char* filename, pid_t crashing_process, bool WriteMinidump(const char* filename, pid_t process, pid_t process_blamed_thread); +// This overload also allows passing a list of known mappings. +bool WriteMinidump(const char* filename, pid_t crashing_process, + const void* blob, size_t blob_size, + const MappingList& mappings); + } // namespace google_breakpad #endif // CLIENT_LINUX_MINIDUMP_WRITER_MINIDUMP_WRITER_H_ diff --git a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc index 0c02f58769c2..83664593a2a7 100644 --- a/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc +++ b/toolkit/crashreporter/google-breakpad/src/client/linux/minidump_writer/minidump_writer_unittest.cc @@ -68,3 +68,91 @@ TEST(MinidumpWriterTest, Setup) { close(fds[1]); } + +TEST(MinidumpWriterTest, MappingInfo) { + int fds[2]; + ASSERT_NE(-1, pipe(fds)); + + // These are defined here so the parent can use them to check the + // data from the minidump afterwards. + const u_int32_t kMemorySize = sysconf(_SC_PAGESIZE); + const char* kMemoryName = "a fake module"; + const u_int8_t kModuleGUID[sizeof(MDGUID)] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF + }; + char module_identifier_buffer[37]; + FileID::ConvertIdentifierToString(kModuleGUID, + module_identifier_buffer, + sizeof(module_identifier_buffer)); + string module_identifier(module_identifier_buffer); + // Strip out dashes + size_t pos; + while ((pos = module_identifier.find('-')) != string::npos) { + module_identifier.erase(pos, 1); + } + // And append a zero, because module IDs include an "age" field + // which is always zero on Linux. + module_identifier += "0"; + + // Get some memory. + char* memory = + reinterpret_cast(mmap(NULL, + kMemorySize, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, + 0)); + const u_int64_t kMemoryAddress = reinterpret_cast(memory); + ASSERT_TRUE(memory); + + const pid_t child = fork(); + if (child == 0) { + close(fds[1]); + char b; + HANDLE_EINTR(read(fds[0], &b, sizeof(b))); + close(fds[0]); + syscall(__NR_exit); + } + close(fds[0]); + + ExceptionHandler::CrashContext context; + memset(&context, 0, sizeof(context)); + + char templ[] = TEMPDIR "/minidump-writer-unittest-XXXXXX"; + mktemp(templ); + + // Add information about the mapped memory. + MappingInfo info; + info.start_addr = kMemoryAddress; + info.size = kMemorySize; + info.offset = 0; + strcpy(info.name, kMemoryName); + + MappingList mappings; + std::pair mapping; + mapping.first = info; + memcpy(mapping.second, kModuleGUID, sizeof(MDGUID)); + mappings.push_back(mapping); + ASSERT_TRUE(WriteMinidump(templ, child, &context, sizeof(context), mappings)); + + // Read the minidump. Load the module list, and ensure that + // the mmap'ed |memory| is listed with the given module name + // and debug ID. + Minidump minidump(templ); + ASSERT_TRUE(minidump.Read()); + + MinidumpModuleList* module_list = minidump.GetModuleList(); + ASSERT_TRUE(module_list); + const MinidumpModule* module = + module_list->GetModuleForAddress(kMemoryAddress); + ASSERT_TRUE(module); + + EXPECT_EQ(kMemoryAddress, module->base_address()); + EXPECT_EQ(kMemorySize, module->size()); + EXPECT_EQ(kMemoryName, module->code_file()); + EXPECT_EQ(module_identifier, module->debug_identifier()); + + unlink(templ); + close(fds[1]); +}