[v3,3/4] gdb/infrun: handle stepping through functions with DW_AT_trampoline

Message ID 20230710225643.32280-4-abdul.b.ijaz@intel.com
State New
Headers
Series GDB support for DW_AT_trampoline |

Commit Message

Ijaz, Abdul B July 10, 2023, 10:56 p.m. UTC
  From: Nils-Christian Kempke <nils-christian.kempke@intel.com>

This patch makes infrun continue stepping into and through trampoline
functions marked via the DW_AT_trampoline in DWARF.  The attribute can
be emitted by the compiler for certain subroutines/inlined subroutines
that are compiler generated and should be hidden from a user.

Mainly, infrun is modified in 3 ways.

First, GDB will now attempt to step through trampoline functions.
Whenever we issued a step command that would make GDB step into a
function that is marked trampoline, GDB will try to step directly towards
the trampoline's 'target' instead and, e.g., not stop at the first
instruction of the trampoline.
The target can be specified by the compiler by the value of
DW_AT_trampoline if its form is either an address, a name, or a DIE
reference.  DW_AT_trampoline is also allowed to be specified as a flag
(containing true or false), in which case the target is assumed to be
unknown.  If GDB successfully finds a target, so if the value of
DW_AT_trampoline was not a flag and could be resolved successfully, GDB
steps directly towards the target and through the trampoline, hiding
the trampoline from the user.  If GDB cannot, however deduce a target,
most likely because the DW_AT_trampoline was given as a flag or because
of broken debug info, it will instead continue inside execution in the
trampoline function until it reaches an instruction that is not
associated with a trampoline function, which is usually the target
function.  It will then stop and give control back to the user.
It should be noted, that there might be the cases, where trampolines
call functions other than the target before the actual target call.  If,
in such a situation, GDB fails to resolve the target, it would resume
execution until stepping into this other function call, and hand back
control to the user, without actually having reached the target.  A
second step would have to be issued by the user to arrive a the target
(by resuming in the trampoline and then until leaving it a second time).
As this is a rather pathological case and no real instance of this is
known, I think the current behavior here is good enough and seems to be
the best GDB can do in such a situation.

Secondly, as trampoline functions normally do not have any real source
code correlation, it is likely that they mostly appear without line
info.
Normally, GDB would skip completely over a function call to a function
that has no source line information, so we would never get to the
aforementioned stepping through a trampoline and target resolution.  To
remedy this, for debug info trampolines, GDB now attempts to step through
them regardless of them having source line information or not.  So
issuing a step at a function call wrapped by a trampoline without source
line information will no longer skip the whole function call, but now
step through the trampoline and attempt to resolve the trampoline target
as described above (so usually, a single step at the call site will step
through the trampoline and towards the target, even if the trampoline had
not source line info).

Last, in all other cases when GDB is about to stop at a location that is
included in a trampoline region (e.g. after a step from the target back
into the trampoline) GDB will instead continue until the trampoline
region is left again and only then give control back to the user.  This
change serves the purpose of allowing stepping back from a target call
through the trampoline without the user noticing the artificial function
call inbetween call site and target.

Together, these changes attempt to hide the trampoline function from the
user while stepping.  Additionally, the skip-trampoline-functions option
has been introduced in infrun.  It is set by default, and, when turned
off, GDB will return to its 'normal' stepping behavior and ignore any
possible DW_AT_trampoline.

As currently only ifx emits the DW_AT_trampoline tag, a test has been
added to gdb.dwarf2 that artificially creates a set of trampoline
functions.

2023-07-10 Nils-Christian Kempke <nils-christian.kempke@intel.com>
---
 gdb/NEWS                                      |  15 ++
 gdb/doc/gdb.texinfo                           |  36 +++
 gdb/infrun.c                                  |  81 +++++-
 gdb/infrun.h                                  |   4 +
 .../gdb.dwarf2/dw2-function-trampolines.c     |  80 ++++++
 .../gdb.dwarf2/dw2-function-trampolines.exp   | 245 ++++++++++++++++++
 6 files changed, 457 insertions(+), 4 deletions(-)
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp
  

Comments

Eli Zaretskii July 11, 2023, 11:21 a.m. UTC | #1
> From: Abdul Basit Ijaz <abdul.b.ijaz@intel.com>
> Cc: abdul.b.ijaz@intel.com,
> 	JiniSusan.George@amd.com,
> 	tom@tromey.com,
> 	eliz@gnu.org,
> 	Nils-Christian Kempke <nils-christian.kempke@intel.com>
> Date: Tue, 11 Jul 2023 00:56:42 +0200
> 
> From: Nils-Christian Kempke <nils-christian.kempke@intel.com>
> 
> This patch makes infrun continue stepping into and through trampoline
> functions marked via the DW_AT_trampoline in DWARF.  The attribute can
> be emitted by the compiler for certain subroutines/inlined subroutines
> that are compiler generated and should be hidden from a user.
> 
> Mainly, infrun is modified in 3 ways.
> 
> First, GDB will now attempt to step through trampoline functions.
> Whenever we issued a step command that would make GDB step into a
> function that is marked trampoline, GDB will try to step directly towards
> the trampoline's 'target' instead and, e.g., not stop at the first
> instruction of the trampoline.
> The target can be specified by the compiler by the value of
> DW_AT_trampoline if its form is either an address, a name, or a DIE
> reference.  DW_AT_trampoline is also allowed to be specified as a flag
> (containing true or false), in which case the target is assumed to be
> unknown.  If GDB successfully finds a target, so if the value of
> DW_AT_trampoline was not a flag and could be resolved successfully, GDB
> steps directly towards the target and through the trampoline, hiding
> the trampoline from the user.  If GDB cannot, however deduce a target,
> most likely because the DW_AT_trampoline was given as a flag or because
> of broken debug info, it will instead continue inside execution in the
> trampoline function until it reaches an instruction that is not
> associated with a trampoline function, which is usually the target
> function.  It will then stop and give control back to the user.
> It should be noted, that there might be the cases, where trampolines
> call functions other than the target before the actual target call.  If,
> in such a situation, GDB fails to resolve the target, it would resume
> execution until stepping into this other function call, and hand back
> control to the user, without actually having reached the target.  A
> second step would have to be issued by the user to arrive a the target
> (by resuming in the trampoline and then until leaving it a second time).
> As this is a rather pathological case and no real instance of this is
> known, I think the current behavior here is good enough and seems to be
> the best GDB can do in such a situation.
> 
> Secondly, as trampoline functions normally do not have any real source
> code correlation, it is likely that they mostly appear without line
> info.
> Normally, GDB would skip completely over a function call to a function
> that has no source line information, so we would never get to the
> aforementioned stepping through a trampoline and target resolution.  To
> remedy this, for debug info trampolines, GDB now attempts to step through
> them regardless of them having source line information or not.  So
> issuing a step at a function call wrapped by a trampoline without source
> line information will no longer skip the whole function call, but now
> step through the trampoline and attempt to resolve the trampoline target
> as described above (so usually, a single step at the call site will step
> through the trampoline and towards the target, even if the trampoline had
> not source line info).
> 
> Last, in all other cases when GDB is about to stop at a location that is
> included in a trampoline region (e.g. after a step from the target back
> into the trampoline) GDB will instead continue until the trampoline
> region is left again and only then give control back to the user.  This
> change serves the purpose of allowing stepping back from a target call
> through the trampoline without the user noticing the artificial function
> call inbetween call site and target.
> 
> Together, these changes attempt to hide the trampoline function from the
> user while stepping.  Additionally, the skip-trampoline-functions option
> has been introduced in infrun.  It is set by default, and, when turned
> off, GDB will return to its 'normal' stepping behavior and ignore any
> possible DW_AT_trampoline.
> 
> As currently only ifx emits the DW_AT_trampoline tag, a test has been
> added to gdb.dwarf2 that artificially creates a set of trampoline
> functions.
> 
> 2023-07-10 Nils-Christian Kempke <nils-christian.kempke@intel.com>
> ---
>  gdb/NEWS                                      |  15 ++
>  gdb/doc/gdb.texinfo                           |  36 +++
>  gdb/infrun.c                                  |  81 +++++-
>  gdb/infrun.h                                  |   4 +
>  .../gdb.dwarf2/dw2-function-trampolines.c     |  80 ++++++
>  .../gdb.dwarf2/dw2-function-trampolines.exp   | 245 ++++++++++++++++++
>  6 files changed, 457 insertions(+), 4 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c
>  create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp

