[PATCH] gdb: Fix assertion failure when inline frame #0 is duplicated
Craig Blackmore
craig.blackmore@embecosm.com
Tue Jan 28 17:10:55 GMT 2025
Modifying inline-frame-cycle-unwind.exp to use `bt -no-filters` produces
the following incorrect backtrace:
#0 inline_func () at .../gdb/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.c:49
#1 normal_func () at .../gdb/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.c:32
#2 0x000055555555517f in inline_func () at .../gdb/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.c:50
#3 normal_func () at .../gdb/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.c:32
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) FAIL: gdb.base/inline-frame-cycle-unwind.exp: cycle at level 1: backtrace when the unwind is broken at frame 1
The expected output, which we get with `bt`, is:
#0 inline_func () at .../gdb/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.c:49
#1 normal_func () at .../gdb/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.c:32
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) PASS: gdb.base/inline-frame-cycle-unwind.exp: cycle at level 1: backtrace when the unwind is broken at frame 1
The cycle checking in `get_prev_frame_maybe_check_cycle` relies on newer
frame ids having already been computed and stashed. Unlike other
frames, frame #0's id does not get computed immediately.
The test passes with `bt` because when applying python frame filters,
the call to `bootstrap_python_frame_filters` happens to compute the id
of frame #0. When `get_prev_frame_maybe_check_cycle` later tries to
stash frame #2's id, the cycle is detected.
The test fails with `bt -no-filters` because frame #0's id has not been
stashed by the time `get_prev_frame_maybe_check_cycle` tries to stash
frame #2's id which succeeds and the cycle is only detected later when
trying to stash frame #4's id. Doing `stepi` after the incorrect
backtrace would then trigger an assertion failure when trying to stash
frame #0's id because it is a duplicate of #2's already stashed id.
In `get_prev_frame`, get the frame id of the current frame to ensure it
has been computed and stashed before `get_prev_frame_maybe_check_cycle`
is called on older frames.
The test case has been updated to run both `bt` and `bt -no-filters`.
---
gdb/frame.c | 5 +
.../gdb.base/inline-frame-cycle-unwind.exp | 148 ++++++++++--------
2 files changed, 85 insertions(+), 68 deletions(-)
diff --git a/gdb/frame.c b/gdb/frame.c
index a5246780962..c30bb4eb2aa 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -2657,6 +2657,11 @@ get_prev_frame (const frame_info_ptr &this_frame)
get_current_frame(). */
gdb_assert (this_frame != NULL);
+ /* Make sure the current frame id has been computed so that the cycle check in
+ get_prev_frame_maybe_check_cycle works for the case where frame 0 has been
+ duplicated. */
+ get_frame_id (get_current_frame ());
+
frame_pc_p = get_frame_pc_if_available (this_frame, &frame_pc);
/* tausq/2004-12-07: Dummy frames are skipped because it doesn't make much
diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
index 45086f69a39..46561a9c9fb 100644
--- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
+++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
@@ -72,77 +72,89 @@ gdb_continue_to_breakpoint "stop at test breakpoint"
gdb_test_no_output "source ${pyfile}"\
"import python scripts"
-# Check the unbroken stack.
-gdb_test_sequence "bt" "backtrace when the unwind is left unbroken" {
- "\\r\\n#0 \[^\r\n\]* inline_func \\(\\) at "
- "\\r\\n#1 \[^\r\n\]* normal_func \\(\\) at "
- "\\r\\n#2 \[^\r\n\]* inline_func \\(\\) at "
- "\\r\\n#3 \[^\r\n\]* normal_func \\(\\) at "
- "\\r\\n#4 \[^\r\n\]* inline_func \\(\\) at "
- "\\r\\n#5 \[^\r\n\]* normal_func \\(\\) at "
- "\\r\\n#6 \[^\r\n\]* main \\(\\) at "
-}
+# Test with and without filters.
+foreach bt_cmd { "bt" "bt -no-filters" } {
+ with_test_prefix "$bt_cmd" {
-with_test_prefix "cycle at level 5" {
- # Arrange to introduce a stack cycle at frame 5.
- gdb_test_no_output "python stop_at_level=5"
- gdb_test "maint flush register-cache" \
- "Register cache flushed\\."
- gdb_test_lines "bt" "backtrace when the unwind is broken at frame 5" \
- [multi_line \
- "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
- "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
- "#2 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
- "#3 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
- "#4 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
- "#5 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
- "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
-}
+ # Check the unbroken stack.
+ gdb_test_sequence "$bt_cmd" "backtrace when the unwind is left unbroken" {
+ "\\r\\n#0 \[^\r\n\]* inline_func \\(\\) at "
+ "\\r\\n#1 \[^\r\n\]* normal_func \\(\\) at "
+ "\\r\\n#2 \[^\r\n\]* inline_func \\(\\) at "
+ "\\r\\n#3 \[^\r\n\]* normal_func \\(\\) at "
+ "\\r\\n#4 \[^\r\n\]* inline_func \\(\\) at "
+ "\\r\\n#5 \[^\r\n\]* normal_func \\(\\) at "
+ "\\r\\n#6 \[^\r\n\]* main \\(\\) at "
+ }
-with_test_prefix "cycle at level 3" {
- # Arrange to introduce a stack cycle at frame 3.
- gdb_test_no_output "python stop_at_level=3"
- gdb_test "maint flush register-cache" \
- "Register cache flushed\\."
- gdb_test_lines "bt" "backtrace when the unwind is broken at frame 3" \
- [multi_line \
- "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
- "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
- "#2 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
- "#3 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
- "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
-}
+ with_test_prefix "cycle at level 5" {
+ # Arrange to introduce a stack cycle at frame 5.
+ gdb_test_no_output "python stop_at_level=5"
+ gdb_test "maint flush register-cache" \
+ "Register cache flushed\\."
+ gdb_test_lines "$bt_cmd" "backtrace when the unwind is broken at frame 5" \
+ [multi_line \
+ "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+ "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+ "#2 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+ "#3 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+ "#4 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+ "#5 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+ "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
+ }
-with_test_prefix "cycle at level 1" {
- # Arrange to introduce a stack cycle at frame 1.
- gdb_test_no_output "python stop_at_level=1"
- gdb_test "maint flush register-cache" \
- "Register cache flushed\\."
- gdb_test_lines "bt" "backtrace when the unwind is broken at frame 1" \
- [multi_line \
- "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
- "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
- "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
-}
+ with_test_prefix "cycle at level 3" {
+ # Arrange to introduce a stack cycle at frame 3.
+ gdb_test_no_output "python stop_at_level=3"
+ gdb_test "maint flush register-cache" \
+ "Register cache flushed\\."
+ gdb_test_lines "$bt_cmd" "backtrace when the unwind is broken at frame 3" \
+ [multi_line \
+ "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+ "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+ "#2 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+ "#3 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+ "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
+ }
-# Flush the register cache (which also flushes the frame cache) so we
-# get a full backtrace again, then switch on frame debugging and try
-# to back trace. At one point this triggered an assertion.
-gdb_test "maint flush register-cache" \
- "Register cache flushed\\." ""
-gdb_test_no_output "set debug frame 1"
-set ok 1
-gdb_test_multiple "bt" "backtrace with debugging on" {
- -re "^$gdb_prompt $" {
- gdb_assert { $ok } $gdb_test_name
- }
- -re "Python Exception <class 'gdb.error'>: \[^\r\n\]*\r\n" {
- set ok 0
- exp_continue
- }
- -re "\[^\r\n\]+\r\n" {
- exp_continue
+ with_test_prefix "cycle at level 1" {
+ # Arrange to introduce a stack cycle at frame 1.
+ gdb_test_no_output "python stop_at_level=1"
+ gdb_test "maint flush register-cache" \
+ "Register cache flushed\\."
+ gdb_test_lines "$bt_cmd" "backtrace when the unwind is broken at frame 1" \
+ [multi_line \
+ "#0 \[^\r\n\]* inline_func \\(\\) at \[^\r\n\]+" \
+ "#1 \[^\r\n\]* normal_func \\(\\) at \[^\r\n\]+" \
+ "Backtrace stopped: previous frame identical to this frame \\(corrupt stack\\?\\)"]
+ }
+
+ # Flush the register cache (which also flushes the frame cache) so we
+ # get a full backtrace again, then switch on frame debugging and try
+ # to back trace. At one point this triggered an assertion.
+ gdb_test "maint flush register-cache" \
+ "Register cache flushed\\." ""
+ gdb_test_no_output "set debug frame 1"
+ set ok 1
+ gdb_test_multiple "$bt_cmd" "backtrace with debugging on" {
+ -re "^$gdb_prompt $" {
+ gdb_assert { $ok } $gdb_test_name
+ }
+ -re "Python Exception <class 'gdb.error'>: \[^\r\n\]*\r\n" {
+ set ok 0
+ exp_continue
+ }
+ -re "\[^\r\n\]+\r\n" {
+ exp_continue
+ }
+ }
+ gdb_test "p 1 + 2 + 3" " = 6" \
+ "ensure GDB is still alive"
+
+ # Prepare for the next iteration of the test loop
+ gdb_test_no_output "set debug frame 0"
+ gdb_test_no_output "python stop_at_level=None"
+ gdb_test "maint flush register-cache" \
+ "Register cache flushed\\." "maint flush register-cache at (loop end)"
}
}
-gdb_test "p 1 + 2 + 3" " = 6" \
- "ensure GDB is still alive"
--
2.43.0
More information about the Gdb-patches
mailing list