]> granicus.if.org Git - strace/commitdiff
Check whether PTRACE_GET_SYSCALL_INFO is supported by the kernel
authorDmitry V. Levin <ldv@altlinux.org>
Wed, 21 Nov 2018 01:44:16 +0000 (01:44 +0000)
committerDmitry V. Levin <ldv@altlinux.org>
Thu, 13 Dec 2018 22:07:26 +0000 (22:07 +0000)
The result of this check is going to be used by subsequent commits.

* ptrace_syscall_info.c: New file.
* ptrace_syscall_info.h: Likewise.
* Makefile.am (strace_SOURCES): Add them.
* strace.c: Include "ptrace_syscall_info.h".
(init): Call test_ptrace_get_syscall_info.

Makefile.am
ptrace_syscall_info.c [new file with mode: 0644]
ptrace_syscall_info.h [new file with mode: 0644]
strace.c

index 0c9f3bca19b31dc79de4b87d955e779240c3bbbf..e13c925828c5e57d554abcf3355f7c11a8f4f1ce 100644 (file)
@@ -249,6 +249,8 @@ strace_SOURCES =    \
        process_vm.c    \
        ptp.c           \
        ptrace.h        \
+       ptrace_syscall_info.c \
+       ptrace_syscall_info.h \
        quota.c         \
        random_ioctl.c  \
        readahead.c     \
