// Copyright (c) 2007, Google Inc. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Author: Alfred Peng #include #include #include #include #include #include #include #include #include #include "client/solaris/handler/minidump_generator.h" #include "client/minidump_file_writer-inl.h" #include "common/solaris/file_id.h" namespace { using namespace google_breakpad; // Argument for the writer function. struct WriterArgument { MinidumpFileWriter* minidump_writer; // Pid of the lwp who called WriteMinidumpToFile int requester_pid; // The stack bottom of the lwp which caused the dump. // Mainly used to find the lwp id of the crashed lwp since signal // handler may not be called in the lwp who caused it. uintptr_t crashed_stack_bottom; // Id of the crashing lwp. int crashed_lwpid; // Signal number when crash happened. Can be 0 if this is a requested dump. int signo; // The ebp of the signal handler frame on x86. Can be 0 if this is a // requested dump. uintptr_t sighandler_ebp; // User context when crash happens. Can be NULL if this is a requested dump. // This is actually an out parameter, but it will be filled in at the start // of the writer LWP. ucontext_t* sig_ctx; // Used to get information about the lwps. SolarisLwp* lwp_lister; }; // Holding context information for the callback of finding the crashing lwp. struct FindCrashLwpContext { const SolarisLwp* lwp_lister; uintptr_t crashing_stack_bottom; int crashing_lwpid; FindCrashLwpContext() : lwp_lister(NULL), crashing_stack_bottom(0UL), crashing_lwpid(-1) { } }; // Callback for list lwps. // It will compare the stack bottom of the provided lwp with the stack // bottom of the crashed lwp, it they are eqaul, this lwp is the one // who crashed. bool IsLwpCrashedCallback(lwpstatus_t* lsp, void* context) { FindCrashLwpContext* crashing_context = static_cast(context); const SolarisLwp* lwp_lister = crashing_context->lwp_lister; const prgregset_t* gregs = &(lsp->pr_reg); #if TARGET_CPU_SPARC uintptr_t last_ebp = (*gregs)[R_FP]; #elif TARGET_CPU_X86 uintptr_t last_ebp = (*gregs)[EBP]; #endif uintptr_t stack_bottom = lwp_lister->GetLwpStackBottom(last_ebp); if (stack_bottom > last_ebp && stack_bottom == crashing_context->crashing_stack_bottom) { // Got it. Stop iteration. crashing_context->crashing_lwpid = lsp->pr_lwpid; return false; } return true; } // Find the crashing lwpid. // This is done based on stack bottom comparing. int FindCrashingLwp(uintptr_t crashing_stack_bottom, int requester_pid, const SolarisLwp* lwp_lister) { FindCrashLwpContext context; context.lwp_lister = lwp_lister; context.crashing_stack_bottom = crashing_stack_bottom; CallbackParam callback_param(IsLwpCrashedCallback, &context); lwp_lister->Lwp_iter_all(lwp_lister->getpid(), &callback_param); return context.crashing_lwpid; } bool WriteLwpStack(const SolarisLwp* lwp_lister, uintptr_t last_esp, UntypedMDRVA* memory, MDMemoryDescriptor* loc) { uintptr_t stack_bottom = lwp_lister->GetLwpStackBottom(last_esp); if (stack_bottom >= last_esp) { int size = stack_bottom - last_esp; if (size > 0) { if (!memory->Allocate(size)) return false; memory->Copy(reinterpret_cast(last_esp), size); loc->start_of_memory_range = last_esp; loc->memory = memory->location(); } return true; } return false; } #if TARGET_CPU_SPARC bool WriteContext(MDRawContextSPARC* context, ucontext_t* sig_ctx) { assert(sig_ctx != NULL); int* regs = sig_ctx->uc_mcontext.gregs; context->context_flags = MD_CONTEXT_SPARC_FULL; context->ccr = (unsigned int)(regs[0]); context->pc = (unsigned int)(regs[REG_PC]); context->npc = (unsigned int)(regs[REG_nPC]); context->y = (unsigned int)(regs[REG_Y]); context->asi = (unsigned int)(regs[19]); context->fprs = (unsigned int)(regs[20]); for ( int i = 0 ; i < 32; ++i ) { context->g_r[i] = 0; } for ( int i = 1 ; i < 16; ++i ) { context->g_r[i] = (uintptr_t)(sig_ctx->uc_mcontext.gregs[i + 3]); } context->g_r[30] = (uintptr_t)(((struct frame*)context->g_r[14])->fr_savfp); return true; } bool WriteContext(MDRawContextSPARC* context, prgregset_t regs, prfpregset_t* fp_regs) { if (!context || !regs) return false; context->context_flags = MD_CONTEXT_SPARC_FULL; context->ccr = (uintptr_t)(regs[32]); context->pc = (uintptr_t)(regs[R_PC]); context->npc = (uintptr_t)(regs[R_nPC]); context->y = (uintptr_t)(regs[R_Y]); context->asi = (uintptr_t)(regs[36]); context->fprs = (uintptr_t)(regs[37]); for ( int i = 0 ; i < 32 ; ++i ){ context->g_r[i] = (uintptr_t)(regs[i]); } return true; } #elif TARGET_CPU_X86 bool WriteContext(MDRawContextX86* context, prgregset_t regs, prfpregset_t* fp_regs) { if (!context || !regs) return false; context->context_flags = MD_CONTEXT_X86_FULL; context->cs = regs[CS]; context->ds = regs[DS]; context->es = regs[ES]; context->fs = regs[FS]; context->gs = regs[GS]; context->ss = regs[SS]; context->edi = regs[EDI]; context->esi = regs[ESI]; context->ebx = regs[EBX]; context->edx = regs[EDX]; context->ecx = regs[ECX]; context->eax = regs[EAX]; context->ebp = regs[EBP]; context->eip = regs[EIP]; context->esp = regs[UESP]; context->eflags = regs[EFL]; return true; } #endif /* TARGET_CPU_XXX */ // Write information about a crashed Lwp. // When a lwp crash, kernel will write something on the stack for processing // signal. This makes the current stack not reliable, and our stack walker // won't figure out the whole call stack for this. So we write the stack at the // time of the crash into the minidump file, not the current stack. bool WriteCrashedLwpStream(MinidumpFileWriter* minidump_writer, const WriterArgument* writer_args, const lwpstatus_t* lsp, MDRawThread* lwp) { assert(writer_args->sig_ctx != NULL); lwp->thread_id = lsp->pr_lwpid; #if TARGET_CPU_SPARC UntypedMDRVA memory(minidump_writer); if (!WriteLwpStack(writer_args->lwp_lister, writer_args->sig_ctx->uc_mcontext.gregs[REG_O6], &memory, &lwp->stack)) return false; TypedMDRVA context(minidump_writer); if (!context.Allocate()) return false; lwp->thread_context = context.location(); memset(context.get(), 0, sizeof(MDRawContextSPARC)); return WriteContext(context.get(), writer_args->sig_ctx); #elif TARGET_CPU_X86 UntypedMDRVA memory(minidump_writer); if (!WriteLwpStack(writer_args->lwp_lister, writer_args->sig_ctx->uc_mcontext.gregs[UESP], &memory, &lwp->stack)) return false; TypedMDRVA context(minidump_writer); if (!context.Allocate()) return false; lwp->thread_context = context.location(); memset(context.get(), 0, sizeof(MDRawContextX86)); return WriteContext(context.get(), (int*)&writer_args->sig_ctx->uc_mcontext.gregs, &writer_args->sig_ctx->uc_mcontext.fpregs); #endif } bool WriteLwpStream(MinidumpFileWriter* minidump_writer, const SolarisLwp* lwp_lister, const lwpstatus_t* lsp, MDRawThread* lwp) { prfpregset_t fp_regs = lsp->pr_fpreg; const prgregset_t* gregs = &(lsp->pr_reg); UntypedMDRVA memory(minidump_writer); #if TARGET_CPU_SPARC if (!WriteLwpStack(lwp_lister, (*gregs)[R_SP], &memory, &lwp->stack)) return false; // Write context TypedMDRVA context(minidump_writer); if (!context.Allocate()) return false; // should be the thread_id lwp->thread_id = lsp->pr_lwpid; lwp->thread_context = context.location(); memset(context.get(), 0, sizeof(MDRawContextSPARC)); #elif TARGET_CPU_X86 if (!WriteLwpStack(lwp_lister, (*gregs)[UESP], &memory, &lwp->stack)) return false; // Write context TypedMDRVA context(minidump_writer); if (!context.Allocate()) return false; // should be the thread_id lwp->thread_id = lsp->pr_lwpid; lwp->thread_context = context.location(); memset(context.get(), 0, sizeof(MDRawContextX86)); #endif /* TARGET_CPU_XXX */ return WriteContext(context.get(), (int*)gregs, &fp_regs); } bool WriteCPUInformation(MDRawSystemInfo* sys_info) { struct utsname uts; char *major, *minor, *build; sys_info->number_of_processors = sysconf(_SC_NPROCESSORS_CONF); sys_info->processor_architecture = MD_CPU_ARCHITECTURE_UNKNOWN; if (uname(&uts) != -1) { // Match "i86pc" as X86 architecture. if (strcmp(uts.machine, "i86pc") == 0) sys_info->processor_architecture = MD_CPU_ARCHITECTURE_X86; else if (strcmp(uts.machine, "sun4u") == 0) sys_info->processor_architecture = MD_CPU_ARCHITECTURE_SPARC; } major = uts.release; minor = strchr(major, '.'); *minor = '\0'; ++minor; sys_info->major_version = atoi(major); sys_info->minor_version = atoi(minor); build = strchr(uts.version, '_'); ++build; sys_info->build_number = atoi(build); return true; } bool WriteOSInformation(MinidumpFileWriter* minidump_writer, MDRawSystemInfo* sys_info) { sys_info->platform_id = MD_OS_SOLARIS; struct utsname uts; if (uname(&uts) != -1) { char os_version[512]; size_t space_left = sizeof(os_version); memset(os_version, 0, space_left); const char* os_info_table[] = { uts.sysname, uts.release, uts.version, uts.machine, "OpenSolaris", NULL }; for (const char** cur_os_info = os_info_table; *cur_os_info != NULL; ++cur_os_info) { if (cur_os_info != os_info_table && space_left > 1) { strcat(os_version, " "); --space_left; } if (space_left > strlen(*cur_os_info)) { strcat(os_version, *cur_os_info); space_left -= strlen(*cur_os_info); } else { break; } } MDLocationDescriptor location; if (!minidump_writer->WriteString(os_version, 0, &location)) return false; sys_info->csd_version_rva = location.rva; } return true; } // Callback context for get writting lwp information. struct LwpInfoCallbackCtx { MinidumpFileWriter* minidump_writer; const WriterArgument* writer_args; TypedMDRVA* list; int lwp_index; }; bool LwpInformationCallback(lwpstatus_t* lsp, void* context) { bool success = true; LwpInfoCallbackCtx* callback_context = static_cast(context); // The current lwp is the one to handle the crash. Ignore it. if (lsp->pr_lwpid != pthread_self()) { LwpInfoCallbackCtx* callback_context = static_cast(context); MDRawThread lwp; memset(&lwp, 0, sizeof(MDRawThread)); if (lsp->pr_lwpid != callback_context->writer_args->crashed_lwpid || callback_context->writer_args->sig_ctx == NULL) { success = WriteLwpStream(callback_context->minidump_writer, callback_context->writer_args->lwp_lister, lsp, &lwp); } else { success = WriteCrashedLwpStream(callback_context->minidump_writer, callback_context->writer_args, lsp, &lwp); } if (success) { callback_context->list->CopyIndexAfterObject( callback_context->lwp_index++, &lwp, sizeof(MDRawThread)); } } return success; } bool WriteLwpListStream(MinidumpFileWriter* minidump_writer, const WriterArgument* writer_args, MDRawDirectory* dir) { // Get the lwp information. const SolarisLwp* lwp_lister = writer_args->lwp_lister; int lwp_count = lwp_lister->GetLwpCount(); if (lwp_count < 0) return false; TypedMDRVA list(minidump_writer); if (!list.AllocateObjectAndArray(lwp_count - 1, sizeof(MDRawThread))) return false; dir->stream_type = MD_THREAD_LIST_STREAM; dir->location = list.location(); list.get()->number_of_threads = lwp_count - 1; LwpInfoCallbackCtx context; context.minidump_writer = minidump_writer; context.writer_args = writer_args; context.list = &list; context.lwp_index = 0; CallbackParam callback_param(LwpInformationCallback, &context); int written = lwp_lister->Lwp_iter_all(lwp_lister->getpid(), &callback_param); return written == lwp_count; } bool WriteCVRecord(MinidumpFileWriter* minidump_writer, MDRawModule* module, const char* module_path, char* realname) { TypedMDRVA cv(minidump_writer); char path[PATH_MAX]; const char* module_name = module_path ? module_path : ""; snprintf(path, sizeof(path), "/proc/self/object/%s", module_name); size_t module_name_length = strlen(realname); if (!cv.AllocateObjectAndArray(module_name_length + 1, sizeof(uint8_t))) return false; if (!cv.CopyIndexAfterObject(0, realname, module_name_length)) return false; module->cv_record = cv.location(); MDCVInfoPDB70* cv_ptr = cv.get(); memset(cv_ptr, 0, sizeof(MDCVInfoPDB70)); cv_ptr->cv_signature = MD_CVINFOPDB70_SIGNATURE; cv_ptr->age = 0; // Get the module identifier FileID file_id(path); unsigned char identifier[16]; if (file_id.ElfFileIdentifier(identifier)) { cv_ptr->signature.data1 = (uint32_t)identifier[0] << 24 | (uint32_t)identifier[1] << 16 | (uint32_t)identifier[2] << 8 | (uint32_t)identifier[3]; cv_ptr->signature.data2 = (uint32_t)identifier[4] << 8 | identifier[5]; cv_ptr->signature.data3 = (uint32_t)identifier[6] << 8 | identifier[7]; cv_ptr->signature.data4[0] = identifier[8]; cv_ptr->signature.data4[1] = identifier[9]; cv_ptr->signature.data4[2] = identifier[10]; cv_ptr->signature.data4[3] = identifier[11]; cv_ptr->signature.data4[4] = identifier[12]; cv_ptr->signature.data4[5] = identifier[13]; cv_ptr->signature.data4[6] = identifier[14]; cv_ptr->signature.data4[7] = identifier[15]; } return true; } struct ModuleInfoCallbackCtx { MinidumpFileWriter* minidump_writer; const WriterArgument* writer_args; TypedMDRVA* list; int module_index; }; bool ModuleInfoCallback(const ModuleInfo& module_info, void* context) { ModuleInfoCallbackCtx* callback_context = static_cast(context); // Skip those modules without name, or those that are not modules. if (strlen(module_info.name) == 0) return true; MDRawModule module; memset(&module, 0, sizeof(module)); MDLocationDescriptor loc; char path[PATH_MAX]; char buf[PATH_MAX]; char* realname; int count; snprintf(path, sizeof (path), "/proc/self/path/%s", module_info.name); if ((count = readlink(path, buf, PATH_MAX)) < 0) return false; buf[count] = '\0'; if ((realname = strrchr(buf, '/')) == NULL) return false; realname++; if (!callback_context->minidump_writer->WriteString(realname, 0, &loc)) return false; module.base_of_image = (uint64_t)module_info.start_addr; module.size_of_image = module_info.size; module.module_name_rva = loc.rva; if (!WriteCVRecord(callback_context->minidump_writer, &module, module_info.name, realname)) return false; callback_context->list->CopyIndexAfterObject( callback_context->module_index++, &module, MD_MODULE_SIZE); return true; } bool WriteModuleListStream(MinidumpFileWriter* minidump_writer, const WriterArgument* writer_args, MDRawDirectory* dir) { TypedMDRVA list(minidump_writer); int module_count = writer_args->lwp_lister->GetModuleCount(); if (module_count <= 0 || !list.AllocateObjectAndArray(module_count, MD_MODULE_SIZE)) { return false; } dir->stream_type = MD_MODULE_LIST_STREAM; dir->location = list.location(); list.get()->number_of_modules = module_count; ModuleInfoCallbackCtx context; context.minidump_writer = minidump_writer; context.writer_args = writer_args; context.list = &list; context.module_index = 0; CallbackParam callback(ModuleInfoCallback, &context); return writer_args->lwp_lister->ListModules(&callback) == module_count; } bool WriteSystemInfoStream(MinidumpFileWriter* minidump_writer, const WriterArgument* writer_args, MDRawDirectory* dir) { TypedMDRVA sys_info(minidump_writer); if (!sys_info.Allocate()) return false; dir->stream_type = MD_SYSTEM_INFO_STREAM; dir->location = sys_info.location(); return WriteCPUInformation(sys_info.get()) && WriteOSInformation(minidump_writer, sys_info.get()); } bool WriteExceptionStream(MinidumpFileWriter* minidump_writer, const WriterArgument* writer_args, MDRawDirectory* dir) { // This happenes when this is not a crash, but a requested dump. if (writer_args->sig_ctx == NULL) return false; TypedMDRVA exception(minidump_writer); if (!exception.Allocate()) return false; dir->stream_type = MD_EXCEPTION_STREAM; dir->location = exception.location(); exception.get()->thread_id = writer_args->crashed_lwpid; exception.get()->exception_record.exception_code = writer_args->signo; exception.get()->exception_record.exception_flags = 0; #if TARGET_CPU_SPARC if (writer_args->sig_ctx != NULL) { exception.get()->exception_record.exception_address = writer_args->sig_ctx->uc_mcontext.gregs[REG_PC]; } else { return true; } // Write context of the exception. TypedMDRVA context(minidump_writer); if (!context.Allocate()) return false; exception.get()->thread_context = context.location(); memset(context.get(), 0, sizeof(MDRawContextSPARC)); return WriteContext(context.get(), writer_args->sig_ctx); #elif TARGET_CPU_X86 if (writer_args->sig_ctx != NULL) { exception.get()->exception_record.exception_address = writer_args->sig_ctx->uc_mcontext.gregs[EIP]; } else { return true; } // Write context of the exception. TypedMDRVA context(minidump_writer); if (!context.Allocate()) return false; exception.get()->thread_context = context.location(); memset(context.get(), 0, sizeof(MDRawContextX86)); return WriteContext(context.get(), (int*)&writer_args->sig_ctx->uc_mcontext.gregs, NULL); #endif } bool WriteMiscInfoStream(MinidumpFileWriter* minidump_writer, const WriterArgument* writer_args, MDRawDirectory* dir) { TypedMDRVA info(minidump_writer); if (!info.Allocate()) return false; dir->stream_type = MD_MISC_INFO_STREAM; dir->location = info.location(); info.get()->size_of_info = sizeof(MDRawMiscInfo); info.get()->flags1 = MD_MISCINFO_FLAGS1_PROCESS_ID; info.get()->process_id = writer_args->requester_pid; return true; } bool WriteBreakpadInfoStream(MinidumpFileWriter* minidump_writer, const WriterArgument* writer_args, MDRawDirectory* dir) { TypedMDRVA info(minidump_writer); if (!info.Allocate()) return false; dir->stream_type = MD_BREAKPAD_INFO_STREAM; dir->location = info.location(); info.get()->validity = MD_BREAKPAD_INFO_VALID_DUMP_THREAD_ID | MD_BREAKPAD_INFO_VALID_REQUESTING_THREAD_ID; info.get()->dump_thread_id = getpid(); info.get()->requesting_thread_id = writer_args->requester_pid; return true; } class AutoLwpResumer { public: AutoLwpResumer(SolarisLwp* lwp) : lwp_(lwp) {} ~AutoLwpResumer() { lwp_->ControlAllLwps(false); } private: SolarisLwp* lwp_; }; // Prototype of writer functions. typedef bool (*WriteStreamFN)(MinidumpFileWriter*, const WriterArgument*, MDRawDirectory*); // Function table to writer a full minidump. const WriteStreamFN writers[] = { WriteLwpListStream, WriteModuleListStream, WriteSystemInfoStream, WriteExceptionStream, WriteMiscInfoStream, WriteBreakpadInfoStream, }; // Will call each writer function in the writers table. //void* MinidumpGenerator::Write(void* argument) { void* Write(void* argument) { WriterArgument* writer_args = static_cast(argument); if (!writer_args->lwp_lister->ControlAllLwps(true)) return NULL; AutoLwpResumer lwpResumer(writer_args->lwp_lister); if (writer_args->sighandler_ebp != 0 && writer_args->lwp_lister->FindSigContext(writer_args->sighandler_ebp, &writer_args->sig_ctx)) { writer_args->crashed_stack_bottom = writer_args->lwp_lister->GetLwpStackBottom( #if TARGET_CPU_SPARC writer_args->sig_ctx->uc_mcontext.gregs[REG_O6] #elif TARGET_CPU_X86 writer_args->sig_ctx->uc_mcontext.gregs[UESP] #endif ); int crashed_lwpid = FindCrashingLwp(writer_args->crashed_stack_bottom, writer_args->requester_pid, writer_args->lwp_lister); if (crashed_lwpid > 0) writer_args->crashed_lwpid = crashed_lwpid; } MinidumpFileWriter* minidump_writer = writer_args->minidump_writer; TypedMDRVA header(minidump_writer); TypedMDRVA dir(minidump_writer); if (!header.Allocate()) return 0; int writer_count = sizeof(writers) / sizeof(writers[0]); // Need directory space for all writers. if (!dir.AllocateArray(writer_count)) return 0; header.get()->signature = MD_HEADER_SIGNATURE; header.get()->version = MD_HEADER_VERSION; header.get()->time_date_stamp = time(NULL); header.get()->stream_count = writer_count; header.get()->stream_directory_rva = dir.position(); int dir_index = 0; MDRawDirectory local_dir; for (int i = 0; i < writer_count; ++i) { if ((*writers[i])(minidump_writer, writer_args, &local_dir)) dir.CopyIndex(dir_index++, &local_dir); } return 0; } } // namespace namespace google_breakpad { MinidumpGenerator::MinidumpGenerator() { } MinidumpGenerator::~MinidumpGenerator() { } // Write minidump into file. // It runs in a different thread from the crashing thread. bool MinidumpGenerator::WriteMinidumpToFile(const char* file_pathname, int signo, uintptr_t sighandler_ebp, ucontext_t** sig_ctx) const { // The exception handler thread. pthread_t handler_thread; assert(file_pathname != NULL); if (file_pathname == NULL) return false; MinidumpFileWriter minidump_writer; if (minidump_writer.Open(file_pathname)) { WriterArgument argument; memset(&argument, 0, sizeof(argument)); SolarisLwp lwp_lister(getpid()); argument.lwp_lister = &lwp_lister; argument.minidump_writer = &minidump_writer; argument.requester_pid = getpid(); argument.crashed_lwpid = pthread_self(); argument.signo = signo; argument.sighandler_ebp = sighandler_ebp; argument.sig_ctx = NULL; pthread_create(&handler_thread, NULL, Write, (void*)&argument); pthread_join(handler_thread, NULL); return true; } return false; } } // namespace google_breakpad