Patchwork [v1] debugging tests

login
register
mail settings
Submitter DJ Delorie
Date Oct. 2, 2019, 6:52 p.m.
Message ID <xna7ajj7yt.fsf@greed.delorie.com>
Download mbox | patch
Permalink /patch/34797/
State New
Headers show

Comments

DJ Delorie - Oct. 2, 2019, 6:52 p.m.
This bit of code tells the test cases to pause just before do_test()
is called, and wait for gdb to attach to it and clear the
wait_for_debugger variable.  There's some extra code to get the
correct pid when the test runs in a container, and some info you can
cut-n-paste into gdb to get the paths et al correct.

With the onetest patch, it looks like this:

$ WAIT_FOR_DEBUGGER=1 make test t=nss/tst-nss-test3

Then in another window you run the gdb command line it prints, and
paste in the recommended gdb commands.

From 39ba1c48e97fb75133f92863561f7dfda23d8d1a Mon Sep 17 00:00:00 2001
From: DJ Delorie <dj@redhat.com>
Date: Wed, 2 Oct 2019 14:46:46 -0400
Subject: Add wait-for-debugger test harness hooks

If WAIT_FOR_DEBUGGER is set in the environment, any test that
runs will print some useful gdb information and wait for gdb
to attach to it and clear the "wait_for_debugger" variable.

Patch

diff --git a/support/support_test_main.c b/support/support_test_main.c
index 05ad92e688..c94b0333b7 100644
--- a/support/support_test_main.c
+++ b/support/support_test_main.c
@@ -19,6 +19,7 @@ 
 #include <support/test-driver.h>
 #include <support/check.h>
 #include <support/temp_file-internal.h>
+#include <support/support.h>
 
 #include <assert.h>
 #include <errno.h>
@@ -176,10 +177,32 @@  signal_handler (int sig)
   exit (1);
 }
 
+static volatile int wait_for_debugger = 0;
+
 /* Run test_function or test_function_argv.  */
 static int
 run_test_function (int argc, char **argv, const struct test_config *config)
 {
+  if (getenv("WAIT_FOR_DEBUGGER") != NULL)
+    {
+      pid_t mypid;
+      mypid = getpid();
+      if (mypid < 3)
+	{
+	  const char *outside_pid = getenv("PID_OUTSIDE_CONTAINER");
+	  if (outside_pid)
+	    mypid = atoi (outside_pid);
+	}
+      wait_for_debugger = 1;
+      fprintf(stderr, "Waiting for debugger, test process is pid %d\n", mypid);
+      fprintf(stderr, "gdb -se %s %s %d\n", argv[0], argv[0], mypid);
+      fprintf(stderr, "(gdb) set sysroot %s/testroot.root\n", support_objdir_root);
+      fprintf(stderr, "(gdb) set wait_for_debugger = 0\n");
+      fprintf(stderr, "(gdb) b do_test\n");
+      fprintf(stderr, "(gdb) c\n");
+    }
+  while (wait_for_debugger)
+    ;
   if (config->test_function != NULL)
     return config->test_function ();
   else if (config->test_function_argv != NULL)
@@ -229,6 +252,11 @@  support_test_main (int argc, char **argv, const struct test_config *config)
   unsigned int timeoutfactor = 1;
   pid_t termpid;
 
+  /* If we're debugging the test, we need to disable timeouts and use
+     the initial pid (esp if we're running inside a container).  */
+  if (getenv("WAIT_FOR_DEBUGGER") != NULL)
+    direct = 1;
+
   if (!config->no_mallopt)
     {
       /* Make uses of freed and uninitialized memory known.  Do not
diff --git a/support/test-container.c b/support/test-container.c
index 9c42d3ae2f..5d08979df3 100644
--- a/support/test-container.c
+++ b/support/test-container.c
@@ -676,6 +676,9 @@  main (int argc, char **argv)
   char *so_base;
   int do_postclean = 0;
 
+  int pipes[2];
+  char pid_buf[20];
+
   uid_t original_uid;
   gid_t original_gid;
   /* If set, the test runs as root instead of the user running the testsuite.  */
@@ -999,6 +1002,11 @@  main (int argc, char **argv)
   if (chdir (new_cwd_path) < 0)
     FAIL_EXIT1 ("Can't cd to new %s - ", new_cwd_path);
 
+  /* This is to pass the "outside" PID to the child, which will be PID
+     1.  */
+  if (pipe2 (pipes, O_CLOEXEC) < 0)
+    FAIL_EXIT1 ("Can't create pid pipe");
+
   /* To complete the containerization, we need to fork () at least
      once.  We can't exec, nor can we somehow link the new child to
      our parent.  So we run the child and propogate it's exit status
@@ -1010,6 +1018,12 @@  main (int argc, char **argv)
     {
       /* Parent.  */
       int status;
+
+      /* Send the child's "outside" pid to it.  */
+      write (pipes[1], &child, sizeof(child));
+      close (pipes[0]);
+      close (pipes[1]);
+
       waitpid (child, &status, 0);
 
       if (WIFEXITED (status))
@@ -1028,6 +1042,14 @@  main (int argc, char **argv)
   /* The rest is the child process, which is now PID 1 and "in" the
      new root.  */
 
+  /* Get our "outside" pid from our parent.  We use this to help with
+     debugging from outside the container.  */
+  read (pipes[0], &child, sizeof(child));
+  close (pipes[0]);
+  close (pipes[1]);
+  sprintf (pid_buf, "%lu", (long unsigned)child);
+  setenv ("PID_OUTSIDE_CONTAINER", pid_buf, 0);
+
   maybe_xmkdir ("/tmp", 0755);
 
   /* Now that we're pid 1 (effectively "root") we can mount /proc  */