nptl: Check for compatible GDB in nptl/tst-pthread-gdb-attach
Commit Message
Also do not clear the subprocess environment, in case running
GDB needs certain environment variables.
---
nptl/tst-pthread-gdb-attach.c | 78 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 76 insertions(+), 2 deletions(-)
Comments
On Thu, Apr 22, 2021 at 2:28 AM Florian Weimer <fweimer@redhat.com> wrote:
>
> Also do not clear the subprocess environment, in case running
> GDB needs certain environment variables.
>
> ---
> nptl/tst-pthread-gdb-attach.c | 78 +++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 76 insertions(+), 2 deletions(-)
>
> diff --git a/nptl/tst-pthread-gdb-attach.c b/nptl/tst-pthread-gdb-attach.c
> index 0603ad844d..901a120034 100644
> --- a/nptl/tst-pthread-gdb-attach.c
> +++ b/nptl/tst-pthread-gdb-attach.c
> @@ -20,8 +20,12 @@
> whether libthread_db can be loaded, and that access to thread-local
> variables works. */
>
> +#include <elf.h>
> #include <errno.h>
> +#include <fcntl.h>
> +#include <stdbool.h>
> #include <stdlib.h>
> +#include <string.h>
> #include <support/check.h>
> #include <support/support.h>
> #include <support/temp_file.h>
> @@ -35,6 +39,49 @@
> the thread. */
> __thread volatile int altered_by_debugger;
>
> +/* Common prefix between 32-bit and 64-bit ELF. */
> +struct elf_prefix
> +{
> + unsigned char e_ident[EI_NIDENT];
> + uint16_t e_type;
> + uint16_t e_machine;
> + uint32_t e_version;
> +};
> +_Static_assert (sizeof (struct elf_prefix) == EI_NIDENT + 8,
> + "padding in struct elf_prefix");
> +
> +/* Reads the ELF header from PATH. Returns true if the header can be
> + read, false if the file is too short. */
> +static bool
> +read_elf_header (const char *path, struct elf_prefix *elf)
> +{
> + int fd = xopen (path, O_RDONLY, 0);
> + bool result = read (fd, elf, sizeof (*elf)) == sizeof (*elf);
> + xclose (fd);
> + return result;
> +}
> +
> +/* Searches for "gdb" alongside the path variable. See execvpe. */
> +static char *
> +find_gdb (void)
> +{
> + const char *path = getenv ("PATH");
> + if (path == NULL)
> + return NULL;
> + while (true)
> + {
> + const char *colon = strchrnul (path, ':');
> + char *candidate = xasprintf ("%.*s/gdb", (int) (colon - path), path);
> + if (access (candidate, X_OK) == 0)
> + return candidate;
> + free (candidate);
> + if (*colon == '\0')
> + break;
> + path = colon + 1;
> + }
> + return NULL;
> +}
> +
> /* Writes the GDB script to run the test to PATH. */
> static void
> write_gdbscript (const char *path, int tested_pid)
> @@ -105,6 +152,33 @@ in_subprocess (void)
> static int
> do_test (void)
> {
> + char *gdb_path = find_gdb ();
> + if (gdb_path == NULL)
> + FAIL_UNSUPPORTED ("gdb command not found in PATH: %s", getenv ("PATH"));
> +
> + /* Check that libthread_db is compatible with the gdb architecture
> + because gdb loads it via dlopen. */
> + {
> + char *threaddb_path = xasprintf ("%s/nptl_db/libthread_db.so",
> + support_objdir_root);
> + struct elf_prefix elf_threaddb;
> + TEST_VERIFY_EXIT (read_elf_header (threaddb_path, &elf_threaddb));
> + struct elf_prefix elf_gdb;
> + /* If the ELF header cannot be read or "gdb" is not an ELF file,
> + assume this is a wrapper script that can run. */
> + if (read_elf_header (gdb_path, &elf_gdb)
> + && memcmp (&elf_gdb, ELFMAG, SELFMAG) == 0)
> + {
> + if (elf_gdb.e_ident[EI_CLASS] != elf_threaddb.e_ident[EI_CLASS])
> + FAIL_UNSUPPORTED ("GDB at %s has wrong class", gdb_path);
> + if (elf_gdb.e_ident[EI_DATA] != elf_threaddb.e_ident[EI_DATA])
> + FAIL_UNSUPPORTED ("GDB at %s has wrong data", gdb_path);
> + if (elf_gdb.e_machine != elf_threaddb.e_machine)
> + FAIL_UNSUPPORTED ("GDB at %s has wrong machine", gdb_path);
> + }
> + free (threaddb_path);
> + }
> +
> pid_t tested_pid = xfork ();
> if (tested_pid == 0)
> in_subprocess ();
> @@ -117,9 +191,8 @@ do_test (void)
> pid_t gdb_pid = xfork ();
> if (gdb_pid == 0)
> {
> - clearenv ();
> xdup2 (STDOUT_FILENO, STDERR_FILENO);
> - execlp ("gdb", "gdb", "-nx", "-batch", "-x", gdbscript, NULL);
> + execl (gdb_path, "gdb", "-nx", "-batch", "-x", gdbscript, NULL);
> if (errno == ENOENT)
> _exit (EXIT_UNSUPPORTED);
> else
> @@ -137,6 +210,7 @@ do_test (void)
>
> free (tested_pid_string);
> free (gdbscript);
> + free (gdb_path);
> return 0;
> }
>
>
LGTM.
Thanks.
@@ -20,8 +20,12 @@
whether libthread_db can be loaded, and that access to thread-local
variables works. */
+#include <elf.h>
#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
#include <stdlib.h>
+#include <string.h>
#include <support/check.h>
#include <support/support.h>
#include <support/temp_file.h>
@@ -35,6 +39,49 @@
the thread. */
__thread volatile int altered_by_debugger;
+/* Common prefix between 32-bit and 64-bit ELF. */
+struct elf_prefix
+{
+ unsigned char e_ident[EI_NIDENT];
+ uint16_t e_type;
+ uint16_t e_machine;
+ uint32_t e_version;
+};
+_Static_assert (sizeof (struct elf_prefix) == EI_NIDENT + 8,
+ "padding in struct elf_prefix");
+
+/* Reads the ELF header from PATH. Returns true if the header can be
+ read, false if the file is too short. */
+static bool
+read_elf_header (const char *path, struct elf_prefix *elf)
+{
+ int fd = xopen (path, O_RDONLY, 0);
+ bool result = read (fd, elf, sizeof (*elf)) == sizeof (*elf);
+ xclose (fd);
+ return result;
+}
+
+/* Searches for "gdb" alongside the path variable. See execvpe. */
+static char *
+find_gdb (void)
+{
+ const char *path = getenv ("PATH");
+ if (path == NULL)
+ return NULL;
+ while (true)
+ {
+ const char *colon = strchrnul (path, ':');
+ char *candidate = xasprintf ("%.*s/gdb", (int) (colon - path), path);
+ if (access (candidate, X_OK) == 0)
+ return candidate;
+ free (candidate);
+ if (*colon == '\0')
+ break;
+ path = colon + 1;
+ }
+ return NULL;
+}
+
/* Writes the GDB script to run the test to PATH. */
static void
write_gdbscript (const char *path, int tested_pid)
@@ -105,6 +152,33 @@ in_subprocess (void)
static int
do_test (void)
{
+ char *gdb_path = find_gdb ();
+ if (gdb_path == NULL)
+ FAIL_UNSUPPORTED ("gdb command not found in PATH: %s", getenv ("PATH"));
+
+ /* Check that libthread_db is compatible with the gdb architecture
+ because gdb loads it via dlopen. */
+ {
+ char *threaddb_path = xasprintf ("%s/nptl_db/libthread_db.so",
+ support_objdir_root);
+ struct elf_prefix elf_threaddb;
+ TEST_VERIFY_EXIT (read_elf_header (threaddb_path, &elf_threaddb));
+ struct elf_prefix elf_gdb;
+ /* If the ELF header cannot be read or "gdb" is not an ELF file,
+ assume this is a wrapper script that can run. */
+ if (read_elf_header (gdb_path, &elf_gdb)
+ && memcmp (&elf_gdb, ELFMAG, SELFMAG) == 0)
+ {
+ if (elf_gdb.e_ident[EI_CLASS] != elf_threaddb.e_ident[EI_CLASS])
+ FAIL_UNSUPPORTED ("GDB at %s has wrong class", gdb_path);
+ if (elf_gdb.e_ident[EI_DATA] != elf_threaddb.e_ident[EI_DATA])
+ FAIL_UNSUPPORTED ("GDB at %s has wrong data", gdb_path);
+ if (elf_gdb.e_machine != elf_threaddb.e_machine)
+ FAIL_UNSUPPORTED ("GDB at %s has wrong machine", gdb_path);
+ }
+ free (threaddb_path);
+ }
+
pid_t tested_pid = xfork ();
if (tested_pid == 0)
in_subprocess ();
@@ -117,9 +191,8 @@ do_test (void)
pid_t gdb_pid = xfork ();
if (gdb_pid == 0)
{
- clearenv ();
xdup2 (STDOUT_FILENO, STDERR_FILENO);
- execlp ("gdb", "gdb", "-nx", "-batch", "-x", gdbscript, NULL);
+ execl (gdb_path, "gdb", "-nx", "-batch", "-x", gdbscript, NULL);
if (errno == ENOENT)
_exit (EXIT_UNSUPPORTED);
else
@@ -137,6 +210,7 @@ do_test (void)
free (tested_pid_string);
free (gdbscript);
+ free (gdb_path);
return 0;
}