Thanks, the documentation parts are okay.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
  
Guinevere Larsen July 27, 2023, 11:46 a.m. UTC | #2
On 11/07/2023 00:56, Abdul Basit Ijaz via Gdb-patches wrote:
> From: Nils-Christian Kempke <nils-christian.kempke@intel.com>
>
> This patch makes infrun continue stepping into and through trampoline
> functions marked via the DW_AT_trampoline in DWARF.  The attribute can
> be emitted by the compiler for certain subroutines/inlined subroutines
> that are compiler generated and should be hidden from a user.
>
> Mainly, infrun is modified in 3 ways.
>
> First, GDB will now attempt to step through trampoline functions.
> Whenever we issued a step command that would make GDB step into a
> function that is marked trampoline, GDB will try to step directly towards
> the trampoline's 'target' instead and, e.g., not stop at the first
> instruction of the trampoline.
> The target can be specified by the compiler by the value of
> DW_AT_trampoline if its form is either an address, a name, or a DIE
> reference.  DW_AT_trampoline is also allowed to be specified as a flag
> (containing true or false), in which case the target is assumed to be
> unknown.  If GDB successfully finds a target, so if the value of
> DW_AT_trampoline was not a flag and could be resolved successfully, GDB
> steps directly towards the target and through the trampoline, hiding
> the trampoline from the user.  If GDB cannot, however deduce a target,
> most likely because the DW_AT_trampoline was given as a flag or because
> of broken debug info, it will instead continue inside execution in the
> trampoline function until it reaches an instruction that is not
> associated with a trampoline function, which is usually the target
> function.  It will then stop and give control back to the user.
> It should be noted, that there might be the cases, where trampolines
> call functions other than the target before the actual target call.  If,
> in such a situation, GDB fails to resolve the target, it would resume
> execution until stepping into this other function call, and hand back
> control to the user, without actually having reached the target.  A
> second step would have to be issued by the user to arrive a the target
> (by resuming in the trampoline and then until leaving it a second time).
> As this is a rather pathological case and no real instance of this is
> known, I think the current behavior here is good enough and seems to be
> the best GDB can do in such a situation.
>
> Secondly, as trampoline functions normally do not have any real source
> code correlation, it is likely that they mostly appear without line
> info.
> Normally, GDB would skip completely over a function call to a function
> that has no source line information, so we would never get to the
> aforementioned stepping through a trampoline and target resolution.  To
> remedy this, for debug info trampolines, GDB now attempts to step through
> them regardless of them having source line information or not.  So
> issuing a step at a function call wrapped by a trampoline without source
> line information will no longer skip the whole function call, but now
> step through the trampoline and attempt to resolve the trampoline target
> as described above (so usually, a single step at the call site will step
> through the trampoline and towards the target, even if the trampoline had
> not source line info).
>
> Last, in all other cases when GDB is about to stop at a location that is
> included in a trampoline region (e.g. after a step from the target back
> into the trampoline) GDB will instead continue until the trampoline
> region is left again and only then give control back to the user.  This
> change serves the purpose of allowing stepping back from a target call
> through the trampoline without the user noticing the artificial function
> call inbetween call site and target.
>
> Together, these changes attempt to hide the trampoline function from the
> user while stepping.  Additionally, the skip-trampoline-functions option
> has been introduced in infrun.  It is set by default, and, when turned
> off, GDB will return to its 'normal' stepping behavior and ignore any
> possible DW_AT_trampoline.

I really like this feature. Unfortunately, this isn't working when first 
entering a trampoline in reverse stepping. ie:

➜  gdb ./gdb -q 
testsuite/outputs/gdb.dwarf2/dw2-function-trampolines/dw2-function-trampolines
Reading symbols from 
testsuite/outputs/gdb.dwarf2/dw2-function-trampolines/dw2-function-trampolines...
(gdb) start
Temporary breakpoint 1, main ()
     at 
/home/blarsen/Documents/fsf_build/gdb/testsuite/../../../binutils-gdb/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c:69
warning: Source file is more recent than executable.
69        global_var = 0;                               /* main set 
global_var */
(gdb) record
(gdb) n
73        ans = trampoline ();                          /* main call 
trampoline */
(gdb) n
75        ans = chained_trampoline ();                  /* main call 
chained_trampoline */
(gdb) rs
0x000000000040114b in trampoline ()
(gdb)
Single stepping until exit from function trampoline,
which has no line number information.
target ()
     at 
/home/blarsen/Documents/fsf_build/gdb/testsuite/../../../binutils-gdb/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c:28
28        return 9 + 10;                                /* target return */
(gdb)
26        ++global_var;                                 /* target add */
(gdb)
main ()
     at 
/home/blarsen/Documents/fsf_build/gdb/testsuite/../../../binutils-gdb/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c:73
73        ans = trampoline ();                          /* main call 
trampoline */
(gdb)

I think this should be fixed before this patch went in.
  
Terekhov, Mikhail via Gdb-patches July 29, 2023, 11:03 a.m. UTC | #3
Thanks a lot Bruno for the feedback and catching the problem in reverse stepping for trampolines.

> I really like this feature. Unfortunately, this isn't working when first entering a trampoline in reverse stepping. ie:
Will fix the problem in V4 patch series. We should avoid processing the targets for trampoline calls in the reverse direction and just keep going in "infrunc.c\process_event_stop_test function". So hopefully this should be fixed by the V4 series of patch. So will test it then hopefully soon I will push it for review.

Thanks & Best Regards
Abdul Basit


-----Original Message-----
From: Bruno Larsen <blarsen@redhat.com> 
Sent: Thursday, July 27, 2023 1:47 PM
To: Ijaz, Abdul B <abdul.b.ijaz@intel.com>; gdb-patches@sourceware.org
Cc: JiniSusan.George@amd.com; tom@tromey.com; eliz@gnu.org; Nils-Christian Kempke <nils-christian.kempke@intel.com>
Subject: Re: [PATCH v3 3/4] gdb/infrun: handle stepping through functions with DW_AT_trampoline

On 11/07/2023 00:56, Abdul Basit Ijaz via Gdb-patches wrote:
> From: Nils-Christian Kempke <nils-christian.kempke@intel.com>
>
> This patch makes infrun continue stepping into and through trampoline 
> functions marked via the DW_AT_trampoline in DWARF.  The attribute can 
> be emitted by the compiler for certain subroutines/inlined subroutines 
> that are compiler generated and should be hidden from a user.
>
> Mainly, infrun is modified in 3 ways.
>
> First, GDB will now attempt to step through trampoline functions.
> Whenever we issued a step command that would make GDB step into a 
> function that is marked trampoline, GDB will try to step directly 
> towards the trampoline's 'target' instead and, e.g., not stop at the 
> first instruction of the trampoline.
> The target can be specified by the compiler by the value of 
> DW_AT_trampoline if its form is either an address, a name, or a DIE 
> reference.  DW_AT_trampoline is also allowed to be specified as a flag 
> (containing true or false), in which case the target is assumed to be 
> unknown.  If GDB successfully finds a target, so if the value of 
> DW_AT_trampoline was not a flag and could be resolved successfully, 
> GDB steps directly towards the target and through the trampoline, 
> hiding the trampoline from the user.  If GDB cannot, however deduce a 
> target, most likely because the DW_AT_trampoline was given as a flag 
> or because of broken debug info, it will instead continue inside 
> execution in the trampoline function until it reaches an instruction 
> that is not associated with a trampoline function, which is usually 
> the target function.  It will then stop and give control back to the user.
> It should be noted, that there might be the cases, where trampolines 
> call functions other than the target before the actual target call.  
> If, in such a situation, GDB fails to resolve the target, it would 
> resume execution until stepping into this other function call, and 
> hand back control to the user, without actually having reached the 
> target.  A second step would have to be issued by the user to arrive a 
> the target (by resuming in the trampoline and then until leaving it a second time).
> As this is a rather pathological case and no real instance of this is 
> known, I think the current behavior here is good enough and seems to 
> be the best GDB can do in such a situation.
>
> Secondly, as trampoline functions normally do not have any real source 
> code correlation, it is likely that they mostly appear without line 
> info.
> Normally, GDB would skip completely over a function call to a function 
> that has no source line information, so we would never get to the 
> aforementioned stepping through a trampoline and target resolution.  
> To remedy this, for debug info trampolines, GDB now attempts to step 
> through them regardless of them having source line information or not.  
> So issuing a step at a function call wrapped by a trampoline without 
> source line information will no longer skip the whole function call, 
> but now step through the trampoline and attempt to resolve the 
> trampoline target as described above (so usually, a single step at the 
> call site will step through the trampoline and towards the target, 
> even if the trampoline had not source line info).
>
> Last, in all other cases when GDB is about to stop at a location that 
> is included in a trampoline region (e.g. after a step from the target 
> back into the trampoline) GDB will instead continue until the 
> trampoline region is left again and only then give control back to the 
> user.  This change serves the purpose of allowing stepping back from a 
> target call through the trampoline without the user noticing the 
> artificial function call inbetween call site and target.
>
> Together, these changes attempt to hide the trampoline function from 
> the user while stepping.  Additionally, the skip-trampoline-functions 
> option has been introduced in infrun.  It is set by default, and, when 
> turned off, GDB will return to its 'normal' stepping behavior and 
> ignore any possible DW_AT_trampoline.

