diff options
| author | Andrew Burgess <aburgess@redhat.com> | 2026-01-21 13:25:43 +0100 |
|---|---|---|
| committer | Andrew Burgess <aburgess@redhat.com> | 2026-02-02 11:03:03 +0000 |
| commit | ef7aabf7979ba4812bfa1568e072653bb8127ea8 (patch) | |
| tree | 19841d87300bdb2bc397e434582102255bc2bf07 /gprof/basic_blocks.c | |
| parent | 1b332804e187cd57ab16c46f623c9b2b05a2177d (diff) | |
| download | fsf-binutils-gdb-master.zip fsf-binutils-gdb-master.tar.gz fsf-binutils-gdb-master.tar.bz2 | |
With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run
into:
...
(gdb) bt
#0 inline_func () at inline-frame-cycle-unwind.c:49
#1 normal_func () at inline-frame-cycle-unwind.c:32
#2 0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45
#3 normal_func () at inline-frame-cycle-unwind.c:32
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) FAIL: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
at frame 5
...
In contrast, on x86_64-linux, I get:
...
(gdb) bt
#0 inline_func () at inline-frame-cycle-unwind.c:49
#1 normal_func () at inline-frame-cycle-unwind.c:32
#2 0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45
#3 normal_func () at inline-frame-cycle-unwind.c:32
#4 0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45
#5 normal_func () at inline-frame-cycle-unwind.c:32
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
at frame 5
...
To understand what's going wrong here, we first need to understand
what this test was trying to do.
The test tries to create a frame-cycle using a custom Python
unwinder. A frame-cycle occurs when a frame in the backtrace has the
same frame-id as an earlier frame. As soon as GDB finds a frame with
a frame-id that it has seen before then the backtrace will be
terminated with the message:
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
A Python frame unwinder does two jobs:
- It provides the frame-id for a frame #n, and
- it provides the unwound register values for the previous (#n + 1)
frame.
For a frame not claimed by a Python frame unwinder, GDB will compute
the frame-id based off of the register values. Particularly, the $pc
and $sp, or $fp registers (or the architecture's equivalent).
In this test then, our frame unwinder does something a little
strange. When we want to stop at frame #5, the frame unwinder claims
frame #5. The frame unwinder then tries to give frame #5 its "normal"
frame-id, but, instead of unwinding the register values, we provide
frame #6 with all of the register values from frame #5 unmodified.
The frame unwinder does not claim frame #6, instead GDB uses its
"normal" logic to compute the frame-id. As the registers in frame #6
are identical to the values in frame #5, GDB computes the same
frame-id for frame #6 as we supplied for frame #5, and thus a frame
cycle is created.
Notice I said that we try to give frame #5 its "normal" frame-id.
When this test was originally written there was no easy way to access
the frame-id for a frame. So instead, the test was written to try and
recreate the frame-id based on the frame stack pointers, and the frame
size (difference between subsequent frames). And this worked fine for
a bunch of common architectures, like x86-64 and AArch64.
But unfortunately this frame-id computation doesn't work on s390.
For this explanation we only care about two parts of the frame-id, the
code address, and the stack address. The code address part is usually
the start of the function for a particular frame. Our computation of
this was fine.
The stack address in the frame-id isn't the actual $sp value.
Instead, this is usually the Call Frame Address (CFA) as defined in
the DWARF. How this is calculated really depends on the DWARF, but is
often influenced by the architectures ABI.
On x86-64 and AArch64 the CFA is usually the $sp immediately on entry
to a frame, before any stack adjustment is performed. Thus, if we
take the frame size (difference between $sp in two frames), and add
this to the current $sp value, we successfully calculate the CFA,
which we can use in the frame-id.
On s390 though, this is not the case, the calculation of the CFA is
more complex as the s390 ABI requires that caller frames allocate a
160 byte Register Save Area below the stack pointer. Because of this,
when the Python unwinder ties to calculate the real frame-id for a
frame, it gets the CFA wrong, and inadvertently ends up calculating a
frame-id which matches an earlier frame-id. In the example above,
frame #5 ends up matching frame #3. Because frame #4 is an inline
frame GDB ends up terminating the backtrace after frame #3.
The fix for this is actually pretty simple. Since this test was
originally written GDB has gained the 'maint print frame-id' command.
So instead of trying to calculate the frame-id we can just capture the
frame-ids that GDB generates, and then, once the Python frame unwinder
is in use, we can repeat the previously captured frame-id back to
GDB.
This fixes the issues seen on s390, and doesn't impact testing on the
other architectures.
Tested on x86_64-linux and s390x-linux.
Co-Authored-By: Tom de Vries <tdevries@suse.de>
Diffstat (limited to 'gprof/basic_blocks.c')
0 files changed, 0 insertions, 0 deletions
