michael@0: // Copyright (c) 2007, Google Inc. michael@0: // All rights reserved. michael@0: // michael@0: // Redistribution and use in source and binary forms, with or without michael@0: // modification, are permitted provided that the following conditions are michael@0: // met: michael@0: // michael@0: // * Redistributions of source code must retain the above copyright michael@0: // notice, this list of conditions and the following disclaimer. michael@0: // * Redistributions in binary form must reproduce the above michael@0: // copyright notice, this list of conditions and the following disclaimer michael@0: // in the documentation and/or other materials provided with the michael@0: // distribution. michael@0: // * Neither the name of Google Inc. nor the names of its michael@0: // contributors may be used to endorse or promote products derived from michael@0: // this software without specific prior written permission. michael@0: // michael@0: // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS michael@0: // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT michael@0: // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR michael@0: // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT michael@0: // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, michael@0: // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT michael@0: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, michael@0: // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY michael@0: // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT michael@0: // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE michael@0: // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. michael@0: michael@0: // Author: Alfred Peng michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "client/solaris/handler/solaris_lwp.h" michael@0: #include "common/solaris/message_output.h" michael@0: michael@0: using namespace google_breakpad; michael@0: michael@0: // This unamed namespace contains helper function. michael@0: namespace { michael@0: michael@0: uintptr_t stack_base_address = 0; michael@0: static const int HEADER_MAX = 2000; michael@0: static const int MAP_MAX = 1000; michael@0: michael@0: // Context information for the callbacks when validating address by listing michael@0: // modules. michael@0: struct AddressValidatingContext { michael@0: uintptr_t address; michael@0: bool is_mapped; michael@0: michael@0: AddressValidatingContext() : address(0UL), is_mapped(false) { michael@0: } michael@0: }; michael@0: michael@0: // Convert from string to int. michael@0: static bool LocalAtoi(char *s, int *r) { michael@0: assert(s != NULL); michael@0: assert(r != NULL); michael@0: char *endptr = NULL; michael@0: int ret = strtol(s, &endptr, 10); michael@0: if (endptr == s) michael@0: return false; michael@0: *r = ret; michael@0: return true; michael@0: } michael@0: michael@0: // Callback invoked for each mapped module. michael@0: // It uses the module's adderss range to validate the address. michael@0: static bool AddressNotInModuleCallback(const ModuleInfo &module_info, michael@0: void *context) { michael@0: AddressValidatingContext *addr = michael@0: reinterpret_cast(context); michael@0: if (addr->is_mapped = ((module_info.start_addr > 0) && michael@0: (addr->address >= module_info.start_addr) && michael@0: (addr->address <= module_info.start_addr + michael@0: module_info.size))) { michael@0: stack_base_address = module_info.start_addr + module_info.size; michael@0: } michael@0: michael@0: return !addr->is_mapped; michael@0: } michael@0: michael@0: static int IterateLwpAll(int pid, michael@0: CallbackParam *callback_param) { michael@0: char lwp_path[40]; michael@0: DIR *dir; michael@0: int count = 0; michael@0: michael@0: snprintf(lwp_path, sizeof (lwp_path), "/proc/%d/lwp", (int)pid); michael@0: if ((dir = opendir(lwp_path)) == NULL) michael@0: return -1; michael@0: michael@0: struct dirent *entry = NULL; michael@0: while ((entry = readdir(dir)) != NULL) { michael@0: if ((strcmp(entry->d_name, ".") != 0) && michael@0: (strcmp(entry->d_name, "..") != 0)) { michael@0: int lwpid = 0; michael@0: int last_pid = 0; michael@0: if (LocalAtoi(entry->d_name, &lwpid) && last_pid != lwpid) { michael@0: last_pid = lwpid; michael@0: ++count; michael@0: if (callback_param && michael@0: !(callback_param->call_back)(lwpid, callback_param->context)) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: closedir(dir); michael@0: return count; michael@0: } michael@0: michael@0: #if defined(__i386) && !defined(NO_FRAME_POINTER) michael@0: void *GetNextFrame(void **last_ebp) { michael@0: void *sp = *last_ebp; michael@0: if ((unsigned long)sp == (unsigned long)last_ebp) michael@0: return NULL; michael@0: if ((unsigned long)sp & (sizeof(void *) - 1)) michael@0: return NULL; michael@0: if ((unsigned long)sp - (unsigned long)last_ebp > 100000) michael@0: return NULL; michael@0: return sp; michael@0: } michael@0: #elif defined(__sparc) michael@0: void *GetNextFrame(void *last_ebp) { michael@0: return reinterpret_cast(last_ebp)->fr_savfp; michael@0: } michael@0: #else michael@0: void *GetNextFrame(void **last_ebp) { michael@0: return reinterpret_cast(last_ebp); michael@0: } michael@0: #endif michael@0: michael@0: michael@0: class AutoCloser { michael@0: public: michael@0: AutoCloser(int fd) : fd_(fd) {} michael@0: ~AutoCloser() { if (fd_) close(fd_); } michael@0: private: michael@0: int fd_; michael@0: }; michael@0: michael@0: // Control the execution of the lwp. michael@0: // Suspend/Resume lwp based on the value of context. michael@0: static bool ControlLwp(int lwpid, void *context) { michael@0: // The current thread is the one to handle the crash. Ignore it. michael@0: if (lwpid != pthread_self()) { michael@0: int ctlfd; michael@0: char procname[PATH_MAX]; michael@0: bool suspend = *(bool *)context; michael@0: michael@0: // Open the /proc/$pid/lwp/$lwpid/lwpctl files michael@0: snprintf(procname, sizeof (procname), "/proc/self/lwp/%d/lwpctl", lwpid); michael@0: michael@0: if ((ctlfd = open(procname, O_WRONLY|O_EXCL)) < 0) { michael@0: print_message2(2, "failed to open %s in ControlLwp\n", procname); michael@0: return false; michael@0: } michael@0: michael@0: AutoCloser autocloser(ctlfd); michael@0: michael@0: long ctl[2]; michael@0: ctl[0] = suspend ? PCSTOP : PCRUN; michael@0: ctl[1] = 0; michael@0: if (write(ctlfd, ctl, sizeof (ctl)) != sizeof (ctl)) { michael@0: print_message2(2, "failed in lwp %d\n", lwpid); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Utility function to read the contents of a file that contains a michael@0: * prheader_t at the start (/proc/$pid/lstatus or /proc/$pid/lpsinfo). michael@0: * Return true on success. michael@0: */ michael@0: static bool read_lfile(int pid, const char *lname, prheader_t *lhp) { michael@0: char lpath[PATH_MAX]; michael@0: struct stat statb; michael@0: int fd; michael@0: size_t size; michael@0: michael@0: snprintf(lpath, sizeof (lpath), "/proc/%d/%s", pid, lname); michael@0: if ((fd = open(lpath, O_RDONLY)) < 0) { michael@0: print_message2(2, "failed to open %s in read_lfile\n", lpath); michael@0: return false; michael@0: } michael@0: michael@0: AutoCloser autocloser(fd); michael@0: michael@0: if (fstat(fd, &statb) != 0) michael@0: return false; michael@0: michael@0: size = statb.st_size; michael@0: if ((size / sizeof (prheader_t)) + 32 > HEADER_MAX) { michael@0: print_message1(2, "map size overflow\n"); michael@0: return false; michael@0: } michael@0: michael@0: if (pread(fd, lhp, size, 0) <= sizeof (prheader_t)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: namespace google_breakpad { michael@0: michael@0: SolarisLwp::SolarisLwp(int pid) : pid_(pid) { michael@0: } michael@0: michael@0: SolarisLwp::~SolarisLwp() { michael@0: } michael@0: michael@0: int SolarisLwp::ControlAllLwps(bool suspend) { michael@0: CallbackParam callback_param(ControlLwp, &suspend); michael@0: return IterateLwpAll(pid_, &callback_param); michael@0: } michael@0: michael@0: int SolarisLwp::GetLwpCount() const { michael@0: return IterateLwpAll(pid_, NULL); michael@0: } michael@0: michael@0: int SolarisLwp::Lwp_iter_all(int pid, michael@0: CallbackParam *callback_param) const { michael@0: lwpstatus_t *Lsp; michael@0: lwpstatus_t *sp; michael@0: prheader_t lphp[HEADER_MAX]; michael@0: prheader_t lhp[HEADER_MAX]; michael@0: prheader_t *Lphp = lphp; michael@0: prheader_t *Lhp = lhp; michael@0: lwpsinfo_t *Lpsp; michael@0: long nstat; michael@0: long ninfo; michael@0: int rv = 0; michael@0: michael@0: /* michael@0: * The /proc/pid/lstatus file has the array of lwpstatus_t's and the michael@0: * /proc/pid/lpsinfo file has the array of lwpsinfo_t's. michael@0: */ michael@0: if (read_lfile(pid, "lstatus", Lhp) == NULL) michael@0: return -1; michael@0: if (read_lfile(pid, "lpsinfo", Lphp) == NULL) { michael@0: return -1; michael@0: } michael@0: michael@0: Lsp = (lwpstatus_t *)(uintptr_t)(Lhp + 1); michael@0: Lpsp = (lwpsinfo_t *)(uintptr_t)(Lphp + 1); michael@0: michael@0: for (ninfo = Lphp->pr_nent; ninfo != 0; --ninfo) { michael@0: if (Lpsp->pr_sname != 'Z') { michael@0: sp = Lsp; michael@0: Lsp = (lwpstatus_t *)((uintptr_t)Lsp + Lhp->pr_entsize); michael@0: } else { michael@0: sp = NULL; michael@0: } michael@0: if (callback_param && michael@0: !(callback_param->call_back)(sp, callback_param->context)) michael@0: break; michael@0: ++rv; michael@0: Lpsp = (lwpsinfo_t *)((uintptr_t)Lpsp + Lphp->pr_entsize); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: uintptr_t SolarisLwp::GetLwpStackBottom(uintptr_t current_esp) const { michael@0: AddressValidatingContext addr; michael@0: addr.address = current_esp; michael@0: CallbackParam callback_param(AddressNotInModuleCallback, michael@0: &addr); michael@0: ListModules(&callback_param); michael@0: return stack_base_address; michael@0: } michael@0: michael@0: int SolarisLwp::GetModuleCount() const { michael@0: return ListModules(NULL); michael@0: } michael@0: michael@0: int SolarisLwp::ListModules( michael@0: CallbackParam *callback_param) const { michael@0: const char *maps_path = "/proc/self/map"; michael@0: struct stat status; michael@0: int fd = 0, num; michael@0: prmap_t map_array[MAP_MAX]; michael@0: prmap_t *maps = map_array; michael@0: size_t size; michael@0: michael@0: if ((fd = open(maps_path, O_RDONLY)) == -1) { michael@0: print_message2(2, "failed to open %s in ListModules\n", maps_path); michael@0: return -1; michael@0: } michael@0: michael@0: AutoCloser autocloser(fd); michael@0: michael@0: if (fstat(fd, &status)) michael@0: return -1; michael@0: michael@0: /* michael@0: * Determine number of mappings, this value must be michael@0: * larger than the actual module count michael@0: */ michael@0: size = status.st_size; michael@0: if ((num = (int)(size / sizeof (prmap_t))) > MAP_MAX) { michael@0: print_message1(2, "map size overflow\n"); michael@0: return -1; michael@0: } michael@0: michael@0: if (read(fd, (void *)maps, size) < 0) { michael@0: print_message2(2, "failed to read %d\n", fd); michael@0: return -1; michael@0: } michael@0: michael@0: prmap_t *_maps; michael@0: int _num; michael@0: int module_count = 0; michael@0: michael@0: /* michael@0: * Scan each mapping - note it is assummed that the mappings are michael@0: * presented in order. We fill holes between mappings. On intel michael@0: * the last mapping is usually the data segment of ld.so.1, after michael@0: * this comes a red zone into which non-fixed mapping won't get michael@0: * place. Thus we can simply bail from the loop after seeing the michael@0: * last mapping. michael@0: */ michael@0: for (_num = 0, _maps = maps; _num < num; ++_num, ++_maps) { michael@0: ModuleInfo module; michael@0: char *name = _maps->pr_mapname; michael@0: michael@0: memset(&module, 0, sizeof (module)); michael@0: module.start_addr = _maps->pr_vaddr; michael@0: module.size = _maps->pr_size; michael@0: if (strlen(name) > 0) { michael@0: int objectfd = 0; michael@0: char path[PATH_MAX]; michael@0: char buf[SELFMAG]; michael@0: michael@0: snprintf(path, sizeof (path), "/proc/self/object/%s", name); michael@0: if ((objectfd = open(path, O_RDONLY)) < 0) { michael@0: print_message1(2, "can't open module file\n"); michael@0: continue; michael@0: } michael@0: michael@0: AutoCloser autocloser(objectfd); michael@0: michael@0: if (read(objectfd, buf, SELFMAG) != SELFMAG) { michael@0: print_message1(2, "can't read module file\n"); michael@0: continue; michael@0: } michael@0: if (buf[0] != ELFMAG0 || buf[1] != ELFMAG1 || michael@0: buf[2] != ELFMAG2 || buf[3] != ELFMAG3) { michael@0: continue; michael@0: } michael@0: michael@0: strncpy(module.name, name, sizeof (module.name) - 1); michael@0: ++module_count; michael@0: } michael@0: if (callback_param && michael@0: (!callback_param->call_back(module, callback_param->context))) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return module_count; michael@0: } michael@0: michael@0: // Check if the address is a valid virtual address. michael@0: // If the address is in any of the mapped modules, we take it as valid. michael@0: // Otherwise it is invalid. michael@0: bool SolarisLwp::IsAddressMapped(uintptr_t address) const { michael@0: AddressValidatingContext addr; michael@0: addr.address = address; michael@0: CallbackParam callback_param(AddressNotInModuleCallback, michael@0: &addr); michael@0: ListModules(&callback_param); michael@0: return addr.is_mapped; michael@0: } michael@0: michael@0: // We're looking for a ucontext_t as the second parameter michael@0: // to a signal handler function call. Luckily, the ucontext_t michael@0: // has an ebp(fp on SPARC) member which should match the ebp(fp) michael@0: // pointed to by the ebp(fp) of the signal handler frame. michael@0: // The Solaris stack looks like this: michael@0: // http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/lib/libproc/common/Pstack.c#81 michael@0: bool SolarisLwp::FindSigContext(uintptr_t sighandler_ebp, michael@0: ucontext_t **sig_ctx) { michael@0: uintptr_t previous_ebp; michael@0: uintptr_t sig_ebp; michael@0: const int MAX_STACK_DEPTH = 50; michael@0: int depth_counter = 0; michael@0: michael@0: do { michael@0: #if TARGET_CPU_SPARC michael@0: previous_ebp = reinterpret_cast(GetNextFrame( michael@0: reinterpret_cast(sighandler_ebp))); michael@0: *sig_ctx = reinterpret_cast(sighandler_ebp + sizeof (struct frame)); michael@0: uintptr_t sig_esp = (*sig_ctx)->uc_mcontext.gregs[REG_O6]; michael@0: if (sig_esp < previous_ebp && sig_esp > sighandler_ebp) michael@0: sig_ebp = (uintptr_t)(((struct frame *)sig_esp)->fr_savfp); michael@0: michael@0: #elif TARGET_CPU_X86 michael@0: previous_ebp = reinterpret_cast(GetNextFrame( michael@0: reinterpret_cast(sighandler_ebp))); michael@0: *sig_ctx = reinterpret_cast(sighandler_ebp + sizeof (struct frame) + michael@0: 3 * sizeof(uintptr_t)); michael@0: sig_ebp = (*sig_ctx)->uc_mcontext.gregs[EBP]; michael@0: #endif michael@0: sighandler_ebp = previous_ebp; michael@0: depth_counter++; michael@0: } while(previous_ebp != sig_ebp && sighandler_ebp != 0 && michael@0: IsAddressMapped(sighandler_ebp) && depth_counter < MAX_STACK_DEPTH); michael@0: michael@0: return previous_ebp == sig_ebp && previous_ebp != 0; michael@0: } michael@0: michael@0: } // namespace google_breakpad