blob: 184a2483e516fd520efb970b591e97e99b5e4475 [file] [log] [blame]
/* **********************************************************
* 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 <string.h>
#include <sys/syscall.h>
#include "../globals.h"
#include "os_private.h"
#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
/* FIXME: 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
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
/* FIXME: 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
return dynamorio_syscall(SYS_fork, 0);
#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
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(), 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(),
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;
int execve_errno;
/* 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!
* FIXME: 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;
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("-------------------------------------------");
}