Test stepping within a runtime loader / dynamic linker
authorKevin Buettner <kevinb@redhat.com>
Thu, 20 Oct 2022 02:36:07 +0000 (19:36 -0700)
committerKevin Buettner <kevinb@redhat.com>
Thu, 20 Oct 2022 02:40:33 +0000 (19:40 -0700)
See the remarks in rtld-step.exp for a description of what this
test is about.

This test case has been tested using gcc on the following x86-64 Linux
distributions/releases:

    Fedora 28
    Fedora 32
    Fedora 33
    Fedora 34
    Fedora 35
    Fedora 36
    Fedora 37
    rawhide (f38)
    RHEL 9.1
    Ubuntu 22.04.1 LTS

It's also been tested (and found to be working) with
RUNTESTFLAGS="CC_FOR_TARGET=clang" on all of the above expect for
Fedora 28.  The (old) version of clang available on F28 did not
accept the -static-pie option.

I also tried to make this test work on FreeBSD 13.1.  While I think I
made significant progress, I was ultimately stymied by this message
which occurs when attempting to run the main program which has been
set to use the fake/pretend RTLD as the ELF interpreter:

ELF interpreter /path/to/rtld-step-rtld not found, error 22

I have left one of the flags (-static) in place which I believe
to be needed for FreeBSD (though since I never got it to work, I
don't know for sure.)  I've also left some declarations needed
for FreeBSD in rtld-step-rtld.c.  They're currently disabled via
a #if 0; you'll need to enable them if you want to try to make
it work on FreeBSD.

gdb/testsuite/gdb.base/rtld-step-main.c [new file with mode: 0644]
gdb/testsuite/gdb.base/rtld-step-rtld.c [new file with mode: 0644]
gdb/testsuite/gdb.base/rtld-step.exp [new file with mode: 0644]

diff --git a/gdb/testsuite/gdb.base/rtld-step-main.c b/gdb/testsuite/gdb.base/rtld-step-main.c
new file mode 100644 (file)
index 0000000..6b3984d
--- /dev/null
@@ -0,0 +1,22 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+main (void)
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/rtld-step-rtld.c b/gdb/testsuite/gdb.base/rtld-step-rtld.c
new file mode 100644 (file)
index 0000000..e234ebf
--- /dev/null
@@ -0,0 +1,65 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <unistd.h>
+
+/* The declarations below are needed if you try to make this test case
+   work on FreeBSD.  They're disabled because there are other problems
+   with this test on FreeBSD.  See the .exp file for more info.  If
+   those other problems can be resolved, it may be worth reenabling
+   these declarations.  */
+#if 0
+__attribute__((weak))
+char *__progname = "fake-rtld";
+
+__attribute__((weak))
+char **environ = 0;
+#endif
+
+void
+baz (int i)
+{
+}
+
+void
+foo (int a)
+{
+  baz (a);
+}
+
+void
+bar ()
+{
+  foo (1);
+  baz (99);
+  foo (2);
+}
+
+int
+main ()
+{
+  foo (0);
+  bar ();
+  return 0;
+}
+
+void
+_start ()
+{
+  main ();
+  _exit (0);
+}
diff --git a/gdb/testsuite/gdb.base/rtld-step.exp b/gdb/testsuite/gdb.base/rtld-step.exp
new file mode 100644 (file)
index 0000000..4773fa8
--- /dev/null
@@ -0,0 +1,140 @@
+# Copyright 2022 Free Software Foundation, Inc.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test stepping through a runtime loader / dynamic linker (RTLD):
+#
+# While it'd be nice to have a test which steps through an actual
+# runtime loader / dynamic linker, constructing such a test would be
+# non-portable; we would need to know implementation details such
+# as the names of some of the symbols and the order of calls to
+# various functions that implement the RTLD.  So, instead, we'll use a
+# program which doesn't even pretend to implement this functionality,
+# but which will instead be invoked in the same fashion (for ELF
+# binaries anyway) as would be expected for an ELF-based RTLD.
+#
+# To that end, we have two programs, one which will pretend to be an
+# RTLD and the other which will be caused to use the pretend RTLD.
+#
+# When the main program is run, the pretend/fake RTLD is run instead,
+# due to it being specified as the ELF interpreter for the main
+# program.  Within GDB, we then attempt to do some simple debugging
+# involving 'step', 'next', and 'finish'.
+
+# This test can't be run on targets lacking shared library support
+# or for non-ELF targets.  (We're not really testing or building
+# shared libraries here, but having a RTLD implies having shared
+# libraries on the target.)
+if { [skip_shlib_tests] || ![is_elf_target] } {
+    return 0
+}
+
+# (Pretend) RTLD file names and flags:
+set rtld_basename ${::gdb_test_file_name}-rtld
+set srcfile_rtld ${srcdir}/${subdir}/${rtld_basename}.c
+set binfile_rtld [standard_output_file ${rtld_basename}]
+
+# Placing 'pie' in the flag list (for rtld_flags) doesn't work, but
+# using -static-pie -FPIE in additional_flags does.  Apparently, when
+# 'pie' is listed, gdb_compile will (on Linux) use both -fPIE and
+# -pie.         Testing shows that use of -pie creates a dynamically linked
+# executable when either a static or static-pie executable is desired
+# instead.  (This is probably fragile.)
+#
+# While developing this code on Fedora Linux, it was found that (only)
+# the flags -static-pie -fPIE were needed for Fedora 35 through Fedora
+# 38.  The source file rtld-step-rtld.c didn't need the _start()
+# function either.  And, better still, it was possible to call
+# printf() to output progress messages in the pretend/fake RTLD. 
+# Sadly, these output statements had to be removed in order to obtain
+# code which would work on other Linux distributions / releases.
+#
+# When testing against earlier versions of Fedora, RHEL 9, and
+# also Ubuntu 22.04, that short flag list didn't work. For these
+# linux releases, it was found that -nostdlib -lc were also required.
+# Due to the use of -nostdlib, a _start() function had to be added
+# to the RTLD code.
+#
+# Finally, on FreeBSD, it was found that in order to end up with a
+# statically linked executable, -static was also needed.
+# Unfortunately, when attempting to run the rtld-step-main under GDB
+# on FreeBSD 13.1, this message was/is encountered:
+#
+# ELF interpreter /path/to/rtld-step-rtld not found, error 22
+#
+# So, sadly, this test does not currently work on FreeBSD.  If you try
+# to make it work on FreeBSD, you'll probably need to enable the
+# declarations for __progname and environ in rtld-step-rtld.c.
+#
+# If this test becomes broken at some point in the future, you might
+# try removing -static from the flags below as it is not needed for
+# Linux.
+#
+# Also, because the RTLD is static, you'll need static versions of
+# libc/glibc installed on your system.  (A message such as "cannot
+# find -lc" is a clue that you're missing a static version of libc.)
+
+set rtld_flags [list debug additional_flags=[list -static-pie -fPIE \
+                                                 -nostdlib -static -lc]]
+
+# Main program file names and flags:
+set main_basename ${::gdb_test_file_name}-main
+set srcfile_main ${srcdir}/${subdir}/${main_basename}.c
+set binfile_main [standard_output_file ${main_basename}]
+set main_flags [list debug additional_flags="-Wl,--dynamic-linker=${binfile_rtld}"]
+
+# Compile pretend RTLD:
+if { [gdb_compile ${srcfile_rtld} ${binfile_rtld} executable $rtld_flags] != "" } {
+    untested "failed to compile"
+    return -1
+}
+
+# Compile main program:
+if { [gdb_compile ${srcfile_main} ${binfile_main} executable $main_flags] != "" } {
+    untested "failed to compile"
+    return -1
+}
+
+clean_restart ${binfile_main}
+
+if {![runto_main]} {
+    return 0
+}
+
+# Running the command 'info sharedlibrary' should output a path to
+# the pretend/fake RTLD along with the address range.  Check that
+# this path is present and, if so, extract the address range.
+gdb_test_multiple "info sharedlibrary" "" {
+    -re -wrap "($hex)\[ \t\]+($hex)\[ \t\]+Yes\[ \t\]+$fullname_syntax$rtld_basename" {
+       set rtld_lower $expect_out(1,string)
+       set rtld_upper $expect_out(2,string)
+       pass $gdb_test_name
+    }
+}
+
+# Fetch PC value.
+set pc [get_hexadecimal_valueof "\$pc" 0]
+
+# Verify that PC is in the address range of the pretend/fake RTLD.
+gdb_assert { $rtld_lower <= $pc && $pc < $rtld_upper } "pc is in rtld"
+
+gdb_test "next" {bar \(\);} "next over foo 0"
+gdb_test "step" {bar \(\) at.*foo \(1\);.*} "step into bar"
+gdb_test "step" {baz \(.*?\);} "step into foo 1"
+gdb_test "finish" {Run till exit.*bar \(\).*baz.*} "finish out of foo 1"
+gdb_test "next" {foo \(2\);} "next over baz in bar"
+gdb_test "step" {baz \(.*?\);} "step into foo 2"
+gdb_test "next" "\}\[\r\n\]+" "next over baz in foo"
+gdb_test "step" "bar \\(\\).*}\[\r\n\]+.*" "step out of foo back into bar"
+
+gdb_continue_to_end