From: Olivier Hainque <[email protected]>
Work on cross-testsuite/ta20_015__exc_traceback exposed
that calls to GNAT.Traceback.Call_Chain, in some configurations,
yield a first entry within Call_Chain rather than at the
call point.
This is supposed to be controlled by a careful computation
of a number of frames to skip, passed down as the SKIP_FRAMES
parameter to __gnat_backtrace in tracebak.c.
Roughly:
```
subprogramA
calls subprogramB
calls GNAT.TB.Call_Chain (Skip_Frames => 1)
calls System.TB.Chain (Skip_Frames => 2)
calls __gnat_backtrace (skip_frames => 3)
Actual backtrace at this point is
1 __gnat_backtrace
2 System.TB.Call_Chain
3 GNAT.TB.Call_Chain
4 spB
5 spA
Skip the first 3 frames -> return
[spB, spA]
```
Within tracebak.c, the skip operation is influenced
by micro details of the semantics of a BASE_SKIP macro,
which was imprecisely documented.
I first thought the fault was in the value for this macro
in a couple of configurations, while it was entirely elswehere.
There were two issues aside from the BASE_SKIP documentation
(in)accuracy:
1 GNAT.Traceback exposes a Call_Chain procedure and a Call_Chain
function.
The function has a Skip_Frames parameter, with a default value,
and a description of how it operates wrt that parameter.
The procedure doesn't accept a parameter, the spec was imprecise
and the behavior was different from that of the function with
the default parameter value.
2 The way Skip_Frames values propagate down to __gnat_backtrace
require the internal calls within the runtime to materialze as actual
calls (and call frames) at run-time. This requires preventing
inlining and sibling call optimization, which was achieved with
dedicated compilation switches for s-traceb.adb and overlooked
for g-traceb.adb.
This patch addresses these combined points together:
gcc/ada/ChangeLog:
* tracebak.c: Improve documentation of BASE_SKIP.
* libgnat/g-traceb.ads (Call_Chain procedure): Clarify the
expected behavior wrt Call_Chain itself.
* libgnat/g-traceb.adb (Call_Chain procedure): Propagate
a Skip_Frames value to System.TB.Call_Chain accordingly.
* libgnat/libgnat.gpr: Use the same special switches for
g-traceb.adb as for s-traceb.adb.
* Makefile.rtl: Likewise.
* libgnat/s-traceb.adb: Reword top comment on the need
to prevent optimizations. No need to specify how to do it
here.
Tested on x86_64-pc-linux-gnu, committed on master.
---
gcc/ada/Makefile.rtl | 11 ++++++++---
gcc/ada/libgnat/g-traceb.adb | 7 ++++++-
gcc/ada/libgnat/g-traceb.ads | 9 ++++-----
gcc/ada/libgnat/libgnat.gpr | 8 ++++----
gcc/ada/libgnat/s-traceb.adb | 6 ++++--
gcc/ada/tracebak.c | 27 +++++++++++++++------------
6 files changed, 41 insertions(+), 27 deletions(-)
diff --git a/gcc/ada/Makefile.rtl b/gcc/ada/Makefile.rtl
index d4377098a00..d99c28faec3 100644
--- a/gcc/ada/Makefile.rtl
+++ b/gcc/ada/Makefile.rtl
@@ -3405,15 +3405,20 @@ system.o: system.ads
$(ADA_TARGET_PROPERTIES) \
$(OUTPUT_OPTION)
-# Force no sibling call optimization on s-traceb.o so the number of stack
-# frames to be skipped when computing a call chain is not modified by
-# optimization. We don't want inlining, either.
+# Force no sibling call optimization on s-traceb.o and g-traceb.o so the
+# number of stack frames to be skipped when computing a call chain is not
+# modified by optimization. We don't want inlining, either.
s-traceb.o: s-traceb.adb s-traceb.ads
$(ADAC) -c $(ALL_ADAFLAGS) $(FORCE_DEBUG_ADAFLAGS) \
$(NO_INLINE_ADAFLAGS) $(NO_SIBLING_ADAFLAGS) $(ADA_INCLUDES) $< \
$(OUTPUT_OPTION)
+g-traceb.o: g-traceb.adb g-traceb.ads
+ $(ADAC) -c $(ALL_ADAFLAGS) $(FORCE_DEBUG_ADAFLAGS) \
+ $(NO_INLINE_ADAFLAGS) $(NO_SIBLING_ADAFLAGS) $(ADA_INCLUDES) $< \
+ $(OUTPUT_OPTION)
+
# Compile s-tasdeb.o without optimization and with debug info so that it is
# always possible to set conditional breakpoints on tasks.
diff --git a/gcc/ada/libgnat/g-traceb.adb b/gcc/ada/libgnat/g-traceb.adb
index 12939923f71..7b7d843b870 100644
--- a/gcc/ada/libgnat/g-traceb.adb
+++ b/gcc/ada/libgnat/g-traceb.adb
@@ -31,6 +31,9 @@
-- Run-time non-symbolic traceback support
+-- As for System.Traceback, inlining and sibling call optimizations
+-- must be prevented within this unit.
+
with System.Traceback;
package body GNAT.Traceback is
@@ -44,7 +47,9 @@ package body GNAT.Traceback is
Len : out Natural)
is
begin
- System.Traceback.Call_Chain (Traceback, Traceback'Length, Len);
+ -- Request skipping this frame + that of our callee
+ System.Traceback.Call_Chain
+ (Traceback, Traceback'Length, Len, Skip_Frames => 2);
end Call_Chain;
function Call_Chain
diff --git a/gcc/ada/libgnat/g-traceb.ads b/gcc/ada/libgnat/g-traceb.ads
index e8d61fdfee9..f99c2945b43 100644
--- a/gcc/ada/libgnat/g-traceb.ads
+++ b/gcc/ada/libgnat/g-traceb.ads
@@ -90,11 +90,10 @@ package GNAT.Traceback is
procedure Call_Chain (Traceback : out Tracebacks_Array; Len : out Natural);
-- Store up to Traceback'Length tracebacks corresponding to the current
- -- call chain. The first entry stored corresponds to the deepest level
- -- of subprogram calls. Len shows the number of traceback entries stored.
- -- It will be equal to Traceback'Length unless the entire traceback is
- -- shorter, in which case positions in Traceback past the Len position
- -- are undefined on return.
+ -- call chain. The first entry is for the caller of this subprogram and
+ -- Len conveys the number of entries stored. Len will be Traceback'Length
+ -- unless the entire traceback is shorter. Positions in Traceback beyond
+ -- Len are undefined on return.
function Call_Chain
(Max_Len : Positive;
diff --git a/gcc/ada/libgnat/libgnat.gpr b/gcc/ada/libgnat/libgnat.gpr
index fd2f225381b..c0c0274886a 100644
--- a/gcc/ada/libgnat/libgnat.gpr
+++ b/gcc/ada/libgnat/libgnat.gpr
@@ -26,12 +26,12 @@ library project Libgnat is
for Switches ("C") use Libgnat_Common.C_Flags;
for Switches ("Ada") use Libgnat_Common.Ada_Flags;
- for Switches ("s-traceb.adb") use
+ for Switches ("?-traceb.adb") use
Libgnat_Common.Ada_Flags & Libgnat_Common.Force_Debug &
Libgnat_Common.No_Inline & Libgnat_Common.No_Sibling;
- -- Force no sibling call optimization on s-traceb.o so the number of
- -- stack frames to be skipped when computing a call chain is not
- -- modified by optimization. We don.t want inlining, either.
+ -- Prevent inlining and sibling call optimization on s-traceb.o and
+ -- g-traceb.o so the number of stack frames to be skipped when computing
+ -- a call chain is not modified by optimization.
for Switches ("a-except.adb") use
Libgnat_Common.Ada_Flags & ("-O1") &
diff --git a/gcc/ada/libgnat/s-traceb.adb b/gcc/ada/libgnat/s-traceb.adb
index 78692582be3..cbea261c5d1 100644
--- a/gcc/ada/libgnat/s-traceb.adb
+++ b/gcc/ada/libgnat/s-traceb.adb
@@ -31,8 +31,10 @@
-- This is the default version of this package
--- Note: this unit must be compiled using -fno-optimize-sibling-calls.
--- See comment below in body of Call_Chain for details on the reason.
+-- Note: To preserve the correctness of "Skip_Frames" processing,
+-- subprogram calls here must materialize as subprogram calls (and
+-- frames) at the machine level, so inlining and sibling call
+-- optimizations must be prevented for this unit.
package body System.Traceback is
diff --git a/gcc/ada/tracebak.c b/gcc/ada/tracebak.c
index 17c6be11f88..2c80b2a1610 100644
--- a/gcc/ada/tracebak.c
+++ b/gcc/ada/tracebak.c
@@ -201,8 +201,8 @@ __gnat_backtrace (void **array,
o FRAME_OFFSET, the offset, from a given frame address or frame pointer
value, at which this layout will be found,
- o FRAME_LEVEL, controls how many frames up we get at to start with,
- from the initial frame pointer we compute by way of the GCC builtin,
+ o FRAME_LEVEL, control how many stack frames up we start from, in
+ builtin_frame_address terms.
0 is most often the appropriate value. 1 may be necessary on targets
where return addresses are saved by a function in it's caller's frame
@@ -237,19 +237,18 @@ __gnat_backtrace (void **array,
| |
+----------------+
- o BASE_SKIP,
+ o BASE_SKIP represents the initial shift incurred by the initial
+ FRAME_LEVEL + the fact that what we find on the stack are return
+ addresses. One way to see it is: starting from
- Since we inherently deal with return addresses, there is an implicit shift
- by at least one for the initial point we are able to observe in the chain.
+ ptr = (struct layout *)__builtin_frame_address(FRAME_LEVEL);
- On some targets (e.g. sparc-solaris), the first return address we can
- easily get without special code is even our caller's return address, so
- there is a initial shift of two.
+ in __gnat_backtrace, where does ptr->return_address land?
- BASE_SKIP represents this initial shift, which is the minimal "skip_frames"
- value we support. We could add special code for the skip_frames < BASE_SKIP
- cases. This is not done currently because there is virtually no situation
- in which this would be useful.
+ If this is within __gnat_backtrace, BASE_SKIP should be 0. If this
+ is within the caller of __gnat_backtrace, BASE_SKIP should be 1. etc.
+
+ BASE_SKIP is the minimal "skip_frames" value we support.
Finally, to account for some ABI specificities, a target may (but does
not have to) define:
@@ -411,6 +410,8 @@ struct layout
#define STOP_FRAME(CURRENT, TOP_STACK) \
((CURRENT)->next == 0 || ((long)(CURRENT)->next % __alignof__(void*)) != 0)
+/* builtin_frame_address(1) gets us the frame pointer of our caller,
+ where we store our own return address. */
#define BASE_SKIP 1
/*-------------------------- SPARC Solaris or RTEMS --------------------*/
@@ -497,6 +498,8 @@ struct layout
|| (void *) ((CURRENT)->next) < (TOP_STACK) \
|| EXTRA_STOP_CONDITION(CURRENT))
+/* builtin_frame_address(1) gets us the *frame* pointer of our caller, where
+ we'll find the return address from that caller to its own caller. */
#define BASE_SKIP (1+FRAME_LEVEL)
/* On i386 architecture we check that at the call point we really have a call
--
2.53.0