📄 linux-low.c
字号:
/* Low level interface to ptrace, for the remote server for GDB. Copyright 1995, 1996, 1998, 1999, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */#include "server.h"#include "linux-low.h"#include <sys/wait.h>#include <stdio.h>#include <sys/param.h>#include <sys/dir.h>#include <sys/ptrace.h>#include <sys/user.h>#include <signal.h>#include <sys/ioctl.h>#include <fcntl.h>#include <string.h>#include <stdlib.h>#include <unistd.h>#include <errno.h>#include <sys/syscall.h>/* ``all_threads'' is keyed by the LWP ID - it should be the thread ID instead, however. This requires changing the ID in place when we go from !using_threads to using_threads, immediately. ``all_processes'' is keyed by the process ID - which on Linux is (presently) the same as the LWP ID. */struct inferior_list all_processes;/* FIXME this is a bit of a hack, and could be removed. */int stopping_threads;/* FIXME make into a target method? */int using_threads;static void linux_resume_one_process (struct inferior_list_entry *entry, int step, int signal);static void linux_resume (struct thread_resume *resume_info);static void stop_all_processes (void);static int linux_wait_for_event (struct thread_info *child);struct pending_signals{ int signal; struct pending_signals *prev;};#define PTRACE_ARG3_TYPE long#define PTRACE_XFER_TYPE long#ifdef HAVE_LINUX_REGSETSstatic int use_regsets_p = 1;#endifint debug_threads = 0;#define pid_of(proc) ((proc)->head.id)/* FIXME: Delete eventually. */#define inferior_pid (pid_of (get_thread_process (current_inferior)))/* This function should only be called if the process got a SIGTRAP. The SIGTRAP could mean several things. On i386, where decr_pc_after_break is non-zero: If we were single-stepping this process using PTRACE_SINGLESTEP, we will get only the one SIGTRAP (even if the instruction we stepped over was a breakpoint). The value of $eip will be the next instruction. If we continue the process using PTRACE_CONT, we will get a SIGTRAP when we hit a breakpoint. The value of $eip will be the instruction after the breakpoint (i.e. needs to be decremented). If we report the SIGTRAP to GDB, we must also report the undecremented PC. If we cancel the SIGTRAP, we must resume at the decremented PC. (Presumably, not yet tested) On a non-decr_pc_after_break machine with hardware or kernel single-step: If we single-step over a breakpoint instruction, our PC will point at the following instruction. If we continue and hit a breakpoint instruction, our PC will point at the breakpoint instruction. */static CORE_ADDRget_stop_pc (void){ CORE_ADDR stop_pc = (*the_low_target.get_pc) (); if (get_thread_process (current_inferior)->stepping) return stop_pc; else return stop_pc - the_low_target.decr_pc_after_break;}static void *add_process (int pid){ struct process_info *process; process = (struct process_info *) malloc (sizeof (*process)); memset (process, 0, sizeof (*process)); process->head.id = pid; /* Default to tid == lwpid == pid. */ process->tid = pid; process->lwpid = pid; add_inferior_to_list (&all_processes, &process->head); return process;}/* Start an inferior process and returns its pid. ALLARGS is a vector of program-name and args. */static intlinux_create_inferior (char *program, char **allargs){ void *new_process; int pid; pid = fork (); if (pid < 0) perror_with_name ("fork"); if (pid == 0) { ptrace (PTRACE_TRACEME, 0, 0, 0); signal (__SIGRTMIN + 1, SIG_DFL); setpgid (0, 0); execv (program, allargs); fprintf (stderr, "Cannot exec %s: %s.\n", program, strerror (errno)); fflush (stderr); _exit (0177); } new_process = add_process (pid); add_thread (pid, new_process); return pid;}/* Attach to an inferior process. */voidlinux_attach_lwp (int pid, int tid){ struct process_info *new_process; if (ptrace (PTRACE_ATTACH, pid, 0, 0) != 0) { fprintf (stderr, "Cannot attach to process %d: %s (%d)\n", pid, strerror (errno), errno); fflush (stderr); /* If we fail to attach to an LWP, just return. */ if (!using_threads) _exit (0177); return; } new_process = (struct process_info *) add_process (pid); add_thread (tid, new_process); /* The next time we wait for this LWP we'll see a SIGSTOP as PTRACE_ATTACH brings it to a halt. We should ignore that SIGSTOP and resume the process (unless this is the first process, in which case the flag will be cleared in linux_attach). On the other hand, if we are currently trying to stop all threads, we should treat the new thread as if we had sent it a SIGSTOP. This works because we are guaranteed that add_process added us to the end of the list, and so the new thread has not yet reached wait_for_sigstop (but will). */ if (! stopping_threads) new_process->stop_expected = 1;}intlinux_attach (int pid){ struct process_info *process; linux_attach_lwp (pid, pid); /* Don't ignore the initial SIGSTOP if we just attached to this process. */ process = (struct process_info *) find_inferior_id (&all_processes, pid); process->stop_expected = 0; return 0;}/* Kill the inferior process. Make us have no inferior. */static voidlinux_kill_one_process (struct inferior_list_entry *entry){ struct thread_info *thread = (struct thread_info *) entry; struct process_info *process = get_thread_process (thread); int wstat; /* We avoid killing the first thread here, because of a Linux kernel (at least 2.6.0-test7 through 2.6.8-rc4) bug; if we kill the parent before the children get a chance to be reaped, it will remain a zombie forever. */ if (entry == all_threads.head) return; do { ptrace (PTRACE_KILL, pid_of (process), 0, 0); /* Make sure it died. The loop is most likely unnecessary. */ wstat = linux_wait_for_event (thread); } while (WIFSTOPPED (wstat));}static voidlinux_kill (void){ struct thread_info *thread = (struct thread_info *) all_threads.head; struct process_info *process = get_thread_process (thread); int wstat; for_each_inferior (&all_threads, linux_kill_one_process); /* See the comment in linux_kill_one_process. We did not kill the first thread in the list, so do so now. */ do { ptrace (PTRACE_KILL, pid_of (process), 0, 0); /* Make sure it died. The loop is most likely unnecessary. */ wstat = linux_wait_for_event (thread); } while (WIFSTOPPED (wstat));}static voidlinux_detach_one_process (struct inferior_list_entry *entry){ struct thread_info *thread = (struct thread_info *) entry; struct process_info *process = get_thread_process (thread); ptrace (PTRACE_DETACH, pid_of (process), 0, 0);}static voidlinux_detach (void){ for_each_inferior (&all_threads, linux_detach_one_process);}/* Return nonzero if the given thread is still alive. */static intlinux_thread_alive (int tid){ if (find_inferior_id (&all_threads, tid) != NULL) return 1; else return 0;}/* Return nonzero if this process stopped at a breakpoint which no longer appears to be inserted. Also adjust the PC appropriately to resume where the breakpoint used to be. */static intcheck_removed_breakpoint (struct process_info *event_child){ CORE_ADDR stop_pc; struct thread_info *saved_inferior; if (event_child->pending_is_breakpoint == 0) return 0; if (debug_threads) fprintf (stderr, "Checking for breakpoint.\n"); saved_inferior = current_inferior; current_inferior = get_process_thread (event_child); stop_pc = get_stop_pc (); /* If the PC has changed since we stopped, then we shouldn't do anything. This happens if, for instance, GDB handled the decr_pc_after_break subtraction itself. */ if (stop_pc != event_child->pending_stop_pc) { if (debug_threads) fprintf (stderr, "Ignoring, PC was changed.\n"); event_child->pending_is_breakpoint = 0; current_inferior = saved_inferior; return 0; } /* If the breakpoint is still there, we will report hitting it. */ if ((*the_low_target.breakpoint_at) (stop_pc)) { if (debug_threads) fprintf (stderr, "Ignoring, breakpoint is still present.\n"); current_inferior = saved_inferior; return 0; } if (debug_threads) fprintf (stderr, "Removed breakpoint.\n"); /* For decr_pc_after_break targets, here is where we perform the decrement. We go immediately from this function to resuming, and can not safely call get_stop_pc () again. */ if (the_low_target.set_pc != NULL) (*the_low_target.set_pc) (stop_pc); /* We consumed the pending SIGTRAP. */ event_child->pending_is_breakpoint = 0; event_child->status_pending_p = 0; event_child->status_pending = 0; current_inferior = saved_inferior; return 1;}/* Return 1 if this process has an interesting status pending. This function may silently resume an inferior process. */static intstatus_pending_p (struct inferior_list_entry *entry, void *dummy){ struct process_info *process = (struct process_info *) entry; if (process->status_pending_p) if (check_removed_breakpoint (process)) { /* This thread was stopped at a breakpoint, and the breakpoint is now gone. We were told to continue (or step...) all threads, so GDB isn't trying to single-step past this breakpoint. So instead of reporting the old SIGTRAP, pretend we got to the breakpoint just after it was removed instead of just before; resume the process. */ linux_resume_one_process (&process->head, 0, 0); return 0; } return process->status_pending_p;}static voidlinux_wait_for_process (struct process_info **childp, int *wstatp){ int ret; int to_wait_for = -1; if (*childp != NULL) to_wait_for = (*childp)->lwpid; while (1) { ret = waitpid (to_wait_for, wstatp, WNOHANG); if (ret == -1) { if (errno != ECHILD) perror_with_name ("waitpid"); } else if (ret > 0) break; ret = waitpid (to_wait_for, wstatp, WNOHANG | __WCLONE); if (ret == -1) { if (errno != ECHILD) perror_with_name ("waitpid (WCLONE)"); } else if (ret > 0) break; usleep (1000); } if (debug_threads && (!WIFSTOPPED (*wstatp) || (WSTOPSIG (*wstatp) != 32 && WSTOPSIG (*wstatp) != 33))) fprintf (stderr, "Got an event from %d (%x)\n", ret, *wstatp); if (to_wait_for == -1) *childp = (struct process_info *) find_inferior_id (&all_processes, ret); (*childp)->stopped = 1; (*childp)->pending_is_breakpoint = 0; if (debug_threads && WIFSTOPPED (*wstatp)) { current_inferior = (struct thread_info *) find_inferior_id (&all_threads, (*childp)->tid); /* For testing only; i386_stop_pc prints out a diagnostic. */ if (the_low_target.get_pc != NULL) get_stop_pc (); }}static intlinux_wait_for_event (struct thread_info *child){ CORE_ADDR stop_pc; struct process_info *event_child; int wstat; /* Check for a process with a pending status. */ /* It is possible that the user changed the pending task's registers since it stopped. We correctly handle the change of PC if we hit a breakpoint (in check_removed_breakpoint); signals should be reported anyway. */ if (child == NULL) { event_child = (struct process_info *) find_inferior (&all_processes, status_pending_p, NULL); if (debug_threads && event_child) fprintf (stderr, "Got a pending child %d\n", event_child->lwpid); } else { event_child = get_thread_process (child); if (event_child->status_pending_p && check_removed_breakpoint (event_child)) event_child = NULL; } if (event_child != NULL) { if (event_child->status_pending_p) { if (debug_threads) fprintf (stderr, "Got an event from pending child %d (%04x)\n", event_child->lwpid, event_child->status_pending); wstat = event_child->status_pending; event_child->status_pending_p = 0; event_child->status_pending = 0; current_inferior = get_process_thread (event_child); return wstat; } } /* We only enter this loop if no process has a pending wait status. Thus any action taken in response to a wait status inside this loop is responding as soon as we detect the status, not after any pending events. */ while (1) { if (child == NULL) event_child = NULL; else event_child = get_thread_process (child); linux_wait_for_process (&event_child, &wstat); if (event_child == NULL) error ("event from unknown child"); current_inferior = (struct thread_info *) find_inferior_id (&all_threads, event_child->tid); if (using_threads) { /* Check for thread exit. */ if (! WIFSTOPPED (wstat)) { if (debug_threads) fprintf (stderr, "Thread %d (LWP %d) exiting\n", event_child->tid, event_child->head.id); /* If the last thread is exiting, just return. */ if (all_threads.head == all_threads.tail) return wstat; dead_thread_notify (event_child->tid);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -