(root)/
glibc-2.38/
nptl/
tst-pthread-gdb-attach.c
       1  /* Smoke testing GDB process attach with thread-local variable access.
       2     Copyright (C) 2021-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the License, or (at your option) any later version.
       9  
      10     The GNU C Library is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  /* This test runs GDB against a forked copy of itself, to check
      20     whether libthread_db can be loaded, and that access to thread-local
      21     variables works.  */
      22  
      23  #include <elf.h>
      24  #include <errno.h>
      25  #include <fcntl.h>
      26  #include <signal.h>
      27  #include <stdbool.h>
      28  #include <stdlib.h>
      29  #include <string.h>
      30  #include <support/capture_subprocess.h>
      31  #include <support/check.h>
      32  #include <support/xptrace.h>
      33  #include <support/subprocess.h>
      34  #include <support/support.h>
      35  #include <support/temp_file.h>
      36  #include <support/test-driver.h>
      37  #include <support/xstdio.h>
      38  #include <support/xthread.h>
      39  #include <support/xunistd.h>
      40  #include <unistd.h>
      41  
      42  /* Starts out as zero, changed to 1 or 2 by the debugger, depending on
      43     the thread.  */
      44  __thread volatile int altered_by_debugger;
      45  
      46  /* Common prefix between 32-bit and 64-bit ELF.  */
      47  struct elf_prefix
      48  {
      49    unsigned char e_ident[EI_NIDENT];
      50    uint16_t e_type;
      51    uint16_t e_machine;
      52    uint32_t e_version;
      53  };
      54  _Static_assert (sizeof (struct elf_prefix) == EI_NIDENT + 8,
      55                  "padding in struct elf_prefix");
      56  
      57  /* Reads the ELF header from PATH.  Returns true if the header can be
      58     read, false if the file is too short.  */
      59  static bool
      60  read_elf_header (const char *path, struct elf_prefix *elf)
      61  {
      62    int fd = xopen (path, O_RDONLY, 0);
      63    bool result = read (fd, elf, sizeof (*elf)) == sizeof (*elf);
      64    xclose (fd);
      65    return result;
      66  }
      67  
      68  /* Searches for "gdb" alongside the path variable.  See execvpe.  */
      69  static char *
      70  find_gdb (void)
      71  {
      72    const char *path = getenv ("PATH");
      73    if (path == NULL)
      74      return NULL;
      75    while (true)
      76      {
      77        const char *colon = strchrnul (path, ':');
      78        char *candidate = xasprintf ("%.*s/gdb", (int) (colon - path), path);
      79        if (access (candidate, X_OK) == 0)
      80          return candidate;
      81        free (candidate);
      82        if (*colon == '\0')
      83          break;
      84        path = colon + 1;
      85      }
      86    return NULL;
      87  }
      88  
      89  /* Writes the GDB script to run the test to PATH.  */
      90  static void
      91  write_gdbscript (const char *path, int tested_pid)
      92  {
      93    FILE *fp = xfopen (path, "w");
      94    fprintf (fp,
      95             "set trace-commands on\n"
      96             "set debug libthread-db 1\n"
      97  #if DO_ADD_SYMBOL_FILE
      98             /* Do not do this unconditionally to work around a GDB
      99                assertion failure: ../../gdb/symtab.c:6404:
     100                internal-error: CORE_ADDR get_msymbol_address(objfile*,
     101                const minimal_symbol*): Assertion `(objf->flags &
     102                OBJF_MAINLINE) == 0' failed.  */
     103             "add-symbol-file %1$s/nptl/tst-pthread-gdb-attach\n"
     104  #endif
     105             "set auto-load safe-path %1$s/nptl_db\n"
     106             "set libthread-db-search-path %1$s/nptl_db\n"
     107             "attach %2$d\n",
     108             support_objdir_root, tested_pid);
     109    fputs ("break debugger_inspection_point\n"
     110           "continue\n"
     111           "thread 1\n"
     112           "print altered_by_debugger\n"
     113           "print altered_by_debugger = 1\n"
     114           "thread 2\n"
     115           "print altered_by_debugger\n"
     116           "print altered_by_debugger = 2\n"
     117           "continue\n",
     118           fp);
     119    xfclose (fp);
     120  }
     121  
     122  /* The test sets a breakpoint on this function and alters the
     123     altered_by_debugger thread-local variable.  */
     124  void __attribute__ ((weak))
     125  debugger_inspection_point (void)
     126  {
     127  }
     128  
     129  /* Thread function for the test thread in the subprocess.  */
     130  static void *
     131  subprocess_thread (void *closure)
     132  {
     133    /* Wait until altered_by_debugger changes the value away from 0.  */
     134    while (altered_by_debugger == 0)
     135      {
     136        usleep (100 * 1000);
     137        debugger_inspection_point ();
     138      }
     139  
     140    TEST_COMPARE (altered_by_debugger, 2);
     141    return NULL;
     142  }
     143  
     144  /* This function implements the subprocess under test.  It creates a
     145     second thread, waiting for its value to change to 2, and checks
     146     that the main thread also changed its value to 1.  */
     147  static void
     148  in_subprocess (void *arg)
     149  {
     150    pthread_t thr = xpthread_create (NULL, subprocess_thread, NULL);
     151    TEST_VERIFY (xpthread_join (thr) == NULL);
     152    TEST_COMPARE (altered_by_debugger, 1);
     153    _exit (0);
     154  }
     155  
     156  static void
     157  gdb_process (const char *gdb_path, const char *gdbscript, pid_t *tested_pid)
     158  {
     159    /* Create a copy of current test to check with gdb.  As the
     160       target_process is a child of this gdb_process, gdb is also able
     161       to attach to target_process if YAMA is configured to 1 =
     162       "restricted ptrace".  */
     163    struct support_subprocess target = support_subprocess (in_subprocess, NULL);
     164  
     165    write_gdbscript (gdbscript, target.pid);
     166    *tested_pid = target.pid;
     167  
     168    xdup2 (STDOUT_FILENO, STDERR_FILENO);
     169    execl (gdb_path, "gdb", "-nx", "-batch", "-x", gdbscript, NULL);
     170    if (errno == ENOENT)
     171      _exit (EXIT_UNSUPPORTED);
     172    else
     173      _exit (1);
     174  }
     175  
     176  static int
     177  do_test (void)
     178  {
     179    char *gdb_path = find_gdb ();
     180    if (gdb_path == NULL)
     181      FAIL_UNSUPPORTED ("gdb command not found in PATH: %s", getenv ("PATH"));
     182  
     183    /* Check that libthread_db is compatible with the gdb architecture
     184       because gdb loads it via dlopen.  */
     185    {
     186      char *threaddb_path = xasprintf ("%s/nptl_db/libthread_db.so",
     187                                       support_objdir_root);
     188      struct elf_prefix elf_threaddb;
     189      TEST_VERIFY_EXIT (read_elf_header (threaddb_path, &elf_threaddb));
     190      struct elf_prefix elf_gdb;
     191      /* If the ELF header cannot be read or "gdb" is not an ELF file,
     192         assume this is a wrapper script that can run.  */
     193      if (read_elf_header (gdb_path, &elf_gdb)
     194          && memcmp (&elf_gdb, ELFMAG, SELFMAG) == 0)
     195        {
     196          if (elf_gdb.e_ident[EI_CLASS] != elf_threaddb.e_ident[EI_CLASS])
     197            FAIL_UNSUPPORTED ("GDB at %s has wrong class", gdb_path);
     198          if (elf_gdb.e_ident[EI_DATA] != elf_threaddb.e_ident[EI_DATA])
     199            FAIL_UNSUPPORTED ("GDB at %s has wrong data", gdb_path);
     200          if (elf_gdb.e_machine != elf_threaddb.e_machine)
     201            FAIL_UNSUPPORTED ("GDB at %s has wrong machine", gdb_path);
     202        }
     203      free (threaddb_path);
     204    }
     205  
     206    /* Check if our subprocess can be debugged with ptrace.  */
     207    {
     208      int ptrace_scope = support_ptrace_scope ();
     209      if (ptrace_scope >= 2)
     210        FAIL_UNSUPPORTED ("/proc/sys/kernel/yama/ptrace_scope >= 2");
     211    }
     212  
     213    char *gdbscript;
     214    xclose (create_temp_file ("tst-pthread-gdb-attach-", &gdbscript));
     215  
     216    /* Run 'gdb' on test subprocess which will be created in gdb_process.
     217       The pid of the subprocess will be written to 'tested_pid'.  */
     218    pid_t *tested_pid = support_shared_allocate (sizeof (pid_t));
     219  
     220    pid_t gdb_pid = xfork ();
     221    if (gdb_pid == 0)
     222      gdb_process (gdb_path, gdbscript, tested_pid);
     223  
     224    int status;
     225    TEST_COMPARE (xwaitpid (gdb_pid, &status, 0), gdb_pid);
     226    if (WIFEXITED (status) && WEXITSTATUS (status) == EXIT_UNSUPPORTED)
     227      /* gdb is not installed.  */
     228      return EXIT_UNSUPPORTED;
     229    TEST_COMPARE (status, 0);
     230  
     231    kill (*tested_pid, SIGKILL);
     232  
     233    support_shared_free (tested_pid);
     234    free (gdbscript);
     235    free (gdb_path);
     236    return 0;
     237  }
     238  
     239  #include <support/test-driver.c>