I really like this feature. Unfortunately, this isn't working when first entering a trampoline in reverse stepping. ie:

➜  gdb ./gdb -q
testsuite/outputs/gdb.dwarf2/dw2-function-trampolines/dw2-function-trampolines
Reading symbols from
testsuite/outputs/gdb.dwarf2/dw2-function-trampolines/dw2-function-trampolines...
(gdb) start
Temporary breakpoint 1, main ()
     at
/home/blarsen/Documents/fsf_build/gdb/testsuite/../../../binutils-gdb/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c:69
warning: Source file is more recent than executable.
69        global_var = 0;                               /* main set global_var */
(gdb) record
(gdb) n
73        ans = trampoline ();                          /* main call trampoline */
(gdb) n
75        ans = chained_trampoline ();                  /* main call chained_trampoline */
(gdb) rs
0x000000000040114b in trampoline ()
(gdb)
Single stepping until exit from function trampoline, which has no line number information.
target ()
     at
/home/blarsen/Documents/fsf_build/gdb/testsuite/../../../binutils-gdb/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c:28
28        return 9 + 10;                                /* target return */
(gdb)
26        ++global_var;                                 /* target add */
(gdb)
main ()
     at
/home/blarsen/Documents/fsf_build/gdb/testsuite/../../../binutils-gdb/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c:73
73        ans = trampoline ();                          /* main call trampoline */
(gdb)

I think this should be fixed before this patch went in.

--
Cheers,
Bruno

