| /* ********************************************************** |
| * Copyright (c) 2015-2025 Google, Inc. All rights reserved. |
| * Copyright (c) 2003-2009 VMware, 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 VMware, 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 VMWARE, INC. 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. |
| */ |
| |
| /* Copyright (c) 2003-2007 Determina Corp. */ |
| /* Copyright (c) 2001-2003 Massachusetts Institute of Technology */ |
| |
| /* |
| * stackdump.c - support for dumping stack trace of DynamoRIO |
| * |
| * based on code from David Spuler and idea from Castor Fu |
| * basic idea: |
| * fork off a process which core dumps then run a debugger on it. |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <sys/file.h> |
| #include <sys/wait.h> |
| |
| #include "../globals.h" |
| #include "os_private.h" |
| #ifdef LINUX |
| # include "include/syscall.h" |
| #else |
| # include <sys/syscall.h> |
| #endif |
| |
| #define DEBUGGER "gdb" |
| /* add -q to suppress gdb copyright notice */ |
| #define QUIET_MODE "-q" |
| #define TEMPORARY_FILENAME "/tmp/dynamorio.stackdump" |
| #define CORE_NAME "core" |
| |
| #define VERBOSE 0 |
| |
| /* use gdb -x <file> -batch, or redirect stdin? |
| * redirecting stdin has issues when where is multi-page, but |
| * batch will wait for stdin on multi-page, so we solve that |
| * by adding extra <enter>s in our temp file |
| */ |
| #define BATCH_MODE 0 |
| #if BATCH_MODE |
| # define DEBUGGER_COMMAND "where\nquit\n" |
| #else |
| /* XXX: want to have some <enter>s to handle multi-page, |
| * but don't want to repeat where cmd, so I use pwd, which is |
| * useful. Hopefully two pages is enough. |
| */ |
| # define DEBUGGER_COMMAND "where\npwd\nquit\n" |
| #endif |
| |
| #ifndef WAIT_ANY |
| /* WAIT_ANY is not defined in Android system */ |
| # define WAIT_ANY (-1) /* Any process. */ |
| #endif |
| |
| pid_t |
| wait_syscall(int *status) |
| { |
| return dynamorio_syscall(SYSNUM_NO_CANCEL(SYS_wait4), 4, WAIT_ANY, status, 0, NULL); |
| } |
| |
| static int |
| execve_syscall(const char *exe, const char **argv, char **envp) |
| { |
| return dynamorio_syscall(SYS_execve, 3, exe, argv, envp); |
| } |
| |
| int |
| fork_syscall(void) |
| { |
| #if FORK_BROKEN_CASE_4967 |
| /* XXX: SYS_fork on dereksha is creating a child whose pid is |
| * same as parent but has a different tid, and the abort() to dump |
| * core kills the parent process -- looks just like a separate |
| * thread, not a separate process! |
| * |
| * When I use glibc fork() I get the proper behavior. |
| * glibc 2.3.3 fork() calls clone() with flags = 0x01200011 |
| * == CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, which matches |
| * glibc-2.3.3-200405070341/nptl/sysdeps/unix/sysv/linux/i386/fork.c's |
| * ARCH_FORK, which is what glibc uses, so why doesn't SYS_fork do |
| * same thing? Instead it simply sets SIGCHLD and not the CLONE_* flags. |
| * (see /usr/src/linux/arch/i386/kernel/process.c). But, trying the CLONE_* flags |
| * doesn't do the trick -- libc fork() is doing something extra, and |
| * glibc-2.3.3-200405070341/nptl/sysdeps/unix/sysv/linux/fork.c's version of it |
| * shows it's doing some funny tricks with the pid! |
| * |
| * Once figure it out, need to have dynamic check for threading version to know |
| * what to do. |
| */ |
| # if 0 |
| /* from /usr/include/bits/sched.h ifdef __USE_MISC: */ |
| # define CLONE_CHILD_CLEARTID \ |
| 0x00200000 /* Register exit futex and memory \ |
| * location to clear. */ |
| # define CLONE_CHILD_SETTID \ |
| 0x01000000 /* Store TID in userlevel buffer in \ |
| * the child. */ |
| /* i386/fork.c pass a 5th arg, &THREAD_SELF->tid, is it needed for SETTID? */ |
| static uint tid; |
| return dynamorio_syscall(SYS_clone, 4/* 5 */, |
| /* flags, newsp (if 0 -> cur esp), parent_tidptr, |
| * child_tidptr, something! */ |
| CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, |
| 0, NULL, NULL /*, &tid*/); |
| # else |
| /* my workaround for now: just use libc -- binaries won't be back-compatible though */ |
| return fork(); |
| # endif |
| #else |
| # ifdef SYS_fork |
| return dynamorio_syscall(SYS_fork, 0); |
| # else |
| return dynamorio_syscall(SYS_clone, 5, SIGCHLD, NULL, NULL, NULL, NULL); |
| # endif |
| #endif |
| } |
| |
| /*-----------------------------------------------------------------------*/ |
| /* Procedure: |
| 1. Fork off a child process that dumps core; creates the "core" file |
| 2. Fork off a 2nd child process which: |
| 2a. Creates a temporary file of input commands for the debugger |
| 2b. Redirects stdin from this temporary file |
| 2c. Executes the debugger using the redirected input commands |
| */ |
| /*-----------------------------------------------------------------------*/ |
| |
| void |
| d_r_stackdump(void) |
| { |
| int pid, core_pid; |
| /* get name now -- will be same for children */ |
| char *exec_name = get_application_name(); |
| char core_name[128]; |
| char tmp_name[128]; |
| snprintf(tmp_name, 128, "%s.%d", TEMPORARY_FILENAME, get_process_id()); |
| |
| #ifdef VMX86_SERVER |
| if (os_in_vmkernel_userworld()) { |
| return; /* no local gdb, no multithreaded fork */ |
| } |
| #endif |
| |
| #if VERBOSE |
| SYSLOG_INTERNAL_ERROR("about to fork parent %d to dump core", get_process_id()); |
| #endif |
| |
| /* Fork a child to dump core */ |
| pid = fork_syscall(); |
| if (pid == 0) { /* child */ |
| #if VERBOSE |
| SYSLOG_INTERNAL_ERROR("about to dump core in process %d parent %d thread " TIDFMT |
| "", |
| get_process_id(), get_parent_id(), d_r_get_thread_id()); |
| #endif |
| /* We used to use abort here, but that had lots of complications with |
| * pthreads and libc, so now we just dereference NULL. |
| */ |
| if (!set_default_signal_action(SIGSEGV)) { |
| SYSLOG_INTERNAL_ERROR("ERROR in setting handler"); |
| exit_process_syscall(1); |
| } |
| *(volatile int *)NULL = 0; |
| #if VERBOSE |
| SYSLOG_INTERNAL_ERROR("about to exit process %d", get_process_id()); |
| #endif |
| exit_process_syscall(0); |
| } else if (pid == -1) { |
| SYSLOG_INTERNAL_ERROR("ERROR: could not fork to dump core"); |
| exit_process_syscall(1); |
| } |
| #if VERBOSE |
| SYSLOG_INTERNAL_ERROR("parent %d %d waiting for child %d", get_process_id(), |
| d_r_get_thread_id(), pid); |
| #endif |
| /* Parent continues */ |
| core_pid = pid; |
| while (wait_syscall(NULL) != pid) { |
| /* wait for core to be dumped */ |
| } |
| |
| #if VERBOSE |
| SYSLOG_INTERNAL_ERROR("about to fork 2nd child to run gdb"); |
| #endif |
| /* Fork a 2nd child to run gdb */ |
| pid = fork_syscall(); |
| if (pid == 0) { /* child */ |
| file_t fp; |
| int fd; |
| const char *argv[16]; |
| int i; |
| |
| /* Open a temporary file for the input: the "where" command */ |
| fp = os_open(tmp_name, OS_OPEN_REQUIRE_NEW | OS_OPEN_WRITE); |
| os_write(fp, DEBUGGER_COMMAND, strlen(DEBUGGER_COMMAND)); |
| os_close(fp); |
| |
| fd = open_syscall(tmp_name, O_RDONLY, 0); |
| if (fd < 0) { |
| SYSLOG_INTERNAL_ERROR("ERROR: open failed on temporary file"); |
| exit_process_syscall(1); |
| } |
| #if !BATCH_MODE |
| /* Redirect stdin from the temporary file */ |
| close_syscall(0); /* close stdin */ |
| dup_syscall(fd); /* replace file descriptor 0 with reference to temp file */ |
| #endif |
| close_syscall(fd); /* close the other reference to temporary file */ |
| |
| /* Find the core file */ |
| strncpy(core_name, CORE_NAME, 127); |
| core_name[127] = '\0'; /* if max no null */ |
| fd = open_syscall(core_name, O_RDONLY, 0); |
| if (fd < 0) { |
| snprintf(core_name, 128, "%s.%d", CORE_NAME, core_pid); |
| SYSLOG_INTERNAL_ERROR("core not found, trying %s", core_name); |
| fd = open_syscall(core_name, O_RDONLY, 0); |
| if (fd < 0) { |
| SYSLOG_INTERNAL_ERROR("ERROR: no core file found!"); |
| exit_process_syscall(1); |
| } |
| } |
| close_syscall(fd); |
| |
| /* avoid running the debugger under us! |
| * XXX: just remove our libraries, instead of entire env var? |
| */ |
| unsetenv("LD_PRELOAD"); |
| |
| SYSLOG_INTERNAL_ERROR("-------------------------------------------"); |
| SYSLOG_INTERNAL_ERROR("stackdump: --- now running the debugger ---"); |
| SYSLOG_INTERNAL_ERROR("%s %s %s %s", DEBUGGER, QUIET_MODE, exec_name, core_name); |
| SYSLOG_INTERNAL_ERROR("-------------------------------------------"); |
| i = 0; |
| /* We rely on /usr/bin/env to do the PATH search for gdb on our behalf. |
| */ |
| argv[i++] = "/usr/bin/env"; |
| argv[i++] = DEBUGGER; |
| argv[i++] = QUIET_MODE; |
| #if BATCH_MODE |
| argv[i++] = "-x"; |
| argv[i++] = tmp_name; |
| argv[i++] = "-batch"; |
| #endif |
| argv[i++] = exec_name; |
| argv[i++] = core_name; |
| argv[i++] = NULL; |
| DEBUG_DECLARE(int execve_errno =) |
| execve_syscall("/usr/bin/env", argv, our_environ); |
| SYSLOG_INTERNAL_ERROR("ERROR: execve failed for debugger: %d", -execve_errno); |
| exit_process_syscall(1); |
| } else if (pid == -1) { |
| SYSLOG_INTERNAL_ERROR("ERROR: could not fork to run debugger"); |
| exit_process_syscall(1); |
| } |
| /* Parent continues */ |
| /* while(wait(NULL)>0) waits for all children, and could hang, so: */ |
| while (wait_syscall(NULL) != pid) { |
| /* empty loop */ |
| } |
| |
| /* Wait for these children to complete before returning */ |
| |
| while (wait_syscall(NULL) > 0) { |
| /* empty loop */ |
| } |
| |
| os_delete_file(tmp_name); /* clean up the temporary file */ |
| SYSLOG_INTERNAL_ERROR("-------------------------------------------"); |
| } |