Index: gdb/common/linux-ptrace.c |
diff --git a/gdb/common/linux-ptrace.c b/gdb/common/linux-ptrace.c |
index ae17786eb7a6485c4dc7a3f59db9d5d2391caaf5..2b12a6812143d49682ccc091092a2dd92a8362b9 100644 |
--- a/gdb/common/linux-ptrace.c |
+++ b/gdb/common/linux-ptrace.c |
@@ -1,5 +1,5 @@ |
/* Linux-specific ptrace manipulation routines. |
- Copyright (C) 2012 Free Software Foundation, Inc. |
+ Copyright (C) 2012-2013 Free Software Foundation, Inc. |
This file is part of GDB. |
@@ -20,13 +20,22 @@ |
#include "server.h" |
#else |
#include "defs.h" |
-#include "gdb_string.h" |
+#include <string.h> |
#endif |
#include "linux-ptrace.h" |
#include "linux-procfs.h" |
+#include "nat/linux-waitpid.h" |
#include "buffer.h" |
#include "gdb_assert.h" |
+#include "gdb_wait.h" |
+ |
+#include <stdint.h> |
+ |
+/* Stores the currently supported ptrace options. A value of |
+ -1 means we did not check for features yet. A value of 0 means |
+ there are no supported features. */ |
+static int current_ptrace_options = -1; |
/* Find all possible reasons we could fail to attach PID and append these |
newline terminated reason strings to initialized BUFFER. '\0' termination |
@@ -57,8 +66,6 @@ extern void (linux_ptrace_test_ret_to_nx_instr) (void); |
#include <sys/reg.h> |
#include <sys/mman.h> |
#include <signal.h> |
-#include <sys/wait.h> |
-#include <stdint.h> |
#endif /* defined __i386__ || defined __x86_64__ */ |
@@ -74,7 +81,7 @@ linux_ptrace_test_ret_to_nx (void) |
pid_t child, got_pid; |
gdb_byte *return_address, *pc; |
long l; |
- int status; |
+ int status, kill_status; |
return_address = mmap (NULL, 2, PROT_READ | PROT_WRITE, |
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
@@ -97,7 +104,8 @@ linux_ptrace_test_ret_to_nx (void) |
return; |
case 0: |
- l = ptrace (PTRACE_TRACEME, 0, NULL, NULL); |
+ l = ptrace (PTRACE_TRACEME, 0, (PTRACE_TYPE_ARG3) NULL, |
+ (PTRACE_TYPE_ARG4) NULL); |
if (l != 0) |
warning (_("linux_ptrace_test_ret_to_nx: Cannot PTRACE_TRACEME: %s"), |
strerror (errno)); |
@@ -114,7 +122,8 @@ linux_ptrace_test_ret_to_nx (void) |
".globl linux_ptrace_test_ret_to_nx_instr;" |
"linux_ptrace_test_ret_to_nx_instr:" |
"ret" |
- : : "r" (return_address) : "%rsp", "memory"); |
+ : : "r" ((uint64_t) (uintptr_t) return_address) |
+ : "%rsp", "memory"); |
#else |
# error "!__i386__ && !__x86_64__" |
#endif |
@@ -162,9 +171,11 @@ linux_ptrace_test_ret_to_nx (void) |
errno = 0; |
#if defined __i386__ |
- l = ptrace (PTRACE_PEEKUSER, child, (void *) (uintptr_t) (EIP * 4), NULL); |
+ l = ptrace (PTRACE_PEEKUSER, child, (PTRACE_TYPE_ARG3) (uintptr_t) (EIP * 4), |
+ (PTRACE_TYPE_ARG4) NULL); |
#elif defined __x86_64__ |
- l = ptrace (PTRACE_PEEKUSER, child, (void *) (uintptr_t) (RIP * 8), NULL); |
+ l = ptrace (PTRACE_PEEKUSER, child, (PTRACE_TYPE_ARG3) (uintptr_t) (RIP * 8), |
+ (PTRACE_TYPE_ARG4) NULL); |
#else |
# error "!__i386__ && !__x86_64__" |
#endif |
@@ -176,32 +187,25 @@ linux_ptrace_test_ret_to_nx (void) |
} |
pc = (void *) (uintptr_t) l; |
- if (ptrace (PTRACE_KILL, child, NULL, NULL) != 0) |
+ kill (child, SIGKILL); |
+ ptrace (PTRACE_KILL, child, (PTRACE_TYPE_ARG3) NULL, |
+ (PTRACE_TYPE_ARG4) NULL); |
+ |
+ errno = 0; |
+ got_pid = waitpid (child, &kill_status, 0); |
+ if (got_pid != child) |
{ |
- warning (_("linux_ptrace_test_ret_to_nx: Cannot PTRACE_KILL: %s"), |
- strerror (errno)); |
+ warning (_("linux_ptrace_test_ret_to_nx: " |
+ "PTRACE_KILL waitpid returned %ld: %s"), |
+ (long) got_pid, strerror (errno)); |
return; |
} |
- else |
+ if (!WIFSIGNALED (kill_status)) |
{ |
- int kill_status; |
- |
- errno = 0; |
- got_pid = waitpid (child, &kill_status, 0); |
- if (got_pid != child) |
- { |
- warning (_("linux_ptrace_test_ret_to_nx: " |
- "PTRACE_KILL waitpid returned %ld: %s"), |
- (long) got_pid, strerror (errno)); |
- return; |
- } |
- if (!WIFSIGNALED (kill_status)) |
- { |
- warning (_("linux_ptrace_test_ret_to_nx: " |
- "PTRACE_KILL status %d is not WIFSIGNALED!"), |
- status); |
- return; |
- } |
+ warning (_("linux_ptrace_test_ret_to_nx: " |
+ "PTRACE_KILL status %d is not WIFSIGNALED!"), |
+ status); |
+ return; |
} |
/* + 1 is there as x86* stops after the 'int3' instruction. */ |
@@ -223,11 +227,307 @@ linux_ptrace_test_ret_to_nx (void) |
"address %p nor is the return instruction %p!"), |
pc, return_address, &linux_ptrace_test_ret_to_nx_instr); |
else |
- warning (_("Cannot call inferior functions, you have broken " |
- "Linux kernel i386 NX (non-executable pages) support!")); |
+ warning (_("Cannot call inferior functions on this system - " |
+ "Linux kernel with broken i386 NX (non-executable pages) " |
+ "support detected!")); |
#endif /* defined __i386__ || defined __x86_64__ */ |
} |
+/* Helper function to fork a process and make the child process call |
+ the function FUNCTION, passing CHILD_STACK as parameter. |
+ |
+ For MMU-less targets, clone is used instead of fork, and |
+ CHILD_STACK is used as stack space for the cloned child. If NULL, |
+ stack space is allocated via malloc (and subsequently passed to |
+ FUNCTION). For MMU targets, CHILD_STACK is ignored. */ |
+ |
+static int |
+linux_fork_to_function (gdb_byte *child_stack, void (*function) (gdb_byte *)) |
+{ |
+ int child_pid; |
+ |
+ /* Sanity check the function pointer. */ |
+ gdb_assert (function != NULL); |
+ |
+#if defined(__UCLIBC__) && defined(HAS_NOMMU) |
+#define STACK_SIZE 4096 |
+ |
+ if (child_stack == NULL) |
+ child_stack = xmalloc (STACK_SIZE * 4); |
+ |
+ /* Use CLONE_VM instead of fork, to support uClinux (no MMU). */ |
+#ifdef __ia64__ |
+ child_pid = __clone2 (function, child_stack, STACK_SIZE, |
+ CLONE_VM | SIGCHLD, child_stack + STACK_SIZE * 2); |
+#else /* !__ia64__ */ |
+ child_pid = clone (function, child_stack + STACK_SIZE, |
+ CLONE_VM | SIGCHLD, child_stack + STACK_SIZE * 2); |
+#endif /* !__ia64__ */ |
+#else /* !defined(__UCLIBC) && defined(HAS_NOMMU) */ |
+ child_pid = fork (); |
+ |
+ if (child_pid == 0) |
+ function (NULL); |
+#endif /* defined(__UCLIBC) && defined(HAS_NOMMU) */ |
+ |
+ if (child_pid == -1) |
+ perror_with_name (("fork")); |
+ |
+ return child_pid; |
+} |
+ |
+/* A helper function for linux_check_ptrace_features, called after |
+ the child forks a grandchild. */ |
+ |
+static void |
+linux_grandchild_function (gdb_byte *child_stack) |
+{ |
+ /* Free any allocated stack. */ |
+ xfree (child_stack); |
+ |
+ /* This code is only reacheable by the grandchild (child's child) |
+ process. */ |
+ _exit (0); |
+} |
+ |
+/* A helper function for linux_check_ptrace_features, called after |
+ the parent process forks a child. The child allows itself to |
+ be traced by its parent. */ |
+ |
+static void |
+linux_child_function (gdb_byte *child_stack) |
+{ |
+ ptrace (PTRACE_TRACEME, 0, (PTRACE_TYPE_ARG3) 0, (PTRACE_TYPE_ARG4) 0); |
+ kill (getpid (), SIGSTOP); |
+ |
+ /* Fork a grandchild. */ |
+ linux_fork_to_function (child_stack, linux_grandchild_function); |
+ |
+ /* This code is only reacheable by the child (grandchild's parent) |
+ process. */ |
+ _exit (0); |
+} |
+ |
+static void linux_test_for_tracesysgood (int child_pid); |
+static void linux_test_for_tracefork (int child_pid); |
+ |
+/* Determine ptrace features available on this target. */ |
+ |
+static void |
+linux_check_ptrace_features (void) |
+{ |
+ int child_pid, ret, status; |
+ |
+ /* Initialize the options. */ |
+ current_ptrace_options = 0; |
+ |
+ /* Fork a child so we can do some testing. The child will call |
+ linux_child_function and will get traced. The child will |
+ eventually fork a grandchild so we can test fork event |
+ reporting. */ |
+ child_pid = linux_fork_to_function (NULL, linux_child_function); |
+ |
+ ret = my_waitpid (child_pid, &status, 0); |
+ if (ret == -1) |
+ perror_with_name (("waitpid")); |
+ else if (ret != child_pid) |
+ error (_("linux_check_ptrace_features: waitpid: unexpected result %d."), |
+ ret); |
+ if (! WIFSTOPPED (status)) |
+ error (_("linux_check_ptrace_features: waitpid: unexpected status %d."), |
+ status); |
+ |
+ linux_test_for_tracesysgood (child_pid); |
+ |
+ linux_test_for_tracefork (child_pid); |
+ |
+ /* Clean things up and kill any pending children. */ |
+ do |
+ { |
+ ret = ptrace (PTRACE_KILL, child_pid, (PTRACE_TYPE_ARG3) 0, |
+ (PTRACE_TYPE_ARG4) 0); |
+ if (ret != 0) |
+ warning (_("linux_check_ptrace_features: failed to kill child")); |
+ my_waitpid (child_pid, &status, 0); |
+ } |
+ while (WIFSTOPPED (status)); |
+} |
+ |
+/* Determine if PTRACE_O_TRACESYSGOOD can be used to catch |
+ syscalls. */ |
+ |
+static void |
+linux_test_for_tracesysgood (int child_pid) |
+{ |
+#ifdef GDBSERVER |
+ /* gdbserver does not support PTRACE_O_TRACESYSGOOD. */ |
+#else |
+ int ret; |
+ |
+ ret = ptrace (PTRACE_SETOPTIONS, child_pid, (PTRACE_TYPE_ARG3) 0, |
+ (PTRACE_TYPE_ARG4) PTRACE_O_TRACESYSGOOD); |
+ if (ret == 0) |
+ current_ptrace_options |= PTRACE_O_TRACESYSGOOD; |
+#endif |
+} |
+ |
+/* Determine if PTRACE_O_TRACEFORK can be used to follow fork |
+ events. */ |
+ |
+static void |
+linux_test_for_tracefork (int child_pid) |
+{ |
+ int ret, status; |
+ long second_pid; |
+ |
+ /* First, set the PTRACE_O_TRACEFORK option. If this fails, we |
+ know for sure that it is not supported. */ |
+ ret = ptrace (PTRACE_SETOPTIONS, child_pid, (PTRACE_TYPE_ARG3) 0, |
+ (PTRACE_TYPE_ARG4) PTRACE_O_TRACEFORK); |
+ |
+ if (ret != 0) |
+ return; |
+ |
+#ifdef GDBSERVER |
+ /* gdbserver does not support PTRACE_O_TRACEVFORKDONE yet. */ |
+#else |
+ /* Check if the target supports PTRACE_O_TRACEVFORKDONE. */ |
+ ret = ptrace (PTRACE_SETOPTIONS, child_pid, (PTRACE_TYPE_ARG3) 0, |
+ (PTRACE_TYPE_ARG4) (PTRACE_O_TRACEFORK |
+ | PTRACE_O_TRACEVFORKDONE)); |
+ if (ret == 0) |
+ current_ptrace_options |= PTRACE_O_TRACEVFORKDONE; |
+#endif |
+ |
+ /* Setting PTRACE_O_TRACEFORK did not cause an error, however we |
+ don't know for sure that the feature is available; old |
+ versions of PTRACE_SETOPTIONS ignored unknown options. |
+ Therefore, we attach to the child process, use PTRACE_SETOPTIONS |
+ to enable fork tracing, and let it fork. If the process exits, |
+ we assume that we can't use PTRACE_O_TRACEFORK; if we get the |
+ fork notification, and we can extract the new child's PID, then |
+ we assume that we can. |
+ |
+ We do not explicitly check for vfork tracing here. It is |
+ assumed that vfork tracing is available whenever fork tracing |
+ is available. */ |
+ ret = ptrace (PTRACE_CONT, child_pid, (PTRACE_TYPE_ARG3) 0, |
+ (PTRACE_TYPE_ARG4) 0); |
+ if (ret != 0) |
+ warning (_("linux_test_for_tracefork: failed to resume child")); |
+ |
+ ret = my_waitpid (child_pid, &status, 0); |
+ |
+ /* Check if we received a fork event notification. */ |
+ if (ret == child_pid && WIFSTOPPED (status) |
+ && status >> 16 == PTRACE_EVENT_FORK) |
+ { |
+ /* We did receive a fork event notification. Make sure its PID |
+ is reported. */ |
+ second_pid = 0; |
+ ret = ptrace (PTRACE_GETEVENTMSG, child_pid, (PTRACE_TYPE_ARG3) 0, |
+ (PTRACE_TYPE_ARG4) &second_pid); |
+ if (ret == 0 && second_pid != 0) |
+ { |
+ int second_status; |
+ |
+ /* We got the PID from the grandchild, which means fork |
+ tracing is supported. */ |
+#ifdef GDBSERVER |
+ /* Do not enable all the options for now since gdbserver does not |
+ properly support them. This restriction will be lifted when |
+ gdbserver is augmented to support them. */ |
+ current_ptrace_options |= PTRACE_O_TRACECLONE; |
+#else |
+ current_ptrace_options |= PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK |
+ | PTRACE_O_TRACECLONE | PTRACE_O_TRACEEXEC; |
+ |
+ /* Do not enable PTRACE_O_TRACEEXIT until GDB is more prepared to |
+ support read-only process state. */ |
+#endif |
+ |
+ /* Do some cleanup and kill the grandchild. */ |
+ my_waitpid (second_pid, &second_status, 0); |
+ ret = ptrace (PTRACE_KILL, second_pid, (PTRACE_TYPE_ARG3) 0, |
+ (PTRACE_TYPE_ARG4) 0); |
+ if (ret != 0) |
+ warning (_("linux_test_for_tracefork: " |
+ "failed to kill second child")); |
+ my_waitpid (second_pid, &status, 0); |
+ } |
+ } |
+ else |
+ warning (_("linux_test_for_tracefork: unexpected result from waitpid " |
+ "(%d, status 0x%x)"), ret, status); |
+} |
+ |
+/* Enable reporting of all currently supported ptrace events. */ |
+ |
+void |
+linux_enable_event_reporting (pid_t pid) |
+{ |
+ /* Check if we have initialized the ptrace features for this |
+ target. If not, do it now. */ |
+ if (current_ptrace_options == -1) |
+ linux_check_ptrace_features (); |
+ |
+ /* Set the options. */ |
+ ptrace (PTRACE_SETOPTIONS, pid, (PTRACE_TYPE_ARG3) 0, |
+ (PTRACE_TYPE_ARG4) (uintptr_t) current_ptrace_options); |
+} |
+ |
+/* Returns non-zero if PTRACE_OPTIONS is contained within |
+ CURRENT_PTRACE_OPTIONS, therefore supported. Returns 0 |
+ otherwise. */ |
+ |
+static int |
+ptrace_supports_feature (int ptrace_options) |
+{ |
+ gdb_assert (current_ptrace_options >= 0); |
+ |
+ return ((current_ptrace_options & ptrace_options) == ptrace_options); |
+} |
+ |
+/* Returns non-zero if PTRACE_EVENT_FORK is supported by ptrace, |
+ 0 otherwise. Note that if PTRACE_EVENT_FORK is supported so is |
+ PTRACE_EVENT_CLONE, PTRACE_EVENT_EXEC and PTRACE_EVENT_VFORK, |
+ since they were all added to the kernel at the same time. */ |
+ |
+int |
+linux_supports_tracefork (void) |
+{ |
+ return ptrace_supports_feature (PTRACE_O_TRACEFORK); |
+} |
+ |
+/* Returns non-zero if PTRACE_EVENT_CLONE is supported by ptrace, |
+ 0 otherwise. Note that if PTRACE_EVENT_CLONE is supported so is |
+ PTRACE_EVENT_FORK, PTRACE_EVENT_EXEC and PTRACE_EVENT_VFORK, |
+ since they were all added to the kernel at the same time. */ |
+ |
+int |
+linux_supports_traceclone (void) |
+{ |
+ return ptrace_supports_feature (PTRACE_O_TRACECLONE); |
+} |
+ |
+/* Returns non-zero if PTRACE_O_TRACEVFORKDONE is supported by |
+ ptrace, 0 otherwise. */ |
+ |
+int |
+linux_supports_tracevforkdone (void) |
+{ |
+ return ptrace_supports_feature (PTRACE_O_TRACEVFORKDONE); |
+} |
+ |
+/* Returns non-zero if PTRACE_O_TRACESYSGOOD is supported by ptrace, |
+ 0 otherwise. */ |
+ |
+int |
+linux_supports_tracesysgood (void) |
+{ |
+ return ptrace_supports_feature (PTRACE_O_TRACESYSGOOD); |
+} |
+ |
/* Display possible problems on this system. Display them only once per GDB |
execution. */ |