>
> As currently only ifx emits the DW_AT_trampoline tag, a test has been 
> added to gdb.dwarf2 that artificially creates a set of trampoline 
> functions.
>
> 2023-07-10 Nils-Christian Kempke <nils-christian.kempke@intel.com>
> ---
>   gdb/NEWS                                      |  15 ++
>   gdb/doc/gdb.texinfo                           |  36 +++
>   gdb/infrun.c                                  |  81 +++++-
>   gdb/infrun.h                                  |   4 +
>   .../gdb.dwarf2/dw2-function-trampolines.c     |  80 ++++++
>   .../gdb.dwarf2/dw2-function-trampolines.exp   | 245 ++++++++++++++++++
>   6 files changed, 457 insertions(+), 4 deletions(-)
>   create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c
>   create mode 100644 
> gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp
>
> diff --git a/gdb/NEWS b/gdb/NEWS
> index b924834d3d7..8bca5540f58 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -571,6 +571,21 @@ platform.
>   
>   * New commands
>   
> +set skip-trampoline-functions on|off
> +show skip-trampoline-functions
> +  This setting is 'on' by default.  When 'on' it controls whether 
> +GDB's
> +  stepping behavior will recognize function calls that have been 
> +marked as
> +  trampolines in the debug info.  It improves stepping behavior in 
> +that it
> +  steps through trampoline code and hides it from the user.  GDB can 
> +now step
> +  through trampolines that are correctly marked as such in the 
> +compiler's
> +  debug info.  If the target of a trampoline is unknown, GDB will 
> +continue
> +  until the trampoline section is left again and only then hand 
> +control back
> +  to the user.  GDB does this even if the trampoline has no 
> +associated line
> +  info.  If this is turned off, GDB will step into trampolines if 
> +there is
> +  line table information for them or step over the trampoline calls 
> +if there
> +  is no line table information.  Currently, only DWARF trampolines 
> +are
> +  supported.
> +
>   maint set backtrace-on-fatal-signal on|off
>   maint show backtrace-on-fatal-signal
>     This setting is 'on' by default.  When 'on' GDB will print a 
> limited diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 
> b10c06ae91f..839c3e6a0f8 100644
> --- a/gdb/doc/gdb.texinfo
> +++ b/gdb/doc/gdb.texinfo
> @@ -6391,6 +6391,42 @@ debug information.  This is the default.
>   Show whether @value{GDBN} will stop in or step over functions without
>   source line debug information.
>   
> +@kindex set skip-trampoline-functions @cindex trampoline functions 
> +@cindex stepping through trampoline functions @item set 
> +skip-trampoline-functions @itemx set skip-trampoline-functions on 
> +When calling a function in any language, some compilers might 
> +generate so-called @dfn{trampoline functions}, which wrap the actual 
> +function call (the target of the trampoline).  The compiler might 
> +mark such a trampoline in its debug information.  Often, such 
> +trampolines do not have any source line information associated with 
> +them which will lead the @code{step} command to behave like a @code{next} and skip the function call completely.
> +
> +The default is @code{set skip-trampoline-functions on} and it will 
> +cause the @code{step} command to treat these trampolines differently.  
> +When issuing a @code{step} at the call site of a trampoline function 
> +if @code{skip-trampoline-functions} is set @value{GDBN} will attempt 
> +to determine the target of the trampoline and then step through the 
> +trampoline stopping at the target.  If the target could not be found 
> +or was not given in the debug info, @value{GDBN} will simply continue 
> +execution until it leaves the trampoline code again, even if the 
> +trampoline has no line info associated with it.  When returning from 
> +a target function call and stepping back into the trampoline, 
> +@value{GDBN} will again step through the trampoline towards the call 
> +site.  Additionally, even if stopped in a trampoline function with 
> +source line information, issuing a @code{step} will prompt 
> +@value{GDBN} to resume execution until leaving the trampoline region 
> +again.  The @code{stepi} command is not affected by the setting which is enabled by default.  Currently, only DWARF trampolines marked via DW_AT_trampoline are supported by this.
> +
> +@item set skip-trampoline-functions off Causes the @code{step} 
> +command to completely ignore any trampoline information a compiler 
> +might have emitted in its debug info.  Trampolines will be treated 
> +like any other function when stepping.
> +
> +@item show skip-trampoline-functions
> +Show whether @value{GDBN} tries to skip trampolines or not.
> +
>   @kindex finish
>   @kindex fin @r{(@code{finish})}
>   @item finish
> diff --git a/gdb/infrun.c b/gdb/infrun.c index 
> 58da1cef29e..dfafe7cdc74 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -184,6 +184,12 @@ get_previous_thread ()
>   
>   static bool detach_fork = true;
>   
> +/* If set (default) GDB will step through functions/inlined subroutines marked
> +   DW_AT_trampoline by the compiler.  If false, GDB will ignore the
> +   attribute.  */
> +
> +static bool skip_trampoline_functions = true;
> +
>   bool debug_infrun = false;
>   static void
>   show_debug_infrun (struct ui_file *file, int from_tty, @@ -7265,7 
> +7271,6 @@ process_event_stop_test (struct execution_control_state *ecs)
>   		  != find_pc_function (ecs->event_thread->stop_pc ())))))
>       {
>         CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
> -      CORE_ADDR real_stop_pc;
>   
>         infrun_debug_printf ("stepped into subroutine");
>   
> @@ -7337,8 +7342,41 @@ process_event_stop_test (struct execution_control_state *ecs)
>   	 calling routine and the real function), locate the real
>   	 function.  That's what tells us (a) whether we want to step
>   	 into it at all, and (b) what prologue we want to run to the
> -	 end of, if we do step into it.  */
> -      real_stop_pc = skip_language_trampoline (frame, stop_pc);
> +	 end of, if we do step into it.  For functions marked as
> +	 trampoline functions we try to find their target and step
> +	 towards it (if skip_trampoline_functions is not set to false by the
> +	 user).  If no target can be determined we just step into the
> +	 trampoline and hand control back to the user.  */
> +      CORE_ADDR real_stop_pc = 0;
> +      bool in_trampoline = skip_trampoline_functions
> +			   && in_trampoline_function (stop_pc);
> +
> +      if (in_trampoline)
> +	{
> +	  real_stop_pc = find_function_trampoline_target (stop_pc);
> +
> +	  for (int i = 0; i < MAX_TRAMPOLINE_CHAIN_SIZE
> +			  && in_trampoline_function (real_stop_pc); ++i)
> +	    {
> +		real_stop_pc = find_function_trampoline_target (real_stop_pc);
> +		/* Exit if find_function_trampoline_target failed to find the
> +		   trampoline target.  Do not try to resolve the trampolines
> +		   in this case.  */
> +		if (real_stop_pc == 0x0)
> +		  break;
> +	    }
> +
> +	  /* If we failed to find a target we will just single step in the
> +	     hope of leaving the trampoline again soon.  */
> +	  if (real_stop_pc == 0x0)
> +	    {
> +	      keep_going (ecs);
> +	      return;
> +	    }
> +	}
> +
> +      if (real_stop_pc == 0)
> +	real_stop_pc = skip_language_trampoline (frame, stop_pc);
>         if (real_stop_pc == 0)
>   	real_stop_pc = gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc);
>         if (real_stop_pc != 0)
> @@ -7359,6 +7397,8 @@ process_event_stop_test (struct execution_control_state *ecs)
>         /* If we have line number information for the function we are
>   	 thinking of stepping into and the function isn't on the skip
>   	 list, step into it.
> +	 If we are about to step into a function marked trampoline with no
> +	 line number information, we still want to enter it here.
>   
>   	 If there are several symtabs at that PC (e.g. with include
>   	 files), just want to know whether *any* of them have line @@ 
> -7367,7 +7407,9 @@ process_event_stop_test (struct execution_control_state *ecs)
>   	struct symtab_and_line tmp_sal;
>   
>   	tmp_sal = find_pc_line (ecs->stop_func_start, 0);
> -	if (tmp_sal.line != 0
> +	if ((tmp_sal.line != 0
> +	     || (skip_trampoline_functions
> +		 && in_trampoline_function (ecs->stop_func_start)))
>   	    && !function_name_is_marked_for_skip (ecs->stop_func_name,
>   						  tmp_sal)
>   	    && !inline_frame_is_marked_for_skip (true, ecs->event_thread)) 
> @@ -7520,6 +7562,19 @@ process_event_stop_test (struct execution_control_state *ecs)
>         return;
>       }
>   
> +  /* If we ended up in a function trampoline without stepping into a new
> +     function we are either in some inlined trampoline or returning through a
> +     trampoline function.  In either case we continue to single step until we
> +     are out of the trampoline code again.  This check has to be done before
> +     stop_pc_sal.line == 0 below, as trampolines usually don't have source
> +     line information associated with them.  */  if 
> + (skip_trampoline_functions && in_trampoline_function (stop_pc_sal.pc))
> +    {
> +      infrun_debug_printf ("stepped into trampoline code");
> +      keep_going (ecs);
> +      return;
> +    }
> +
>     if (stop_pc_sal.line == 0)
>       {
>         /* We have no line number information.  That means to stop @@ 
> -9731,6 +9786,14 @@ show_exec_direction_func (struct ui_file *out, int from_tty,
>     }
>   }
>   
> +static void
> +show_skip_trampoline_functions (ui_file *file, int from_tty,
> +				cmd_list_element *c,
> +				const char *value)
> +{
> +  gdb_printf (file, _("Skipping trampoline functions is %s.\n"), 
> +value); }
> +
>   static void
>   show_schedule_multiple (struct ui_file *file, int from_tty,
>   			struct cmd_list_element *c, const char *value) @@ -10076,6 
> +10139,16 @@ Options are 'forward' or 'reverse'."),
>   			set_exec_direction_func, show_exec_direction_func,
>   			&setlist, &showlist);
>   
> +  add_setshow_boolean_cmd ("skip-trampoline-functions", class_run,
> +			  &skip_trampoline_functions, _("\ Set whether gdb attempts to 
> +hide trampolines marked in the debug info."), _("\ Show whether gdb 
> +attempts to hide trampolines marked in the debug info."), _("\ If on, 
> +while stepping gdb will skip through functions and inlined 
> +functions\n\ marked as trampolines by the compiler.  If off, gdb will 
> +ignore such function\n\ trampolines."),
> +			  nullptr, show_skip_trampoline_functions, &setlist,
> +			  &showlist);
> +
>     /* Set/show detach-on-fork: user-settable mode.  */
>   
>     add_setshow_boolean_cmd ("detach-on-fork", class_run, 
> &detach_fork, _("\ diff --git a/gdb/infrun.h b/gdb/infrun.h index 
> a343d27f72d..d4a6b7892b5 100644
> --- a/gdb/infrun.h
> +++ b/gdb/infrun.h
> @@ -76,6 +76,10 @@ infrun_debug_show_threads (const char *title, ThreadRange threads)
>   }
>   
>   
> +/* Maximum size of trampoline chain to process while resolving
> +   trampolines.  */
> +#define MAX_TRAMPOLINE_CHAIN_SIZE 10
> +
>   /* Nonzero if we want to give control to the user when we're notified
>      of shared library events by the dynamic linker.  */
>   extern int stop_on_solib_events;
> diff --git a/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c 
> b/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c
> new file mode 100644
> index 00000000000..1f0eb2e0efa
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c
> @@ -0,0 +1,80 @@
> +/* Copyright 2023 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/>.  */
> +
> +/* This test relies on inlined_trampoline being inlined into main and the other
> +   functions not.  All functions except target will be marked via
> +   DW_AT_trampoline in the debug info and we'll check whether one can step
> +   through the trampolines towards target.  */ volatile int 
> +global_var;
> +
> +int __attribute__ ((noinline))
> +target ()					/* target decl line */
> +{						/* target prologue */
> +  asm ("target_label: .globl target_label");
> +  ++global_var;					/* target add */
> +  asm ("target_label2: .globl target_label2");
> +  return 9 + 10;				/* target return */
> +}						/* target end */
> +
> +int __attribute__ ((noinline))
> +trampoline ()
> +{						/* trampoline prologue */
> +  asm ("trampoline_label: .globl trampoline_label");
> +  ++global_var;
> +  return target ();				/* trampoline target call */
> +}						/* trampoline end */
> +
> +static inline int __attribute__ ((always_inline)) inlined_trampoline 
> +()
> +{						/* inlined_trampoline prologue */
> +  asm ("inlined_trampoline_label: .globl inlined_trampoline_label");
> +  ++global_var;					/* inlined_trampoline add */
> +  asm ("inlined_trampoline_label2: .globl inlined_trampoline_label2");
> +  return target ();				/* inlined_trampoline target call */
> +}						/* inlined_trampoline end */
> +
> +int __attribute__ ((noinline))
> +chained_trampoline ()
> +{						/* chained_trampoline prologue */
> +  asm ("chained_trampoline_label: .globl chained_trampoline_label");
> +  ++global_var;
> +  return trampoline ();				/* chained_trampoline trampoline call */
> +}						/* chained_trampoline end */
> +
> +int __attribute__ ((noinline))
> +doubly_chained_trampoline ()
> +{						/* doubly_chained_trampoline prologue */
> +  asm ("doubly_chained_trampoline_label: .globl 
> +doubly_chained_trampoline_label");
> +  ++global_var;
> +  return chained_trampoline ();			/* doubly_chained_trampoline chained_trampoline call */
> +}						/* doubly_chained_trampoline end */
> +
> +int
> +main ()						/* main decl line */
> +{						/* main prologue */
> +  int ans;
> +  asm ("main_label: .globl main_label");
> +  global_var = 0;				/* main set global_var */
> +  asm ("main_label2: .globl main_label2");
> +  ans = inlined_trampoline ();			/* main call inlined_trampoline */
> +  asm ("main_label3: .globl main_label3");
> +  ans = trampoline ();				/* main call trampoline */
> +  asm ("main_label4: .globl main_label4");
> +  ans = chained_trampoline ();			/* main call chained_trampoline */
> +  asm ("main_label5: .globl main_label5");
> +  ans = doubly_chained_trampoline ();		/* main call doubly_chained_trampoline */
> +  asm ("main_label6: .globl main_label6");
> +  return ans;					/* main call return */
> +}						/* main end */
> diff --git a/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp 
> b/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp
> new file mode 100644
> index 00000000000..67eec2b5540
> --- /dev/null
> +++ b/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp
> @@ -0,0 +1,245 @@
> +# Copyright 2023 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/>.
> +
> +# This test checks GDB's handling of functions/inlined functions 
> +marked # DW_AT_trampoline by the compiler.  A function marked as 
> +trampoline should # generally be hidden from the user.  We check 
> +whether we can step through # trampolines.  Every trampoline is 
> +defined using a different type for its # target: a string, an address, a DIE reference or a flag.
> +# Setting skip-trampoline-functions to false inside GDB should make 
> +it return # to its 'normal' behavior, ignore the DW_AT_trampoline, 
> +and skip all of the # non-inlined trampoline calls (as their DIEs 
> +don't have any source # information).
> +
> +load_lib dwarf.exp
> +
> +# This test can only be run on targets which support DWARF-2 and use gas.
> +if {![dwarf2_support]} {
> +    return 0
> +}
> +
> +standard_testfile .c .S
> +
> +set asm_file [standard_output_file $srcfile2] Dwarf::assemble 
> +$asm_file {
> +    global srcdir subdir srcfile
> +    declare_labels lines_label trampoline_label
> +
> +    get_func_info target
> +    get_func_info trampoline
> +    get_func_info chained_trampoline
> +    get_func_info doubly_chained_trampoline
> +    get_func_info main
> +
> +    set target_decl_line [gdb_get_line_number "target decl line"]
> +    set main_decl_line [gdb_get_line_number "main decl line"]
> +    set main_call_inlined_trampoline_line [gdb_get_line_number "main 
> + call inlined_trampoline"]
> +
> +    cu {} {
> +	compile_unit {
> +	    {language @DW_LANG_C}
> +	    {name dw2-function-trampolines.c}
> +	    {low_pc 0 addr}
> +	    {stmt_list ${lines_label} DW_FORM_sec_offset}
> +	} {
> +	    subprogram {
> +		{name target}
> +		{low_pc $target_start addr}
> +		{high_pc "$target_start + $target_len" addr}
> +		{decl_file 1 data1}
> +		{decl_line $target_decl_line data1}
> +	    }
> +	    # The 'trampoline' subprogram declares its target by name.
> +	    trampoline_label: subprogram {
> +		    {name trampoline}
> +		    {low_pc $trampoline_start addr}
> +		    {high_pc "$trampoline_start + $trampoline_len" addr}
> +		    {trampoline target string}
> +		}
> +	    # The 'chained_trampoline' subprogram declares its target as die
> +	    # reference.
> +	    subprogram {
> +		{name chained_trampoline}
> +		{low_pc $chained_trampoline_start addr}
> +		{high_pc "$chained_trampoline_start + $chained_trampoline_len" addr}
> +		{trampoline %$trampoline_label}
> +	    }
> +	    # The 'doubly_chained_trampoline' subprogram declares no target.
> +	    # Its DW_AT_trampoline is a flag set to true.
> +	    subprogram {
> +		{name doubly_chained_trampoline}
> +		{low_pc $doubly_chained_trampoline_start addr}
> +		{high_pc "$doubly_chained_trampoline_start + $doubly_chained_trampoline_len" addr}
> +		{trampoline 1 flag}
> +	    }
> +	    subprogram {
> +		{external 1 flag}
> +		{name main}
> +		{main_subprogram 1 flag}
> +		{low_pc $main_start addr}
> +		{high_pc "$main_start + $main_len" addr}
> +		{decl_file 1 data1}
> +		{decl_line $main_decl_line data1}
> +	    } {
> +		# The 'inlined_trampoline' subroutine declares its target as
> +		# an address.
> +		inlined_subroutine {
> +		    {name inlined_trampoline}
> +		    {low_pc main_label2 addr}
> +		    {high_pc main_label3 addr}
> +		    {trampoline $target_start addr}
> +		    {call_file 1 data1}
> +		    {call_line $main_call_inlined_trampoline_line data1}
> +		}
> +	    }
> +	}
> +    }
> +
> +    lines {version 2} lines_label {
> +	include_dir "${srcdir}/${subdir}"
> +	file_name "$srcfile" 1
> +
> +	program {
> +	    DW_LNE_set_address $main_start
> +	    line [gdb_get_line_number "main set global_var"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address main_label
> +	    line [gdb_get_line_number "main set global_var"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address main_label2
> +	    line [gdb_get_line_number "main call inlined_trampoline"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address inlined_trampoline_label
> +	    line [gdb_get_line_number "inlined_trampoline add"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address inlined_trampoline_label2
> +	    line [gdb_get_line_number "inlined_trampoline target call"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address main_label3
> +	    line [gdb_get_line_number "main call trampoline"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address main_label4
> +	    line [gdb_get_line_number "main call chained_trampoline"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address main_label5
> +	    line [gdb_get_line_number "main call doubly_chained_trampoline"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address main_label6
> +	    line [gdb_get_line_number "main call return"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address $main_end
> +	    DW_LNE_end_sequence
> +
> +	    DW_LNE_set_address $target_start
> +	    line [gdb_get_line_number "target prologue"]
> +	    DW_LNS_negate_stmt
> +	    DW_LNS_copy
> +	    DW_LNE_set_address target_label
> +	    line [gdb_get_line_number "target add"]
> +	    DW_LNS_negate_stmt
> +	    DW_LNS_copy
> +	    DW_LNE_set_address target_label2
> +	    line [gdb_get_line_number "target return"]
> +	    DW_LNS_copy
> +	    DW_LNE_set_address $target_end
> +	    DW_LNE_end_sequence
> +	}
> +    }
> +}
> +
> +if {[prepare_for_testing "failed to prepare" ${testfile} \
> +	[list $srcfile $asm_file] {nodebug additional_flags=-O0}]} {
> +    return -1
> +}
> +
> +set target_first_line_pattern ".*target add.*"
> +set target_second_line_pattern ".*target return.*"
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +gdb_test "show skip-trampoline-functions" \
> +    "Skipping trampoline functions is on\." \
> +    "check skip-trampoline-functions is enabled"
> +
> +with_test_prefix "with trampoline handling" {
> +    foreach {trampoline return_line} { "inlined_trampoline" "trampoline" \
> +	"trampoline" "chained_trampoline" \
> +	"chained_trampoline" "doubly_chained_trampoline" } {
> +
> +	gdb_test "s" "$target_first_line_pattern" "step through $trampoline"
> +	gdb_test "s" "$target_second_line_pattern" \
> +	    "step target second line from $trampoline"
> +	gdb_test "s" ".*main call $return_line.*" \
> +	    "step back through $trampoline"
> +    }
> +
> +    # The doubly_chained_trampoline has only been marked as trampoline but no
> +    # target was given.  In this case GDB steps into the trampoline and then
> +    # continues until the trampoline section is left again.
> +
> +    # When compiled with gcc 7.5 (and possibly others) on a 32 bit system, the
> +    # trampoline function contains a call to __x86.get_pc_thunk.ax before the
> +    # actual target call.  So, we end up in __x86.get_pc_thunk.ax.  Issuing a
> +    # second step command will return from the function call back into the
> +    # trampoline and go on inside the trampoline towards the actual target call.
> +    # On other targets we step directly towards the target call.
> +    gdb_test_multiple "s" "step through double_chained_trampoline" {
> +	-re -wrap "$target_first_line_pattern" {
> +	    pass $gdb_test_name
> +	}
> +	-re -wrap ".*__x86.get_pc_thunk.ax.*" {
> +	    gdb_test "s" "$target_first_line_pattern" \
> +		"step through double_chained_trampoline 2nd try"
> +	}
> +    }
> +    gdb_test "s" "$target_second_line_pattern" \
> +	"step target second line fromdoubly_chained_trampoline"
> +    gdb_test "s" ".*main call return.*" \
> +	"step back through doubly_chained_trampoline"
> +}
> +
> +clean_restart ${testfile}
> +
> +if ![runto_main] {
> +    return -1
> +}
> +
> +gdb_test_no_output "set skip-trampoline-functions off" \
> +    "disable trampoline handling"
> +gdb_test "show skip-trampoline-functions" \
> +    "Skipping trampoline functions is off." \
> +    "check skip-trampoline-functions is disabled"
> +
> +with_test_prefix "without trampoline handling" {
> +    gdb_test "s" ".*main call inlined_trampoline.*"
> +    gdb_test "s" ".*inlined_trampoline add.*" \
> +	"step into inlined_trampoline with skip-trampoline off"
> +    gdb_test "s" ".*inlined_trampoline target call.*" \
> +	"step in inlined_trampoline with skip-trampoline off"
> +    gdb_test "s" "$target_first_line_pattern" \
> +	"step into target with skip-trampoline off"
> +    gdb_test "s" "$target_second_line_pattern" \
> +	"step second line in target with skip-trampoline off"
> +    gdb_test "s" ".*main call trampoline.*" \
> +	"step brack from target with skip-trampoline off"
> +    gdb_test "s" ".*main call chained_trampoline.*" \
> +	"skip trampoline call with no line info"
> +    gdb_test "s" ".*main call doubly_chained_trampoline.*" \
> +	"skip chained_trampoline call with no line info"
> +    gdb_test "s" ".*main call return.*" \
> +	"skip doubly_chained_trampoline call with no line info"
> +}

Intel Deutschland GmbH
Registered Address: Am Campeon 10, 85579 Neubiberg, Germany
Tel: +49 89 99 8853-0, www.intel.de <http://www.intel.de>
Managing Directors: Christin Eisenschmid, Sharon Heck, Tiffany Doon Silva  
Chairperson of the Supervisory Board: Nicole Lau
Registered Office: Munich
Commercial Register: Amtsgericht Muenchen HRB 186928
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index b924834d3d7..8bca5540f58 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -571,6 +571,21 @@  platform.
 
 * New commands
 
+set skip-trampoline-functions on|off
+show skip-trampoline-functions
+  This setting is 'on' by default.  When 'on' it controls whether GDB's
+  stepping behavior will recognize function calls that have been marked as
+  trampolines in the debug info.  It improves stepping behavior in that it
+  steps through trampoline code and hides it from the user.  GDB can now step
+  through trampolines that are correctly marked as such in the compiler's
+  debug info.  If the target of a trampoline is unknown, GDB will continue
+  until the trampoline section is left again and only then hand control back
+  to the user.  GDB does this even if the trampoline has no associated line
+  info.  If this is turned off, GDB will step into trampolines if there is
+  line table information for them or step over the trampoline calls if there
+  is no line table information.  Currently, only DWARF trampolines are
+  supported.
+
 maint set backtrace-on-fatal-signal on|off
 maint show backtrace-on-fatal-signal
   This setting is 'on' by default.  When 'on' GDB will print a limited
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index b10c06ae91f..839c3e6a0f8 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -6391,6 +6391,42 @@  debug information.  This is the default.
 Show whether @value{GDBN} will stop in or step over functions without
 source line debug information.
 
+@kindex set skip-trampoline-functions
+@cindex trampoline functions
+@cindex stepping through trampoline functions
+@item set skip-trampoline-functions
+@itemx set skip-trampoline-functions on
+When calling a function in any language, some compilers might generate
+so-called @dfn{trampoline functions}, which wrap the actual function call
+(the target of the trampoline).  The compiler might mark such a trampoline in
+its debug information.  Often, such trampolines do not have any source line
+information associated with them which will lead the @code{step} command to
+behave like a @code{next} and skip the function call completely.
+
+The default is @code{set skip-trampoline-functions on} and it will cause the
+@code{step} command to treat these trampolines differently.  When issuing a
+@code{step} at the call site of a trampoline function if
+@code{skip-trampoline-functions} is set @value{GDBN} will attempt to determine
+the target of the trampoline and then step through the trampoline stopping at
+the target.  If the target could not be found or was not given in the debug
+info, @value{GDBN} will simply continue execution until it leaves the
+trampoline code again, even if the trampoline has no line info associated with
+it.  When returning from a target function call and stepping back into the
+trampoline, @value{GDBN} will again step through the trampoline towards the
+call site.  Additionally, even if stopped in a trampoline function with source
+line information, issuing a @code{step} will prompt @value{GDBN} to resume
+execution until leaving the trampoline region again.  The @code{stepi} command
+is not affected by the setting which is enabled by default.  Currently, only
+DWARF trampolines marked via DW_AT_trampoline are supported by this.
+
+@item set skip-trampoline-functions off
+Causes the @code{step} command to completely ignore any trampoline information
+a compiler might have emitted in its debug info.  Trampolines will be treated
+like any other function when stepping.
+
+@item show skip-trampoline-functions
+Show whether @value{GDBN} tries to skip trampolines or not.
+
 @kindex finish
 @kindex fin @r{(@code{finish})}
 @item finish
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 58da1cef29e..dfafe7cdc74 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -184,6 +184,12 @@  get_previous_thread ()
 
 static bool detach_fork = true;
 
+/* If set (default) GDB will step through functions/inlined subroutines marked
+   DW_AT_trampoline by the compiler.  If false, GDB will ignore the
+   attribute.  */
+
+static bool skip_trampoline_functions = true;
+
 bool debug_infrun = false;
 static void
 show_debug_infrun (struct ui_file *file, int from_tty,
@@ -7265,7 +7271,6 @@  process_event_stop_test (struct execution_control_state *ecs)
 		  != find_pc_function (ecs->event_thread->stop_pc ())))))
     {
       CORE_ADDR stop_pc = ecs->event_thread->stop_pc ();
-      CORE_ADDR real_stop_pc;
 
       infrun_debug_printf ("stepped into subroutine");
 
@@ -7337,8 +7342,41 @@  process_event_stop_test (struct execution_control_state *ecs)
 	 calling routine and the real function), locate the real
 	 function.  That's what tells us (a) whether we want to step
 	 into it at all, and (b) what prologue we want to run to the
-	 end of, if we do step into it.  */
-      real_stop_pc = skip_language_trampoline (frame, stop_pc);
+	 end of, if we do step into it.  For functions marked as
+	 trampoline functions we try to find their target and step
+	 towards it (if skip_trampoline_functions is not set to false by the
+	 user).  If no target can be determined we just step into the
+	 trampoline and hand control back to the user.  */
+      CORE_ADDR real_stop_pc = 0;
+      bool in_trampoline = skip_trampoline_functions
+			   && in_trampoline_function (stop_pc);
+
+      if (in_trampoline)
+	{
+	  real_stop_pc = find_function_trampoline_target (stop_pc);
+
+	  for (int i = 0; i < MAX_TRAMPOLINE_CHAIN_SIZE
+			  && in_trampoline_function (real_stop_pc); ++i)
+	    {
+		real_stop_pc = find_function_trampoline_target (real_stop_pc);
+		/* Exit if find_function_trampoline_target failed to find the
+		   trampoline target.  Do not try to resolve the trampolines
+		   in this case.  */
+		if (real_stop_pc == 0x0)
+		  break;
+	    }
+
+	  /* If we failed to find a target we will just single step in the
+	     hope of leaving the trampoline again soon.  */
+	  if (real_stop_pc == 0x0)
+	    {
+	      keep_going (ecs);
+	      return;
+	    }
+	}
+
+      if (real_stop_pc == 0)
+	real_stop_pc = skip_language_trampoline (frame, stop_pc);
       if (real_stop_pc == 0)
 	real_stop_pc = gdbarch_skip_trampoline_code (gdbarch, frame, stop_pc);
       if (real_stop_pc != 0)
@@ -7359,6 +7397,8 @@  process_event_stop_test (struct execution_control_state *ecs)
       /* If we have line number information for the function we are
 	 thinking of stepping into and the function isn't on the skip
 	 list, step into it.
+	 If we are about to step into a function marked trampoline with no
+	 line number information, we still want to enter it here.
 
 	 If there are several symtabs at that PC (e.g. with include
 	 files), just want to know whether *any* of them have line
@@ -7367,7 +7407,9 @@  process_event_stop_test (struct execution_control_state *ecs)
 	struct symtab_and_line tmp_sal;
 
 	tmp_sal = find_pc_line (ecs->stop_func_start, 0);
-	if (tmp_sal.line != 0
+	if ((tmp_sal.line != 0
+	     || (skip_trampoline_functions
+		 && in_trampoline_function (ecs->stop_func_start)))
 	    && !function_name_is_marked_for_skip (ecs->stop_func_name,
 						  tmp_sal)
 	    && !inline_frame_is_marked_for_skip (true, ecs->event_thread))
@@ -7520,6 +7562,19 @@  process_event_stop_test (struct execution_control_state *ecs)
       return;
     }
 
+  /* If we ended up in a function trampoline without stepping into a new
+     function we are either in some inlined trampoline or returning through a
+     trampoline function.  In either case we continue to single step until we
+     are out of the trampoline code again.  This check has to be done before
+     stop_pc_sal.line == 0 below, as trampolines usually don't have source
+     line information associated with them.  */
+  if (skip_trampoline_functions && in_trampoline_function (stop_pc_sal.pc))
+    {
+      infrun_debug_printf ("stepped into trampoline code");
+      keep_going (ecs);
+      return;
+    }
+
   if (stop_pc_sal.line == 0)
     {
       /* We have no line number information.  That means to stop
@@ -9731,6 +9786,14 @@  show_exec_direction_func (struct ui_file *out, int from_tty,
   }
 }
 
+static void
+show_skip_trampoline_functions (ui_file *file, int from_tty,
+				cmd_list_element *c,
+				const char *value)
+{
+  gdb_printf (file, _("Skipping trampoline functions is %s.\n"), value);
+}
+
 static void
 show_schedule_multiple (struct ui_file *file, int from_tty,
 			struct cmd_list_element *c, const char *value)
@@ -10076,6 +10139,16 @@  Options are 'forward' or 'reverse'."),
 			set_exec_direction_func, show_exec_direction_func,
 			&setlist, &showlist);
 
+  add_setshow_boolean_cmd ("skip-trampoline-functions", class_run,
+			  &skip_trampoline_functions, _("\
+Set whether gdb attempts to hide trampolines marked in the debug info."), _("\
+Show whether gdb attempts to hide trampolines marked in the debug info."), _("\
+If on, while stepping gdb will skip through functions and inlined functions\n\
+marked as trampolines by the compiler.  If off, gdb will ignore such function\n\
+trampolines."),
+			  nullptr, show_skip_trampoline_functions, &setlist,
+			  &showlist);
+
   /* Set/show detach-on-fork: user-settable mode.  */
 
   add_setshow_boolean_cmd ("detach-on-fork", class_run, &detach_fork, _("\
diff --git a/gdb/infrun.h b/gdb/infrun.h
index a343d27f72d..d4a6b7892b5 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -76,6 +76,10 @@  infrun_debug_show_threads (const char *title, ThreadRange threads)
 }
 
 
+/* Maximum size of trampoline chain to process while resolving
+   trampolines.  */
+#define MAX_TRAMPOLINE_CHAIN_SIZE 10
+
 /* Nonzero if we want to give control to the user when we're notified
    of shared library events by the dynamic linker.  */
 extern int stop_on_solib_events;
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c b/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c
new file mode 100644
index 00000000000..1f0eb2e0efa
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.c
@@ -0,0 +1,80 @@ 
+/* Copyright 2023 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/>.  */
+
+/* This test relies on inlined_trampoline being inlined into main and the other
+   functions not.  All functions except target will be marked via
+   DW_AT_trampoline in the debug info and we'll check whether one can step
+   through the trampolines towards target.  */
+volatile int global_var;
+
+int __attribute__ ((noinline))
+target ()					/* target decl line */
+{						/* target prologue */
+  asm ("target_label: .globl target_label");
+  ++global_var;					/* target add */
+  asm ("target_label2: .globl target_label2");
+  return 9 + 10;				/* target return */
+}						/* target end */
+
+int __attribute__ ((noinline))
+trampoline ()
+{						/* trampoline prologue */
+  asm ("trampoline_label: .globl trampoline_label");
+  ++global_var;
+  return target ();				/* trampoline target call */
+}						/* trampoline end */
+
+static inline int __attribute__ ((always_inline))
+inlined_trampoline ()
+{						/* inlined_trampoline prologue */
+  asm ("inlined_trampoline_label: .globl inlined_trampoline_label");
+  ++global_var;					/* inlined_trampoline add */
+  asm ("inlined_trampoline_label2: .globl inlined_trampoline_label2");
+  return target ();				/* inlined_trampoline target call */
+}						/* inlined_trampoline end */
+
+int __attribute__ ((noinline))
+chained_trampoline ()
+{						/* chained_trampoline prologue */
+  asm ("chained_trampoline_label: .globl chained_trampoline_label");
+  ++global_var;
+  return trampoline ();				/* chained_trampoline trampoline call */
+}						/* chained_trampoline end */
+
+int __attribute__ ((noinline))
+doubly_chained_trampoline ()
+{						/* doubly_chained_trampoline prologue */
+  asm ("doubly_chained_trampoline_label: .globl doubly_chained_trampoline_label");
+  ++global_var;
+  return chained_trampoline ();			/* doubly_chained_trampoline chained_trampoline call */
+}						/* doubly_chained_trampoline end */
+
+int
+main ()						/* main decl line */
+{						/* main prologue */
+  int ans;
+  asm ("main_label: .globl main_label");
+  global_var = 0;				/* main set global_var */
+  asm ("main_label2: .globl main_label2");
+  ans = inlined_trampoline ();			/* main call inlined_trampoline */
+  asm ("main_label3: .globl main_label3");
+  ans = trampoline ();				/* main call trampoline */
+  asm ("main_label4: .globl main_label4");
+  ans = chained_trampoline ();			/* main call chained_trampoline */
+  asm ("main_label5: .globl main_label5");
+  ans = doubly_chained_trampoline ();		/* main call doubly_chained_trampoline */
+  asm ("main_label6: .globl main_label6");
+  return ans;					/* main call return */
+}						/* main end */
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp b/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp
new file mode 100644
index 00000000000..67eec2b5540
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-function-trampolines.exp
@@ -0,0 +1,245 @@ 
+# Copyright 2023 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/>.
+
+# This test checks GDB's handling of functions/inlined functions marked
+# DW_AT_trampoline by the compiler.  A function marked as trampoline should
+# generally be hidden from the user.  We check whether we can step through
+# trampolines.  Every trampoline is defined using a different type for its
+# target: a string, an address, a DIE reference or a flag.
+# Setting skip-trampoline-functions to false inside GDB should make it return
+# to its 'normal' behavior, ignore the DW_AT_trampoline, and skip all of the
+# non-inlined trampoline calls (as their DIEs don't have any source
+# information).
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+if {![dwarf2_support]} {
+    return 0
+}
+
+standard_testfile .c .S
+
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+    global srcdir subdir srcfile
+    declare_labels lines_label trampoline_label
+
+    get_func_info target
+    get_func_info trampoline
+    get_func_info chained_trampoline
+    get_func_info doubly_chained_trampoline
+    get_func_info main
+
+    set target_decl_line [gdb_get_line_number "target decl line"]
+    set main_decl_line [gdb_get_line_number "main decl line"]
+    set main_call_inlined_trampoline_line [gdb_get_line_number "main call inlined_trampoline"]
+
+    cu {} {
+	compile_unit {
+	    {language @DW_LANG_C}
+	    {name dw2-function-trampolines.c}
+	    {low_pc 0 addr}
+	    {stmt_list ${lines_label} DW_FORM_sec_offset}
+	} {
+	    subprogram {
+		{name target}
+		{low_pc $target_start addr}
+		{high_pc "$target_start + $target_len" addr}
+		{decl_file 1 data1}
+		{decl_line $target_decl_line data1}
+	    }
+	    # The 'trampoline' subprogram declares its target by name.
+	    trampoline_label: subprogram {
+		    {name trampoline}
+		    {low_pc $trampoline_start addr}
+		    {high_pc "$trampoline_start + $trampoline_len" addr}
+		    {trampoline target string}
+		}
+	    # The 'chained_trampoline' subprogram declares its target as die
+	    # reference.
+	    subprogram {
+		{name chained_trampoline}
+		{low_pc $chained_trampoline_start addr}
+		{high_pc "$chained_trampoline_start + $chained_trampoline_len" addr}
+		{trampoline %$trampoline_label}
+	    }
+	    # The 'doubly_chained_trampoline' subprogram declares no target.
+	    # Its DW_AT_trampoline is a flag set to true.
+	    subprogram {
+		{name doubly_chained_trampoline}
+		{low_pc $doubly_chained_trampoline_start addr}
+		{high_pc "$doubly_chained_trampoline_start + $doubly_chained_trampoline_len" addr}
+		{trampoline 1 flag}
+	    }
+	    subprogram {
+		{external 1 flag}
+		{name main}
+		{main_subprogram 1 flag}
+		{low_pc $main_start addr}
+		{high_pc "$main_start + $main_len" addr}
+		{decl_file 1 data1}
+		{decl_line $main_decl_line data1}
+	    } {
+		# The 'inlined_trampoline' subroutine declares its target as
+		# an address.
+		inlined_subroutine {
+		    {name inlined_trampoline}
+		    {low_pc main_label2 addr}
+		    {high_pc main_label3 addr}
+		    {trampoline $target_start addr}
+		    {call_file 1 data1}
+		    {call_line $main_call_inlined_trampoline_line data1}
+		}
+	    }
+	}
+    }
+
+    lines {version 2} lines_label {
+	include_dir "${srcdir}/${subdir}"
+	file_name "$srcfile" 1
+
+	program {
+	    DW_LNE_set_address $main_start
+	    line [gdb_get_line_number "main set global_var"]
+	    DW_LNS_copy
+	    DW_LNE_set_address main_label
+	    line [gdb_get_line_number "main set global_var"]
+	    DW_LNS_copy
+	    DW_LNE_set_address main_label2
+	    line [gdb_get_line_number "main call inlined_trampoline"]
+	    DW_LNS_copy
+	    DW_LNE_set_address inlined_trampoline_label
+	    line [gdb_get_line_number "inlined_trampoline add"]
+	    DW_LNS_copy
+	    DW_LNE_set_address inlined_trampoline_label2
+	    line [gdb_get_line_number "inlined_trampoline target call"]
+	    DW_LNS_copy
+	    DW_LNE_set_address main_label3
+	    line [gdb_get_line_number "main call trampoline"]
+	    DW_LNS_copy
+	    DW_LNE_set_address main_label4
+	    line [gdb_get_line_number "main call chained_trampoline"]
+	    DW_LNS_copy
+	    DW_LNE_set_address main_label5
+	    line [gdb_get_line_number "main call doubly_chained_trampoline"]
+	    DW_LNS_copy
+	    DW_LNE_set_address main_label6
+	    line [gdb_get_line_number "main call return"]
+	    DW_LNS_copy
+	    DW_LNE_set_address $main_end
+	    DW_LNE_end_sequence
+
+	    DW_LNE_set_address $target_start
+	    line [gdb_get_line_number "target prologue"]
+	    DW_LNS_negate_stmt
+	    DW_LNS_copy
+	    DW_LNE_set_address target_label
+	    line [gdb_get_line_number "target add"]
+	    DW_LNS_negate_stmt
+	    DW_LNS_copy
+	    DW_LNE_set_address target_label2
+	    line [gdb_get_line_number "target return"]
+	    DW_LNS_copy
+	    DW_LNE_set_address $target_end
+	    DW_LNE_end_sequence
+	}
+    }
+}
+
+if {[prepare_for_testing "failed to prepare" ${testfile} \
+	[list $srcfile $asm_file] {nodebug additional_flags=-O0}]} {
+    return -1
+}
+
+set target_first_line_pattern ".*target add.*"
+set target_second_line_pattern ".*target return.*"
+
+if ![runto_main] {
+    return -1
+}
+
+gdb_test "show skip-trampoline-functions" \
+    "Skipping trampoline functions is on\." \
+    "check skip-trampoline-functions is enabled"
+
+with_test_prefix "with trampoline handling" {
+    foreach {trampoline return_line} { "inlined_trampoline" "trampoline" \
+	"trampoline" "chained_trampoline" \
+	"chained_trampoline" "doubly_chained_trampoline" } {
+
+	gdb_test "s" "$target_first_line_pattern" "step through $trampoline"
+	gdb_test "s" "$target_second_line_pattern" \
+	    "step target second line from $trampoline"
+	gdb_test "s" ".*main call $return_line.*" \
+	    "step back through $trampoline"
+    }
+
+    # The doubly_chained_trampoline has only been marked as trampoline but no
+    # target was given.  In this case GDB steps into the trampoline and then
+    # continues until the trampoline section is left again.
+
+    # When compiled with gcc 7.5 (and possibly others) on a 32 bit system, the
+    # trampoline function contains a call to __x86.get_pc_thunk.ax before the
+    # actual target call.  So, we end up in __x86.get_pc_thunk.ax.  Issuing a
+    # second step command will return from the function call back into the
+    # trampoline and go on inside the trampoline towards the actual target call.
+    # On other targets we step directly towards the target call.
+    gdb_test_multiple "s" "step through double_chained_trampoline" {
+	-re -wrap "$target_first_line_pattern" {
+	    pass $gdb_test_name
+	}
+	-re -wrap ".*__x86.get_pc_thunk.ax.*" {
+	    gdb_test "s" "$target_first_line_pattern" \
+		"step through double_chained_trampoline 2nd try"
+	}
+    }
+    gdb_test "s" "$target_second_line_pattern" \
+	"step target second line fromdoubly_chained_trampoline"
+    gdb_test "s" ".*main call return.*" \
+	"step back through doubly_chained_trampoline"
+}
+
+clean_restart ${testfile}
+
+if ![runto_main] {
+    return -1
+}
+
+gdb_test_no_output "set skip-trampoline-functions off" \
+    "disable trampoline handling"
+gdb_test "show skip-trampoline-functions" \
+    "Skipping trampoline functions is off." \
+    "check skip-trampoline-functions is disabled"
+
+with_test_prefix "without trampoline handling" {
+    gdb_test "s" ".*main call inlined_trampoline.*"
+    gdb_test "s" ".*inlined_trampoline add.*" \
+	"step into inlined_trampoline with skip-trampoline off"
+    gdb_test "s" ".*inlined_trampoline target call.*" \
+	"step in inlined_trampoline with skip-trampoline off"
+    gdb_test "s" "$target_first_line_pattern" \
+	"step into target with skip-trampoline off"
+    gdb_test "s" "$target_second_line_pattern" \
+	"step second line in target with skip-trampoline off"
+    gdb_test "s" ".*main call trampoline.*" \
+	"step brack from target with skip-trampoline off"
+    gdb_test "s" ".*main call chained_trampoline.*" \
+	"skip trampoline call with no line info"
+    gdb_test "s" ".*main call doubly_chained_trampoline.*" \
+	"skip chained_trampoline call with no line info"
+    gdb_test "s" ".*main call return.*" \
+	"skip doubly_chained_trampoline call with no line info"
+}