summaryrefslogtreecommitdiff
path: root/gdb
diff options
context:
space:
mode:
authorMarkus Metzger <markus.t.metzger@intel.com>2018-02-14 14:30:57 +0100
committerMarkus Metzger <markus.t.metzger@intel.com>2018-04-13 10:44:47 +0200
commit1d509aa625f891e20b37b8cee4659771e87b1ba4 (patch)
treefe552af320ac898cabcfaa37c709e324b3c96b25 /gdb
parent7a2711e437c50041c0a83761a90ea5e6c9ad8b98 (diff)
downloadbinutils-gdb-1d509aa625f891e20b37b8cee4659771e87b1ba4.tar.gz
infrun: step through indirect branch thunks
With version 7.3 GCC supports new options -mindirect-branch=<choice> -mfunction-return=<choice> The choices are: keep behaves as before thunk jumps through a thunk thunk-external jumps through an external thunk thunk-inline jumps through an inlined thunk For thunk and thunk-external, GDB would, on a call to the thunk, step into the thunk and then resume to its caller assuming that this is an undebuggable function. On a return thunk, GDB would stop inside the thunk. Make GDB step through such thunks instead. Before: Temporary breakpoint 1, main () at gdb.base/step-indirect-call-thunk.c:37 37 x = apply (inc, 41); (gdb) s apply (op=0x80483e6 <inc>, x=41) at gdb.base/step-indirect-call-thunk.c:29 29 return op (x); (gdb) 30 } After: Temporary breakpoint 1, main () at gdb.base/step-indirect-call-thunk.c:37 37 x = apply (inc, 41); (gdb) s apply (op=0x80483e6 <inc>, x=41) at gdb.base/step-indirect-call-thunk.c:29 29 return op (x); (gdb) inc (x=41) at gdb.base/step-indirect-call-thunk.c:23 23 return x + 1; This is independent of the step-mode. In order to step into the thunk, you would need to use stepi. When stepping over an indirect call thunk, GDB would first step through the thunk, then recognize that it stepped into a sub-routine and resume to the caller (of the thunk). Not sure whether this is worth optimizing. Thunk detection is implemented via gdbarch. I implemented the methods for IA. Other architectures may run into unexpected fails. The tests assume a fixed number of instruction steps to reach a thunk. This depends on the compiler as well as the architecture. They may need adjustments when we add support for more architectures. Or we can simply drop those tests that cover being able to step into thunks using instruction stepping. When using an older GCC, the tests will fail to build and will be reported as untested: Running .../gdb.base/step-indirect-call-thunk.exp ... gdb compile failed, \ gcc: error: unrecognized command line option '-mindirect-branch=thunk' gcc: error: unrecognized command line option '-mfunction-return=thunk' === gdb Summary === # of untested testcases 1 gdb/ * infrun.c (process_event_stop_test): Call gdbarch_in_indirect_branch_thunk. * gdbarch.sh (in_indirect_branch_thunk): New. * gdbarch.c: Regenerated. * gdbarch.h: Regenerated. * x86-tdep.h: New. * x86-tdep.c: New. * Makefile.in (ALL_TARGET_OBS): Add x86-tdep.o. (HFILES_NO_SRCDIR): Add x86-tdep.h. (ALLDEPFILES): Add x86-tdep.c. * arch-utils.h (default_in_indirect_branch_thunk): New. * arch-utils.c (default_in_indirect_branch_thunk): New. * i386-tdep: Include x86-tdep.h. (i386_in_indirect_branch_thunk): New. (i386_elf_init_abi): Set in_indirect_branch_thunk gdbarch function. * amd64-tdep: Include x86-tdep.h. (amd64_in_indirect_branch_thunk): New. (amd64_init_abi): Set in_indirect_branch_thunk gdbarch function. testsuite/ * gdb.base/step-indirect-call-thunk.exp: New. * gdb.base/step-indirect-call-thunk.c: New. * gdb.reverse/step-indirect-call-thunk.exp: New. * gdb.reverse/step-indirect-call-thunk.c: New.
Diffstat (limited to 'gdb')
-rw-r--r--gdb/ChangeLog22
-rw-r--r--gdb/Makefile.in5
-rw-r--r--gdb/amd64-tdep.c14
-rw-r--r--gdb/arch-utils.c8
-rw-r--r--gdb/arch-utils.h5
-rw-r--r--gdb/gdbarch.c23
-rw-r--r--gdb/gdbarch.h6
-rwxr-xr-xgdb/gdbarch.sh3
-rw-r--r--gdb/i386-tdep.c13
-rw-r--r--gdb/infrun.c11
-rw-r--r--gdb/testsuite/ChangeLog7
-rw-r--r--gdb/testsuite/gdb.base/step-indirect-call-thunk.c42
-rw-r--r--gdb/testsuite/gdb.base/step-indirect-call-thunk.exp73
-rw-r--r--gdb/testsuite/gdb.reverse/step-indirect-call-thunk.c36
-rw-r--r--gdb/testsuite/gdb.reverse/step-indirect-call-thunk.exp100
-rw-r--r--gdb/x86-tdep.c76
-rw-r--r--gdb/x86-tdep.h30
17 files changed, 473 insertions, 1 deletions
diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 1141a1d8bb1..f379680b2cb 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,25 @@
+2018-04-13 Markus Metzger <markus.t.metzger@intel.com>
+
+ * infrun.c (process_event_stop_test): Call
+ gdbarch_in_indirect_branch_thunk.
+ * gdbarch.sh (in_indirect_branch_thunk): New.
+ * gdbarch.c: Regenerated.
+ * gdbarch.h: Regenerated.
+ * x86-tdep.h: New.
+ * x86-tdep.c: New.
+ * Makefile.in (ALL_TARGET_OBS): Add x86-tdep.o.
+ (HFILES_NO_SRCDIR): Add x86-tdep.h.
+ (ALLDEPFILES): Add x86-tdep.c.
+ * arch-utils.h (default_in_indirect_branch_thunk): New.
+ * arch-utils.c (default_in_indirect_branch_thunk): New.
+ * i386-tdep: Include x86-tdep.h.
+ (i386_in_indirect_branch_thunk): New.
+ (i386_elf_init_abi): Set in_indirect_branch_thunk gdbarch
+ function.
+ * amd64-tdep: Include x86-tdep.h.
+ (amd64_in_indirect_branch_thunk): New.
+ (amd64_init_abi): Set in_indirect_branch_thunk gdbarch function.
+
2018-04-12 Jan Kratochvil <jan.kratochvil@redhat.com>
PR gdb/23053
diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index e885dca7d2c..40c9f8985fb 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -793,6 +793,7 @@ ALL_TARGET_OBS = \
vax-nbsd-tdep.o \
vax-tdep.o \
windows-tdep.o \
+ x86-tdep.o \
xcoffread.o \
xstormy16-tdep.o \
xtensa-config.o \
@@ -1521,7 +1522,8 @@ HFILES_NO_SRCDIR = \
tui/tui-win.h \
tui/tui-windata.h \
tui/tui-wingeneral.h \
- tui/tui-winsource.h
+ tui/tui-winsource.h \
+ x86-tdep.h
# Header files that already have srcdir in them, or which are in objdir.
@@ -2363,6 +2365,7 @@ ALLDEPFILES = \
windows-nat.c \
windows-tdep.c \
x86-nat.c \
+ x86-tdep.c \
xcoffread.c \
xstormy16-tdep.c \
xtensa-config.c \
diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c
index bceb6e14279..1fea26409ae 100644
--- a/gdb/amd64-tdep.c
+++ b/gdb/amd64-tdep.c
@@ -48,6 +48,7 @@
#include "ax-gdb.h"
#include "common/byte-vector.h"
#include "osabi.h"
+#include "x86-tdep.h"
/* Note that the AMD64 architecture was previously known as x86-64.
The latter is (forever) engraved into the canonical system name as
@@ -3034,6 +3035,16 @@ static const int amd64_record_regmap[] =
AMD64_DS_REGNUM, AMD64_ES_REGNUM, AMD64_FS_REGNUM, AMD64_GS_REGNUM
};
+/* Implement the "in_indirect_branch_thunk" gdbarch function. */
+
+static bool
+amd64_in_indirect_branch_thunk (struct gdbarch *gdbarch, CORE_ADDR pc)
+{
+ return x86_in_indirect_branch_thunk (pc, amd64_register_names,
+ AMD64_RAX_REGNUM,
+ AMD64_RIP_REGNUM);
+}
+
void
amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
const target_desc *default_tdesc)
@@ -3206,6 +3217,9 @@ amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch,
set_gdbarch_insn_is_call (gdbarch, amd64_insn_is_call);
set_gdbarch_insn_is_ret (gdbarch, amd64_insn_is_ret);
set_gdbarch_insn_is_jump (gdbarch, amd64_insn_is_jump);
+
+ set_gdbarch_in_indirect_branch_thunk (gdbarch,
+ amd64_in_indirect_branch_thunk);
}
/* Initialize ARCH for x86-64, no osabi. */
diff --git a/gdb/arch-utils.c b/gdb/arch-utils.c
index 5986ed6c0ce..cd9bd66430b 100644
--- a/gdb/arch-utils.c
+++ b/gdb/arch-utils.c
@@ -979,6 +979,14 @@ gdbarch_skip_prologue_noexcept (gdbarch *gdbarch, CORE_ADDR pc) noexcept
return new_pc;
}
+/* See arch-utils.h. */
+
+bool
+default_in_indirect_branch_thunk (gdbarch *gdbarch, CORE_ADDR pc)
+{
+ return false;
+}
+
void
_initialize_gdbarch_utils (void)
{
diff --git a/gdb/arch-utils.h b/gdb/arch-utils.h
index 3407a165e0d..b785b24643d 100644
--- a/gdb/arch-utils.h
+++ b/gdb/arch-utils.h
@@ -262,4 +262,9 @@ extern int default_print_insn (bfd_vma memaddr, disassemble_info *info);
extern CORE_ADDR gdbarch_skip_prologue_noexcept (gdbarch *gdbarch,
CORE_ADDR pc) noexcept;
+/* Default implementation of gdbarch_in_indirect_branch_thunk that returns
+ false. */
+extern bool default_in_indirect_branch_thunk (gdbarch *gdbarch,
+ CORE_ADDR pc);
+
#endif
diff --git a/gdb/gdbarch.c b/gdb/gdbarch.c
index ddafe25d4de..1359c2fb53f 100644
--- a/gdb/gdbarch.c
+++ b/gdb/gdbarch.c
@@ -266,6 +266,7 @@ struct gdbarch
gdbarch_skip_trampoline_code_ftype *skip_trampoline_code;
gdbarch_skip_solib_resolver_ftype *skip_solib_resolver;
gdbarch_in_solib_return_trampoline_ftype *in_solib_return_trampoline;
+ gdbarch_in_indirect_branch_thunk_ftype *in_indirect_branch_thunk;
gdbarch_stack_frame_destroyed_p_ftype *stack_frame_destroyed_p;
gdbarch_elf_make_msymbol_special_ftype *elf_make_msymbol_special;
gdbarch_coff_make_msymbol_special_ftype *coff_make_msymbol_special;
@@ -433,6 +434,7 @@ gdbarch_alloc (const struct gdbarch_info *info,
gdbarch->skip_trampoline_code = generic_skip_trampoline_code;
gdbarch->skip_solib_resolver = generic_skip_solib_resolver;
gdbarch->in_solib_return_trampoline = generic_in_solib_return_trampoline;
+ gdbarch->in_indirect_branch_thunk = default_in_indirect_branch_thunk;
gdbarch->stack_frame_destroyed_p = generic_stack_frame_destroyed_p;
gdbarch->coff_make_msymbol_special = default_coff_make_msymbol_special;
gdbarch->make_symbol_special = default_make_symbol_special;
@@ -627,6 +629,7 @@ verify_gdbarch (struct gdbarch *gdbarch)
/* Skip verify of skip_trampoline_code, invalid_p == 0 */
/* Skip verify of skip_solib_resolver, invalid_p == 0 */
/* Skip verify of in_solib_return_trampoline, invalid_p == 0 */
+ /* Skip verify of in_indirect_branch_thunk, invalid_p == 0 */
/* Skip verify of stack_frame_destroyed_p, invalid_p == 0 */
/* Skip verify of elf_make_msymbol_special, has predicate. */
/* Skip verify of coff_make_msymbol_special, invalid_p == 0 */
@@ -1103,6 +1106,9 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file)
"gdbarch_dump: have_nonsteppable_watchpoint = %s\n",
plongest (gdbarch->have_nonsteppable_watchpoint));
fprintf_unfiltered (file,
+ "gdbarch_dump: in_indirect_branch_thunk = <%s>\n",
+ host_address_to_string (gdbarch->in_indirect_branch_thunk));
+ fprintf_unfiltered (file,
"gdbarch_dump: in_solib_return_trampoline = <%s>\n",
host_address_to_string (gdbarch->in_solib_return_trampoline));
fprintf_unfiltered (file,
@@ -3353,6 +3359,23 @@ set_gdbarch_in_solib_return_trampoline (struct gdbarch *gdbarch,
gdbarch->in_solib_return_trampoline = in_solib_return_trampoline;
}
+bool
+gdbarch_in_indirect_branch_thunk (struct gdbarch *gdbarch, CORE_ADDR pc)
+{
+ gdb_assert (gdbarch != NULL);
+ gdb_assert (gdbarch->in_indirect_branch_thunk != NULL);
+ if (gdbarch_debug >= 2)
+ fprintf_unfiltered (gdb_stdlog, "gdbarch_in_indirect_branch_thunk called\n");
+ return gdbarch->in_indirect_branch_thunk (gdbarch, pc);
+}
+
+void
+set_gdbarch_in_indirect_branch_thunk (struct gdbarch *gdbarch,
+ gdbarch_in_indirect_branch_thunk_ftype in_indirect_branch_thunk)
+{
+ gdbarch->in_indirect_branch_thunk = in_indirect_branch_thunk;
+}
+
int
gdbarch_stack_frame_destroyed_p (struct gdbarch *gdbarch, CORE_ADDR addr)
{
diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h
index 5cb131de1d2..0084f199d7c 100644
--- a/gdb/gdbarch.h
+++ b/gdb/gdbarch.h
@@ -741,6 +741,12 @@ typedef int (gdbarch_in_solib_return_trampoline_ftype) (struct gdbarch *gdbarch,
extern int gdbarch_in_solib_return_trampoline (struct gdbarch *gdbarch, CORE_ADDR pc, const char *name);
extern void set_gdbarch_in_solib_return_trampoline (struct gdbarch *gdbarch, gdbarch_in_solib_return_trampoline_ftype *in_solib_return_trampoline);
+/* Return true if PC lies inside an indirect branch thunk. */
+
+typedef bool (gdbarch_in_indirect_branch_thunk_ftype) (struct gdbarch *gdbarch, CORE_ADDR pc);
+extern bool gdbarch_in_indirect_branch_thunk (struct gdbarch *gdbarch, CORE_ADDR pc);
+extern void set_gdbarch_in_indirect_branch_thunk (struct gdbarch *gdbarch, gdbarch_in_indirect_branch_thunk_ftype *in_indirect_branch_thunk);
+
/* A target might have problems with watchpoints as soon as the stack
frame of the current function has been destroyed. This mostly happens
as the first action in a function's epilogue. stack_frame_destroyed_p()
diff --git a/gdb/gdbarch.sh b/gdb/gdbarch.sh
index 092302993a7..4fc54cba9c3 100755
--- a/gdb/gdbarch.sh
+++ b/gdb/gdbarch.sh
@@ -660,6 +660,9 @@ m;CORE_ADDR;skip_solib_resolver;CORE_ADDR pc;pc;;generic_skip_solib_resolver;;0
# Some systems also have trampoline code for returning from shared libs.
m;int;in_solib_return_trampoline;CORE_ADDR pc, const char *name;pc, name;;generic_in_solib_return_trampoline;;0
+# Return true if PC lies inside an indirect branch thunk.
+m;bool;in_indirect_branch_thunk;CORE_ADDR pc;pc;;default_in_indirect_branch_thunk;;0
+
# A target might have problems with watchpoints as soon as the stack
# frame of the current function has been destroyed. This mostly happens
# as the first action in a function's epilogue. stack_frame_destroyed_p()
diff --git a/gdb/i386-tdep.c b/gdb/i386-tdep.c
index 60dc8013a23..bf4ca543032 100644
--- a/gdb/i386-tdep.c
+++ b/gdb/i386-tdep.c
@@ -47,6 +47,7 @@
#include "i386-tdep.h"
#include "i387-tdep.h"
#include "x86-xstate.h"
+#include "x86-tdep.h"
#include "record.h"
#include "record-full.h"
@@ -4421,6 +4422,15 @@ i386_gnu_triplet_regexp (struct gdbarch *gdbarch)
+/* Implement the "in_indirect_branch_thunk" gdbarch function. */
+
+static bool
+i386_in_indirect_branch_thunk (struct gdbarch *gdbarch, CORE_ADDR pc)
+{
+ return x86_in_indirect_branch_thunk (pc, i386_register_names,
+ I386_EAX_REGNUM, I386_EIP_REGNUM);
+}
+
/* Generic ELF. */
void
@@ -4447,6 +4457,9 @@ i386_elf_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
i386_stap_is_single_operand);
set_gdbarch_stap_parse_special_token (gdbarch,
i386_stap_parse_special_token);
+
+ set_gdbarch_in_indirect_branch_thunk (gdbarch,
+ i386_in_indirect_branch_thunk);
}
/* System V Release 4 (SVR4). */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index c1db689c1cc..9e5e0639ceb 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -6565,6 +6565,17 @@ process_event_stop_test (struct execution_control_state *ecs)
return;
}
+ /* Step through an indirect branch thunk. */
+ if (ecs->event_thread->control.step_over_calls != STEP_OVER_NONE
+ && gdbarch_in_indirect_branch_thunk (gdbarch, stop_pc))
+ {
+ if (debug_infrun)
+ fprintf_unfiltered (gdb_stdlog,
+ "infrun: stepped into indirect branch thunk\n");
+ keep_going (ecs);
+ return;
+ }
+
if (ecs->event_thread->control.step_range_end != 1
&& (ecs->event_thread->control.step_over_calls == STEP_OVER_UNDEBUGGABLE
|| ecs->event_thread->control.step_over_calls == STEP_OVER_ALL)
diff --git a/gdb/testsuite/ChangeLog b/gdb/testsuite/ChangeLog
index d7722a8b381..0dc05dd8649 100644
--- a/gdb/testsuite/ChangeLog
+++ b/gdb/testsuite/ChangeLog
@@ -1,3 +1,10 @@
+2018-04-13 Markus Metzger <markus.t.metzger@intel.com>
+
+ * gdb.base/step-indirect-call-thunk.exp: New.
+ * gdb.base/step-indirect-call-thunk.c: New.
+ * gdb.reverse/step-indirect-call-thunk.exp: New.
+ * gdb.reverse/step-indirect-call-thunk.c: New.
+
2018-04-11 Simon Marchi <simon.marchi@ericsson.com>
* gdb.base/pie-fork.c: New file.
diff --git a/gdb/testsuite/gdb.base/step-indirect-call-thunk.c b/gdb/testsuite/gdb.base/step-indirect-call-thunk.c
new file mode 100644
index 00000000000..bd22ea6924a
--- /dev/null
+++ b/gdb/testsuite/gdb.base/step-indirect-call-thunk.c
@@ -0,0 +1,42 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2018 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/>.
+
+*/
+
+static int
+inc (int x)
+{ /* inc.1 */
+ return x + 1; /* inc.2 */
+} /* inc.3 */
+
+static int
+thrice (int (*op)(int), int x)
+{ /* thrice.1 */
+ x = op (x); /* thrice.2 */
+ x = op (x); /* thrice.3 */
+ return op (x); /* thrice.4 */
+} /* thrice.5 */
+
+int
+main ()
+{
+ int x;
+
+ x = thrice (inc, 40);
+
+ return x;
+}
diff --git a/gdb/testsuite/gdb.base/step-indirect-call-thunk.exp b/gdb/testsuite/gdb.base/step-indirect-call-thunk.exp
new file mode 100644
index 00000000000..baeb6d6b8ed
--- /dev/null
+++ b/gdb/testsuite/gdb.base/step-indirect-call-thunk.exp
@@ -0,0 +1,73 @@
+# Copyright 2018 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/>.
+
+standard_testfile
+
+set cflags "-mindirect-branch=thunk -mfunction-return=thunk"
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+ [list debug "additional_flags=$cflags"]] } {
+ return -1
+}
+
+if { ![runto_main] } {
+ untested "failed to run to main"
+ return -1
+}
+
+# Do repeated instruction steps in order to reach TARGET from CURRENT
+#
+# CURRENT is a string matching the current location
+# TARGET is a string matching the target location
+# TEST is the test name
+#
+# The function issues repeated "stepi" commands as long as the location
+# matches CURRENT up to a maximum of 100 steps.
+#
+# TEST passes if the resulting location matches TARGET and fails
+# otherwise.
+#
+proc stepi_until { current target test } {
+ global gdb_prompt
+
+ set count 0
+ gdb_test_multiple "stepi" "$test" {
+ -re "$current.*$gdb_prompt $" {
+ incr count
+ if { $count < 100 } {
+ send_gdb "stepi\n"
+ exp_continue
+ } else {
+ fail "$test"
+ }
+ }
+ -re "$target.*$gdb_prompt $" {
+ pass "$test"
+ }
+ }
+}
+
+# Normal stepping steps through all thunks.
+gdb_test "step" "thrice\.2.*" "step into thrice"
+gdb_test "next" "thrice\.3.*" "step through thunks and over inc"
+gdb_test "step" "inc\.2.*" "step through call thunk into inc"
+gdb_test "step" "inc\.3.*" "step inside inc"
+gdb_test "step" "thrice\.4.*" "step through return thunk back into thrice"
+
+# We can use instruction stepping to step into thunks.
+stepi_until "thrice" "indirect_thunk" "stepi into call thunk"
+stepi_until "indirect_thunk" "inc." "stepi out of call thunk into inc"
+stepi_until "inc" "return_thunk" "stepi into return thunk"
+stepi_until "return_thunk" "thrice" \
+ "stepi out of return thunk back into thrice"
diff --git a/gdb/testsuite/gdb.reverse/step-indirect-call-thunk.c b/gdb/testsuite/gdb.reverse/step-indirect-call-thunk.c
new file mode 100644
index 00000000000..85464c31088
--- /dev/null
+++ b/gdb/testsuite/gdb.reverse/step-indirect-call-thunk.c
@@ -0,0 +1,36 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2018 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/>.
+
+*/
+
+static int
+inc (int x)
+{ /* inc.1 */
+ return x + 1; /* inc.2 */
+} /* inc.3 */
+
+static int
+apply (int (*op)(int), int x)
+{ /* apply.1 */
+ return op (x); /* apply.2 */
+} /* apply.3 */
+
+int
+main ()
+{ /* main.1 */
+ return apply (inc, 41); /* main.2 */
+} /* main.3 */
diff --git a/gdb/testsuite/gdb.reverse/step-indirect-call-thunk.exp b/gdb/testsuite/gdb.reverse/step-indirect-call-thunk.exp
new file mode 100644
index 00000000000..bb7fbf6e104
--- /dev/null
+++ b/gdb/testsuite/gdb.reverse/step-indirect-call-thunk.exp
@@ -0,0 +1,100 @@
+# Copyright 2018 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/>.
+
+if { ![supports_reverse] } {
+ untested "target does not support record"
+ return -1
+}
+
+standard_testfile
+
+set cflags "-mindirect-branch=thunk -mfunction-return=thunk"
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+ [list debug "additional_flags=$cflags"]] } {
+ return -1
+}
+
+if { ![runto_main] } {
+ untested "failed to run to main"
+ return -1
+}
+
+# Do repeated stepping COMMANDs in order to reach TARGET from CURRENT
+#
+# COMMAND is a stepping command
+# CURRENT is a string matching the current location
+# TARGET is a string matching the target location
+# TEST is the test name
+#
+# The function issues repeated COMMANDs as long as the location matches
+# CURRENT up to a maximum of 100 steps.
+#
+# TEST passes if the resulting location matches TARGET and fails
+# otherwise.
+#
+proc step_until { command current target test } {
+ global gdb_prompt
+
+ set count 0
+ gdb_test_multiple "$command" "$test" {
+ -re "$current.*$gdb_prompt $" {
+ incr count
+ if { $count < 100 } {
+ send_gdb "$command\n"
+ exp_continue
+ } else {
+ fail "$test"
+ }
+ }
+ -re "$target.*$gdb_prompt $" {
+ pass "$test"
+ }
+ }
+}
+
+gdb_test_no_output "record"
+gdb_test "next" ".*" "record trace"
+
+# Normal stepping steps through all thunks.
+gdb_test "reverse-step" "apply\.3.*" "reverse-step into apply"
+gdb_test "reverse-step" "inc\.3.*" "reverse-step into inc"
+gdb_test "reverse-step" "inc\.2.*" "reverse-step inside inc"
+gdb_test "reverse-step" "apply\.2.*" \
+ "reverse-step through call thunk into apply"
+gdb_test "reverse-step" "main\.2.*" "reverse-step into main"
+gdb_test "step" "apply\.2.*" "step into apply"
+gdb_test "step" "inc\.2.*" "step through call thunk into inc"
+gdb_test "reverse-step" "apply\.2.*" \
+ "reverse-step through call thunk into apply"
+gdb_test "next" "apply\.3.*" "step through thunks and over inc"
+gdb_test "reverse-next" "apply\.2.*" \
+ "reverse-step through thunks and over inc"
+
+# We can use instruction stepping to step into thunks.
+step_until "stepi" "apply\.2" "indirect_thunk" "stepi into call thunk"
+step_until "stepi" "indirect_thunk" "inc" \
+ "stepi out of call thunk into inc"
+step_until "stepi" "inc" "return_thunk" "stepi into return thunk"
+step_until "stepi" "return_thunk" "apply" \
+ "stepi out of return thunk back into apply"
+
+step_until "reverse-stepi" "apply" "return_thunk" \
+ "reverse-stepi into return thunk"
+step_until "reverse-stepi" "return_thunk" "inc" \
+ "reverse-stepi out of return thunk into inc"
+step_until "reverse-stepi" "inc" "indirect_thunk" \
+ "reverse-stepi into call thunk"
+step_until "reverse-stepi" "indirect_thunk" "apply" \
+ "reverse-stepi out of call thunk into apply"
diff --git a/gdb/x86-tdep.c b/gdb/x86-tdep.c
new file mode 100644
index 00000000000..3e665c2be4f
--- /dev/null
+++ b/gdb/x86-tdep.c
@@ -0,0 +1,76 @@
+/* Target-dependent code for X86-based targets.
+
+ Copyright (C) 2018 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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 "defs.h"
+#include "x86-tdep.h"
+
+
+/* Check whether NAME is included in NAMES[LO] (inclusive) to NAMES[HI]
+ (exclusive). */
+
+static bool
+x86_is_thunk_register_name (const char *name, const char **names, int lo,
+ int hi)
+{
+ int reg;
+ for (reg = lo; reg < hi; ++reg)
+ if (strcmp (name, names[reg]) == 0)
+ return true;
+
+ return false;
+}
+
+/* See x86-tdep.h. */
+
+bool
+x86_in_indirect_branch_thunk (CORE_ADDR pc, const char **register_names,
+ int lo, int hi)
+{
+ struct bound_minimal_symbol bmfun = lookup_minimal_symbol_by_pc (pc);
+ if (bmfun.minsym == nullptr)
+ return false;
+
+ const char *name = MSYMBOL_LINKAGE_NAME (bmfun.minsym);
+ if (name == nullptr)
+ return false;
+
+ /* Check the indirect return thunk first. */
+ if (strcmp (name, "__x86_return_thunk") == 0)
+ return true;
+
+ /* Then check a family of indirect call/jump thunks. */
+ static const char thunk[] = "__x86_indirect_thunk";
+ static const size_t length = sizeof (thunk) - 1;
+ if (strncmp (name, thunk, length) != 0)
+ return false;
+
+ /* If that's the complete name, we're in the memory thunk. */
+ name += length;
+ if (*name == '\0')
+ return true;
+
+ /* Check for suffixes. */
+ if (*name++ != '_')
+ return false;
+
+ if (x86_is_thunk_register_name (name, register_names, lo, hi))
+ return true;
+
+ return false;
+}
diff --git a/gdb/x86-tdep.h b/gdb/x86-tdep.h
new file mode 100644
index 00000000000..807ad1d2b32
--- /dev/null
+++ b/gdb/x86-tdep.h
@@ -0,0 +1,30 @@
+/* Target-dependent code for X86-based targets.
+
+ Copyright (C) 2018 Free Software Foundation, Inc.
+
+ This file is part of GDB.
+
+ 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/>. */
+
+#ifndef X86_TDEP_H
+#define X86_TDEP_H
+
+/* Checks whether PC lies in an indirect branch thunk using registers
+ REGISTER_NAMES[LO] (inclusive) to REGISTER_NAMES[HI] (exclusive). */
+
+extern bool x86_in_indirect_branch_thunk (CORE_ADDR pc,
+ const char **register_names,
+ int lo, int hi);
+
+#endif /* x86-tdep.h */