diff --git a/ptrace_syscall_info.c b/ptrace_syscall_info.c
new file mode 100644 (file)
index 0000000..9f884e4
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org>
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "defs.h"
+#include "kill_save_errno.h"
+#include "ptrace.h"
+#include "ptrace_syscall_info.h"
+#include "scno.h"
+
+#include <signal.h>
+#include <sys/wait.h>
+
+bool ptrace_get_syscall_info_supported;
+
+static int
+kill_tracee(pid_t pid)
+{
+       return kill_save_errno(pid, SIGKILL);
+}
+
+#define FAIL   do { ptrace_stop = -1U; goto done; } while (0)
+
+static const unsigned int expected_none_size =
+       offsetof(struct ptrace_syscall_info, entry);
+static const unsigned int expected_entry_size =
+       offsetofend(struct ptrace_syscall_info, entry.args);
+static const unsigned int expected_exit_size =
+       offsetofend(struct ptrace_syscall_info, exit.is_error);
+
+/*
+ * Test that PTRACE_GET_SYSCALL_INFO API is supported by the kernel, and
+ * that the semantics implemented in the kernel matches our expectations.
+ */
+bool
+test_ptrace_get_syscall_info(void)
+{
+       static const unsigned long args[][7] = {
+               /* a sequence of architecture-agnostic syscalls */
+               {
+                       __NR_chdir,
+                       (unsigned long) "",
+                       0xbad1fed1,
+                       0xbad2fed2,
+                       0xbad3fed3,
+                       0xbad4fed4,
+                       0xbad5fed5
+               },
+               {
+                       __NR_gettid,
+                       0xcaf0bea0,
+                       0xcaf1bea1,
+                       0xcaf2bea2,
+                       0xcaf3bea3,
+                       0xcaf4bea4,
+                       0xcaf5bea5
+               },
+               {
+                       __NR_exit_group,
+                       0,
+                       0xfac1c0d1,
+                       0xfac2c0d2,
+                       0xfac3c0d3,
+                       0xfac4c0d4,
+                       0xfac5c0d5
+               }
+       };
+       const unsigned long *exp_args;
+
+       int pid = fork();
+       if (pid < 0)
+               perror_func_msg_and_die("fork");
+
+       if (pid == 0) {
+               /* get the pid before PTRACE_TRACEME */
+               pid = getpid();
+               if (ptrace(PTRACE_TRACEME, 0L, 0L, 0L) < 0) {
+                       /* exit with a nonzero exit status */
+                       perror_func_msg_and_die("PTRACE_TRACEME");
+               }
+               kill(pid, SIGSTOP);
+               for (unsigned int i = 0; i < ARRAY_SIZE(args); ++i) {
+                       syscall(args[i][0],
+                               args[i][1], args[i][2], args[i][3],
+                               args[i][4], args[i][5], args[i][6]);
+               }
+               /* unreachable */
+               _exit(1);
+       }
+
+       const struct {
+               unsigned int is_error;
+               int rval;
+       } *exp_param, exit_param[] = {
+               { 1, -ENOENT }, /* chdir */
+               { 0, pid }      /* gettid */
+       };
+
+       unsigned int ptrace_stop;
+
+       for (ptrace_stop = 0; ; ++ptrace_stop) {
+               struct ptrace_syscall_info info = {
+                       .op = 0xff      /* invalid PTRACE_SYSCALL_INFO_* op */
+               };
+               const size_t size = sizeof(info);
+               int status;
+               long rc = waitpid(pid, &status, 0);
+               if (rc != pid) {
+                       /* cannot happen */
+                       kill_tracee(pid);
+                       perror_func_msg_and_die("#%d: unexpected wait result"
+                                               " %ld", ptrace_stop, rc);
+               }
+               if (WIFEXITED(status)) {
+                       /* tracee is no more */
+                       pid = 0;
+                       if (WEXITSTATUS(status) == 0)
+                               break;
+                       debug_func_msg("#%d: unexpected exit status %u",
+                                      ptrace_stop, WEXITSTATUS(status));
+                       FAIL;
+               }
+               if (WIFSIGNALED(status)) {
+                       /* tracee is no more */
+                       pid = 0;
+                       debug_func_msg("#%d: unexpected signal %u",
+                                      ptrace_stop, WTERMSIG(status));
+                       FAIL;
+               }
+               if (!WIFSTOPPED(status)) {
+                       /* cannot happen */
+                       kill_tracee(pid);
+                       error_func_msg_and_die("#%d: unexpected wait status"
+                                              " %#x", ptrace_stop, status);
+               }
+
+               switch (WSTOPSIG(status)) {
+               case SIGSTOP:
+                       if (ptrace_stop) {
+                               debug_func_msg("#%d: unexpected signal stop",
+                                              ptrace_stop);
+                               FAIL;
+                       }
+                       if (ptrace(PTRACE_SETOPTIONS, pid, 0L,
+                                  PTRACE_O_TRACESYSGOOD) < 0) {
+                               /* cannot happen */
+                               kill_tracee(pid);
+                               perror_func_msg_and_die("PTRACE_SETOPTIONS");
+                       }
+                       rc = ptrace(PTRACE_GET_SYSCALL_INFO, pid,
+                                   (void *) size, &info);
+                       if (rc < 0) {
+                               debug_perror_msg("PTRACE_GET_SYSCALL_INFO");
+                               FAIL;
+                       }
+                       if (rc < (long) expected_none_size
+                           || info.op != PTRACE_SYSCALL_INFO_NONE
+                           || !info.arch
+                           || !info.instruction_pointer
+                           || !info.stack_pointer) {
+                               debug_func_msg("signal stop mismatch");
+                               FAIL;
+                       }
+                       break;
+
+               case SIGTRAP | 0x80:
+                       rc = ptrace(PTRACE_GET_SYSCALL_INFO, pid,
+                                   (void *) size, &info);
+                       if (rc < 0) {
+                               debug_perror_msg("#%d: PTRACE_GET_SYSCALL_INFO",
+                                                ptrace_stop);
+                               FAIL;
+                       }
+                       switch (ptrace_stop) {
+                       case 1: /* entering chdir */
+                       case 3: /* entering gettid */
+                       case 5: /* entering exit_group */
+                               exp_args = args[ptrace_stop / 2];
+                               if (rc < (long) expected_entry_size
+                                   || info.op != PTRACE_SYSCALL_INFO_ENTRY
+                                   || !info.arch
+                                   || !info.instruction_pointer
+                                   || !info.stack_pointer
+                                   || (info.entry.nr != exp_args[0])
+                                   || (info.entry.args[0] != exp_args[1])
+                                   || (info.entry.args[1] != exp_args[2])
+                                   || (info.entry.args[2] != exp_args[3])
+                                   || (info.entry.args[3] != exp_args[4])
+                                   || (info.entry.args[4] != exp_args[5])
+                                   || (info.entry.args[5] != exp_args[6])) {
+                                       debug_func_msg("#%d: entry stop"
+                                                      " mismatch",
+                                                      ptrace_stop);
+                                       FAIL;
+                               }
+                               break;
+                       case 2: /* exiting chdir */
+                       case 4: /* exiting gettid */
+                               exp_param = &exit_param[ptrace_stop / 2 - 1];
+                               if (rc < (long) expected_exit_size
+                                   || info.op != PTRACE_SYSCALL_INFO_EXIT
+                                   || !info.arch
+                                   || !info.instruction_pointer
+                                   || !info.stack_pointer
+                                   || info.exit.is_error != exp_param->is_error
+                                   || info.exit.rval != exp_param->rval) {
+                                       debug_func_msg("#%d: exit stop"
+                                                      " mismatch",
+                                                      ptrace_stop);
+                                       FAIL;
+                               }
+                               break;
+                       default:
+                               debug_func_msg("#%d: unexpected syscall stop",
+                                              ptrace_stop);
+                               FAIL;
+                       }
+                       break;
+
+               default:
+                       debug_func_msg("#%d: unexpected stop signal %#x",
+                                      ptrace_stop, WSTOPSIG(status));
+                       FAIL;
+               }
+
+               if (ptrace(PTRACE_SYSCALL, pid, 0L, 0L) < 0) {
+                       /* cannot happen */
+                       kill_tracee(pid);
+                       perror_func_msg_and_die("PTRACE_SYSCALL");
+               }
+       }
+
+done:
+       if (pid) {
+               kill_tracee(pid);
+               waitpid(pid, NULL, 0);
+               ptrace_stop = -1U;
+       }
+
+       ptrace_get_syscall_info_supported =
+               ptrace_stop == ARRAY_SIZE(args) * 2;
+
+       if (ptrace_get_syscall_info_supported)
+               debug_msg("PTRACE_GET_SYSCALL_INFO works");
+       else
+               debug_msg("PTRACE_GET_SYSCALL_INFO does not work");
+
+       return ptrace_get_syscall_info_supported;
+}
diff --git a/ptrace_syscall_info.h b/ptrace_syscall_info.h
new file mode 100644 (file)
index 0000000..25850f8
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2018 Dmitry V. Levin <ldv@altlinux.org>
+ * All rights reserved.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef STRACE_PTRACE_SYSCALL_INFO_H
+#define STRACE_PTRACE_SYSCALL_INFO_H
+
+extern bool ptrace_get_syscall_info_supported;
+extern bool test_ptrace_get_syscall_info(void);
+
+#endif /* !STRACE_PTRACE_SYSCALL_INFO_H */
index c169ae0903bbec8929f2914898aafa7411edcd1b..a87d6729616b8353bcc4b3b08bfc7eb164971168 100644 (file)
--- a/strace.c
+++ b/strace.c
@@ -35,6 +35,7 @@
 #include "largefile_wrappers.h"
 #include "mmap_cache.h"
 #include "number_set.h"
+#include "ptrace_syscall_info.h"
 #include "scno.h"
 #include "printsiginfo.h"
 #include "trace_event.h"
@@ -1800,6 +1801,13 @@ init(int argc, char *argv[])
        debug_msg("ptrace_setoptions = %#x", ptrace_setoptions);
        test_ptrace_seize();
 
+       /*
+        * NOMMU provides no forks necessary for PTRACE_GET_SYSCALL_INFO test,
+        * leave the default unchanged.
+        */
+       if (!NOMMU_SYSTEM)
+               test_ptrace_get_syscall_info();
+
        /*
         * Is something weird with our stdin and/or stdout -
         * for example, may they be not open? In this case,