378 lines
9.1 KiB
C
378 lines
9.1 KiB
C
/**********************************************************************
|
|
proxy.c
|
|
|
|
Copyright (C) 1999 Lars Brinkhoff. See the file COPYING for licensing
|
|
terms and conditions.
|
|
|
|
Jeff Dike (jdike@karaya.com) : Modified for integration into uml
|
|
**********************************************************************/
|
|
|
|
/* XXX This file shouldn't refer to CONFIG_* */
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <termios.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ioctl.h>
|
|
#include <asm/unistd.h>
|
|
#include "ptrace_user.h"
|
|
|
|
#include "ptproxy.h"
|
|
#include "sysdep.h"
|
|
#include "wait.h"
|
|
|
|
#include "user_util.h"
|
|
#include "user.h"
|
|
#include "os.h"
|
|
#include "tempfile.h"
|
|
|
|
static int debugger_wait(debugger_state *debugger, int *status, int options,
|
|
int (*syscall)(debugger_state *debugger, pid_t child),
|
|
int (*normal_return)(debugger_state *debugger,
|
|
pid_t unused),
|
|
int (*wait_return)(debugger_state *debugger,
|
|
pid_t unused))
|
|
{
|
|
if(debugger->real_wait){
|
|
debugger->handle_trace = normal_return;
|
|
syscall_continue(debugger->pid);
|
|
debugger->real_wait = 0;
|
|
return(1);
|
|
}
|
|
debugger->wait_status_ptr = status;
|
|
debugger->wait_options = options;
|
|
if((debugger->debugee != NULL) && debugger->debugee->event){
|
|
syscall_continue(debugger->pid);
|
|
wait_for_stop(debugger->pid, SIGTRAP, PTRACE_SYSCALL,
|
|
NULL);
|
|
(*wait_return)(debugger, -1);
|
|
return(0);
|
|
}
|
|
else if(debugger->wait_options & WNOHANG){
|
|
syscall_cancel(debugger->pid, 0);
|
|
debugger->handle_trace = syscall;
|
|
return(0);
|
|
}
|
|
else {
|
|
syscall_pause(debugger->pid);
|
|
debugger->handle_trace = wait_return;
|
|
debugger->waiting = 1;
|
|
}
|
|
return(1);
|
|
}
|
|
|
|
/*
|
|
* Handle debugger trap, i.e. syscall.
|
|
*/
|
|
|
|
int debugger_syscall(debugger_state *debugger, pid_t child)
|
|
{
|
|
long arg1, arg2, arg3, arg4, arg5, result;
|
|
int syscall, ret = 0;
|
|
|
|
syscall = get_syscall(debugger->pid, &arg1, &arg2, &arg3, &arg4,
|
|
&arg5);
|
|
|
|
switch(syscall){
|
|
case __NR_execve:
|
|
/* execve never returns */
|
|
debugger->handle_trace = debugger_syscall;
|
|
break;
|
|
|
|
case __NR_ptrace:
|
|
if(debugger->debugee->pid != 0) arg2 = debugger->debugee->pid;
|
|
if(!debugger->debugee->in_context)
|
|
child = debugger->debugee->pid;
|
|
result = proxy_ptrace(debugger, arg1, arg2, arg3, arg4, child,
|
|
&ret);
|
|
syscall_cancel(debugger->pid, result);
|
|
debugger->handle_trace = debugger_syscall;
|
|
return(ret);
|
|
|
|
#ifdef __NR_waitpid
|
|
case __NR_waitpid:
|
|
#endif
|
|
case __NR_wait4:
|
|
if(!debugger_wait(debugger, (int *) arg2, arg3,
|
|
debugger_syscall, debugger_normal_return,
|
|
proxy_wait_return))
|
|
return(0);
|
|
break;
|
|
|
|
case __NR_kill:
|
|
if(!debugger->debugee->in_context)
|
|
child = debugger->debugee->pid;
|
|
if(arg1 == debugger->debugee->pid){
|
|
result = kill(child, arg2);
|
|
syscall_cancel(debugger->pid, result);
|
|
debugger->handle_trace = debugger_syscall;
|
|
return(0);
|
|
}
|
|
else debugger->handle_trace = debugger_normal_return;
|
|
break;
|
|
|
|
default:
|
|
debugger->handle_trace = debugger_normal_return;
|
|
}
|
|
|
|
syscall_continue(debugger->pid);
|
|
return(0);
|
|
}
|
|
|
|
/* Used by the tracing thread */
|
|
static debugger_state parent;
|
|
static int parent_syscall(debugger_state *debugger, int pid);
|
|
|
|
int init_parent_proxy(int pid)
|
|
{
|
|
parent = ((debugger_state) { .pid = pid,
|
|
.wait_options = 0,
|
|
.wait_status_ptr = NULL,
|
|
.waiting = 0,
|
|
.real_wait = 0,
|
|
.expecting_child = 0,
|
|
.handle_trace = parent_syscall,
|
|
.debugee = NULL } );
|
|
return(0);
|
|
}
|
|
|
|
int parent_normal_return(debugger_state *debugger, pid_t unused)
|
|
{
|
|
debugger->handle_trace = parent_syscall;
|
|
syscall_continue(debugger->pid);
|
|
return(0);
|
|
}
|
|
|
|
static int parent_syscall(debugger_state *debugger, int pid)
|
|
{
|
|
long arg1, arg2, arg3, arg4, arg5;
|
|
int syscall;
|
|
|
|
syscall = get_syscall(pid, &arg1, &arg2, &arg3, &arg4, &arg5);
|
|
|
|
if((syscall == __NR_wait4)
|
|
#ifdef __NR_waitpid
|
|
|| (syscall == __NR_waitpid)
|
|
#endif
|
|
){
|
|
debugger_wait(&parent, (int *) arg2, arg3, parent_syscall,
|
|
parent_normal_return, parent_wait_return);
|
|
}
|
|
else ptrace(PTRACE_SYSCALL, pid, 0, 0);
|
|
return(0);
|
|
}
|
|
|
|
int debugger_normal_return(debugger_state *debugger, pid_t unused)
|
|
{
|
|
debugger->handle_trace = debugger_syscall;
|
|
syscall_continue(debugger->pid);
|
|
return(0);
|
|
}
|
|
|
|
void debugger_cancelled_return(debugger_state *debugger, int result)
|
|
{
|
|
debugger->handle_trace = debugger_syscall;
|
|
syscall_set_result(debugger->pid, result);
|
|
syscall_continue(debugger->pid);
|
|
}
|
|
|
|
/* Used by the tracing thread */
|
|
static debugger_state debugger;
|
|
static debugee_state debugee;
|
|
|
|
void init_proxy (pid_t debugger_pid, int stopped, int status)
|
|
{
|
|
debugger.pid = debugger_pid;
|
|
debugger.handle_trace = debugger_syscall;
|
|
debugger.debugee = &debugee;
|
|
debugger.waiting = 0;
|
|
debugger.real_wait = 0;
|
|
debugger.expecting_child = 0;
|
|
|
|
debugee.pid = 0;
|
|
debugee.traced = 0;
|
|
debugee.stopped = stopped;
|
|
debugee.event = 0;
|
|
debugee.zombie = 0;
|
|
debugee.died = 0;
|
|
debugee.wait_status = status;
|
|
debugee.in_context = 1;
|
|
}
|
|
|
|
int debugger_proxy(int status, int pid)
|
|
{
|
|
int ret = 0, sig;
|
|
|
|
if(WIFSTOPPED(status)){
|
|
sig = WSTOPSIG(status);
|
|
if (sig == SIGTRAP)
|
|
ret = (*debugger.handle_trace)(&debugger, pid);
|
|
|
|
else if(sig == SIGCHLD){
|
|
if(debugger.expecting_child){
|
|
ptrace(PTRACE_SYSCALL, debugger.pid, 0, sig);
|
|
debugger.expecting_child = 0;
|
|
}
|
|
else if(debugger.waiting)
|
|
real_wait_return(&debugger);
|
|
else {
|
|
ptrace(PTRACE_SYSCALL, debugger.pid, 0, sig);
|
|
debugger.real_wait = 1;
|
|
}
|
|
}
|
|
else ptrace(PTRACE_SYSCALL, debugger.pid, 0, sig);
|
|
}
|
|
else if(WIFEXITED(status)){
|
|
tracer_panic("debugger (pid %d) exited with status %d",
|
|
debugger.pid, WEXITSTATUS(status));
|
|
}
|
|
else if(WIFSIGNALED(status)){
|
|
tracer_panic("debugger (pid %d) exited with signal %d",
|
|
debugger.pid, WTERMSIG(status));
|
|
}
|
|
else {
|
|
tracer_panic("proxy got unknown status (0x%x) on debugger "
|
|
"(pid %d)", status, debugger.pid);
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
void child_proxy(pid_t pid, int status)
|
|
{
|
|
debugee.event = 1;
|
|
debugee.wait_status = status;
|
|
|
|
if(WIFSTOPPED(status)){
|
|
debugee.stopped = 1;
|
|
debugger.expecting_child = 1;
|
|
kill(debugger.pid, SIGCHLD);
|
|
}
|
|
else if(WIFEXITED(status) || WIFSIGNALED(status)){
|
|
debugee.zombie = 1;
|
|
debugger.expecting_child = 1;
|
|
kill(debugger.pid, SIGCHLD);
|
|
}
|
|
else panic("proxy got unknown status (0x%x) on child (pid %d)",
|
|
status, pid);
|
|
}
|
|
|
|
void debugger_parent_signal(int status, int pid)
|
|
{
|
|
int sig;
|
|
|
|
if(WIFSTOPPED(status)){
|
|
sig = WSTOPSIG(status);
|
|
if(sig == SIGTRAP) (*parent.handle_trace)(&parent, pid);
|
|
else ptrace(PTRACE_SYSCALL, pid, 0, sig);
|
|
}
|
|
}
|
|
|
|
void fake_child_exit(void)
|
|
{
|
|
int status, pid;
|
|
|
|
child_proxy(1, W_EXITCODE(0, 0));
|
|
while(debugger.waiting == 1){
|
|
CATCH_EINTR(pid = waitpid(debugger.pid, &status, WUNTRACED));
|
|
if(pid != debugger.pid){
|
|
printk("fake_child_exit - waitpid failed, "
|
|
"errno = %d\n", errno);
|
|
return;
|
|
}
|
|
debugger_proxy(status, debugger.pid);
|
|
}
|
|
CATCH_EINTR(pid = waitpid(debugger.pid, &status, WUNTRACED));
|
|
if(pid != debugger.pid){
|
|
printk("fake_child_exit - waitpid failed, "
|
|
"errno = %d\n", errno);
|
|
return;
|
|
}
|
|
if(ptrace(PTRACE_DETACH, debugger.pid, 0, SIGCONT) < 0)
|
|
printk("fake_child_exit - PTRACE_DETACH failed, errno = %d\n",
|
|
errno);
|
|
}
|
|
|
|
char gdb_init_string[] =
|
|
"att 1 \n\
|
|
b panic \n\
|
|
b stop \n\
|
|
handle SIGWINCH nostop noprint pass \n\
|
|
";
|
|
|
|
int start_debugger(char *prog, int startup, int stop, int *fd_out)
|
|
{
|
|
int slave, child;
|
|
|
|
slave = open_gdb_chan();
|
|
child = fork();
|
|
if(child == 0){
|
|
char *tempname = NULL;
|
|
int fd;
|
|
|
|
if(setsid() < 0) perror("setsid");
|
|
if((dup2(slave, 0) < 0) || (dup2(slave, 1) < 0) ||
|
|
(dup2(slave, 2) < 0)){
|
|
printk("start_debugger : dup2 failed, errno = %d\n",
|
|
errno);
|
|
exit(1);
|
|
}
|
|
if(ioctl(0, TIOCSCTTY, 0) < 0){
|
|
printk("start_debugger : TIOCSCTTY failed, "
|
|
"errno = %d\n", errno);
|
|
exit(1);
|
|
}
|
|
if(tcsetpgrp (1, os_getpid()) < 0){
|
|
printk("start_debugger : tcsetpgrp failed, "
|
|
"errno = %d\n", errno);
|
|
#ifdef notdef
|
|
exit(1);
|
|
#endif
|
|
}
|
|
fd = make_tempfile("/tmp/gdb_init-XXXXXX", &tempname, 0);
|
|
if(fd < 0){
|
|
printk("start_debugger : make_tempfile failed,"
|
|
"err = %d\n", -fd);
|
|
exit(1);
|
|
}
|
|
os_write_file(fd, gdb_init_string, sizeof(gdb_init_string) - 1);
|
|
if(startup){
|
|
if(stop){
|
|
os_write_file(fd, "b start_kernel\n",
|
|
strlen("b start_kernel\n"));
|
|
}
|
|
os_write_file(fd, "c\n", strlen("c\n"));
|
|
}
|
|
if(ptrace(PTRACE_TRACEME, 0, 0, 0) < 0){
|
|
printk("start_debugger : PTRACE_TRACEME failed, "
|
|
"errno = %d\n", errno);
|
|
exit(1);
|
|
}
|
|
execlp("gdb", "gdb", "--command", tempname, prog, NULL);
|
|
printk("start_debugger : exec of gdb failed, errno = %d\n",
|
|
errno);
|
|
}
|
|
if(child < 0){
|
|
printk("start_debugger : fork for gdb failed, errno = %d\n",
|
|
errno);
|
|
return(-1);
|
|
}
|
|
*fd_out = slave;
|
|
return(child);
|
|
}
|
|
|
|
/*
|
|
* Overrides for Emacs so that we follow Linus's tabbing style.
|
|
* Emacs will notice this stuff at the end of the file and automatically
|
|
* adjust the settings for this buffer only. This must remain at the end
|
|
* of the file.
|
|
* ---------------------------------------------------------------------------
|
|
* Local variables:
|
|
* c-file-style: "linux"
|
|
* End:
|
|
*/
|