Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
@ 2025-12-11 13:39 Tom de Vries
  2026-01-03 15:12 ` [PING][PATCH] " Tom de Vries
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Tom de Vries @ 2025-12-11 13:39 UTC (permalink / raw)
  To: gdb-patches

With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run
into:
...
 (gdb) bt^M
 #0  inline_func () at inline-frame-cycle-unwind.c:49^M
 #1  normal_func () at inline-frame-cycle-unwind.c:32^M
 #2  0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45^M
 #3  normal_func () at inline-frame-cycle-unwind.c:32^M
 Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
 (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^M
 #0  inline_func () at inline-frame-cycle-unwind.c:49^M
 #1  normal_func () at inline-frame-cycle-unwind.c:32^M
 #2  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
 #3  normal_func () at inline-frame-cycle-unwind.c:32^M
 #4  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
 #5  normal_func () at inline-frame-cycle-unwind.c:32^M
 Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
 (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
   at frame 5
...

AFAIU, the mechanism of the test is as follows: the custom unwinder produces the
frame-id for frame #5 at frame #4.  Consequently, when arriving at frame #5, a
cycle is detected.

[ It took me a while to understand this because of the following off-by-one
confusion: for frame #0, we get pending_frame.level() == 1.  So when
stop_at_level == 5, the custom unwinder calculates a frame-id for frame #4,
not frame #5.  But the frame-id it calculates is the one for frame #5, so
unwinding will stop at frame #5 because the frame-ids for frame #4 and
frame #5 are identical. ]

This relies on the test-case to calculate the offending frame-id, and the
problem on s390x is that that calculation is incorrect.

Fix this by using "maint print frame-id" to get all frame-ids, and using those
instead.

Tested on x86_64-linux and s390x-linux.
---
 .../gdb.base/inline-frame-cycle-unwind.exp          | 13 +++++++++++++
 gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py |  8 +++++---
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
index 7fc47af624f..5c6504323ee 100644
--- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
+++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
@@ -72,6 +72,19 @@ gdb_continue_to_breakpoint "stop at test breakpoint"
 gdb_test_no_output "source ${pyfile}"\
     "import python scripts"
 
+foreach_with_prefix n { 0 1 2 3 4 5 6 } {
+    set sp 0x0
+    set pc 0x0
+    gdb_test_multiple "maint print frame-id $n" "" {
+	-re -wrap "frame-id for frame #$n: {stack=($hex),code=($hex),.*}" {
+	    set sp $expect_out(1,string)
+	    set pc $expect_out(2,string)
+	    gdb_test_no_output "python frame_id_sp.append($sp)"
+	    gdb_test_no_output "python frame_id_pc.append($pc)"
+	}
+    }
+}
+
 # Test with and without filters.
 foreach bt_cmd { "bt" "bt -no-filters" } {
     with_test_prefix "$bt_cmd" {
diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
index 55dea989512..25a67b1a7c9 100644
--- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
+++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
@@ -26,6 +26,9 @@ stop_at_level = None
 # function called recursively.
 stack_adjust = None
 
+frame_id_sp = []
+frame_id_pc = []
+
 
 class FrameId(object):
     def __init__(self, sp, pc):
@@ -55,9 +58,8 @@ class TestUnwinder(Unwinder):
         if stop_at_level not in [1, 3, 5]:
             raise gdb.GdbError("invalid stop_at_level")
 
-        sp_desc = pending_frame.architecture().registers().find("sp")
-        sp = pending_frame.read_register(sp_desc) + stack_adjust
-        pc = (gdb.lookup_symbol("normal_func"))[0].value().address
+        sp = frame_id_sp[stop_at_level]
+        pc = frame_id_pc[stop_at_level]
         unwinder = pending_frame.create_unwind_info(FrameId(sp, pc))
 
         for reg in pending_frame.architecture().registers("general"):

base-commit: 2271dee682787051c0628c869d7cdb220bdd0e67
-- 
2.51.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PING][PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2025-12-11 13:39 [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative) Tom de Vries
@ 2026-01-03 15:12 ` Tom de Vries
  2026-01-19 18:36   ` [PING^2][PATCH] " Tom de Vries
  2026-01-20 10:38 ` [PATCH] " Andrew Burgess
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 10+ messages in thread
From: Tom de Vries @ 2026-01-03 15:12 UTC (permalink / raw)
  To: gdb-patches

On 12/11/25 2:39 PM, Tom de Vries wrote:
> With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run
> into:
> ...
>   (gdb) bt^M
>   #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>   #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>   #2  0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45^M
>   #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>   Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>   (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^M
>   #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>   #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>   #2  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>   #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>   #4  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>   #5  normal_func () at inline-frame-cycle-unwind.c:32^M
>   Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>   (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
>     at frame 5
> ...
> 
> AFAIU, the mechanism of the test is as follows: the custom unwinder produces the
> frame-id for frame #5 at frame #4.  Consequently, when arriving at frame #5, a
> cycle is detected.
> 
> [ It took me a while to understand this because of the following off-by-one
> confusion: for frame #0, we get pending_frame.level() == 1.  So when
> stop_at_level == 5, the custom unwinder calculates a frame-id for frame #4,
> not frame #5.  But the frame-id it calculates is the one for frame #5, so
> unwinding will stop at frame #5 because the frame-ids for frame #4 and
> frame #5 are identical. ]
> 
> This relies on the test-case to calculate the offending frame-id, and the
> problem on s390x is that that calculation is incorrect.
> 
> Fix this by using "maint print frame-id" to get all frame-ids, and using those
> instead.
> 

Ping.

Thanks,
- Tom

> Tested on x86_64-linux and s390x-linux.
> ---
>   .../gdb.base/inline-frame-cycle-unwind.exp          | 13 +++++++++++++
>   gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py |  8 +++++---
>   2 files changed, 18 insertions(+), 3 deletions(-)
> 
> diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> index 7fc47af624f..5c6504323ee 100644
> --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> @@ -72,6 +72,19 @@ gdb_continue_to_breakpoint "stop at test breakpoint"
>   gdb_test_no_output "source ${pyfile}"\
>       "import python scripts"
>   
> +foreach_with_prefix n { 0 1 2 3 4 5 6 } {
> +    set sp 0x0
> +    set pc 0x0
> +    gdb_test_multiple "maint print frame-id $n" "" {
> +	-re -wrap "frame-id for frame #$n: {stack=($hex),code=($hex),.*}" {
> +	    set sp $expect_out(1,string)
> +	    set pc $expect_out(2,string)
> +	    gdb_test_no_output "python frame_id_sp.append($sp)"
> +	    gdb_test_no_output "python frame_id_pc.append($pc)"
> +	}
> +    }
> +}
> +
>   # Test with and without filters.
>   foreach bt_cmd { "bt" "bt -no-filters" } {
>       with_test_prefix "$bt_cmd" {
> diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> index 55dea989512..25a67b1a7c9 100644
> --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> @@ -26,6 +26,9 @@ stop_at_level = None
>   # function called recursively.
>   stack_adjust = None
>   
> +frame_id_sp = []
> +frame_id_pc = []
> +
>   
>   class FrameId(object):
>       def __init__(self, sp, pc):
> @@ -55,9 +58,8 @@ class TestUnwinder(Unwinder):
>           if stop_at_level not in [1, 3, 5]:
>               raise gdb.GdbError("invalid stop_at_level")
>   
> -        sp_desc = pending_frame.architecture().registers().find("sp")
> -        sp = pending_frame.read_register(sp_desc) + stack_adjust
> -        pc = (gdb.lookup_symbol("normal_func"))[0].value().address
> +        sp = frame_id_sp[stop_at_level]
> +        pc = frame_id_pc[stop_at_level]
>           unwinder = pending_frame.create_unwind_info(FrameId(sp, pc))
>   
>           for reg in pending_frame.architecture().registers("general"):
> 
> base-commit: 2271dee682787051c0628c869d7cdb220bdd0e67


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PING^2][PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2026-01-03 15:12 ` [PING][PATCH] " Tom de Vries
@ 2026-01-19 18:36   ` Tom de Vries
  0 siblings, 0 replies; 10+ messages in thread
From: Tom de Vries @ 2026-01-19 18:36 UTC (permalink / raw)
  To: gdb-patches; +Cc: Kevin Buettner

On 1/3/26 4:12 PM, Tom de Vries wrote:
> On 12/11/25 2:39 PM, Tom de Vries wrote:
>> With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, 
>> I run
>> into:
>> ...
>>   (gdb) bt^M
>>   #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>>   #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   #2  0x000000000100065c in inline_func () at inline-frame-cycle- 
>> unwind.c:45^M
>>   #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   Backtrace stopped: previous frame identical to this frame (corrupt 
>> stack?)^M
>>   (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^M
>>   #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>>   #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   #2  0x0000000000401157 in inline_func () at inline-frame-cycle- 
>> unwind.c:45^M
>>   #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   #4  0x0000000000401157 in inline_func () at inline-frame-cycle- 
>> unwind.c:45^M
>>   #5  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   Backtrace stopped: previous frame identical to this frame (corrupt 
>> stack?)^M
>>   (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is 
>> broken \
>>     at frame 5
>> ...
>>
>> AFAIU, the mechanism of the test is as follows: the custom unwinder 
>> produces the
>> frame-id for frame #5 at frame #4.  Consequently, when arriving at 
>> frame #5, a
>> cycle is detected.
>>
>> [ It took me a while to understand this because of the following off- 
>> by-one
>> confusion: for frame #0, we get pending_frame.level() == 1.  So when
>> stop_at_level == 5, the custom unwinder calculates a frame-id for 
>> frame #4,
>> not frame #5.  But the frame-id it calculates is the one for frame #5, so
>> unwinding will stop at frame #5 because the frame-ids for frame #4 and
>> frame #5 are identical. ]
>>
>> This relies on the test-case to calculate the offending frame-id, and the
>> problem on s390x is that that calculation is incorrect.
>>
>> Fix this by using "maint print frame-id" to get all frame-ids, and 
>> using those
>> instead.
>>

Ping^2.

Thanks,
- Tom

>> Tested on x86_64-linux and s390x-linux.
>> ---
>>   .../gdb.base/inline-frame-cycle-unwind.exp          | 13 +++++++++++++
>>   gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py |  8 +++++---
>>   2 files changed, 18 insertions(+), 3 deletions(-)
>>
>> diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp b/ 
>> gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
>> index 7fc47af624f..5c6504323ee 100644
>> --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
>> +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
>> @@ -72,6 +72,19 @@ gdb_continue_to_breakpoint "stop at test breakpoint"
>>   gdb_test_no_output "source ${pyfile}"\
>>       "import python scripts"
>> +foreach_with_prefix n { 0 1 2 3 4 5 6 } {
>> +    set sp 0x0
>> +    set pc 0x0
>> +    gdb_test_multiple "maint print frame-id $n" "" {
>> +    -re -wrap "frame-id for frame #$n: {stack=($hex),code=($hex),.*}" {
>> +        set sp $expect_out(1,string)
>> +        set pc $expect_out(2,string)
>> +        gdb_test_no_output "python frame_id_sp.append($sp)"
>> +        gdb_test_no_output "python frame_id_pc.append($pc)"
>> +    }
>> +    }
>> +}
>> +
>>   # Test with and without filters.
>>   foreach bt_cmd { "bt" "bt -no-filters" } {
>>       with_test_prefix "$bt_cmd" {
>> diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py b/ 
>> gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
>> index 55dea989512..25a67b1a7c9 100644
>> --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
>> +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
>> @@ -26,6 +26,9 @@ stop_at_level = None
>>   # function called recursively.
>>   stack_adjust = None
>> +frame_id_sp = []
>> +frame_id_pc = []
>> +
>>   class FrameId(object):
>>       def __init__(self, sp, pc):
>> @@ -55,9 +58,8 @@ class TestUnwinder(Unwinder):
>>           if stop_at_level not in [1, 3, 5]:
>>               raise gdb.GdbError("invalid stop_at_level")
>> -        sp_desc = pending_frame.architecture().registers().find("sp")
>> -        sp = pending_frame.read_register(sp_desc) + stack_adjust
>> -        pc = (gdb.lookup_symbol("normal_func"))[0].value().address
>> +        sp = frame_id_sp[stop_at_level]
>> +        pc = frame_id_pc[stop_at_level]
>>           unwinder = pending_frame.create_unwind_info(FrameId(sp, pc))
>>           for reg in pending_frame.architecture().registers("general"):
>>
>> base-commit: 2271dee682787051c0628c869d7cdb220bdd0e67
> 


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2025-12-11 13:39 [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative) Tom de Vries
  2026-01-03 15:12 ` [PING][PATCH] " Tom de Vries
@ 2026-01-20 10:38 ` Andrew Burgess
  2026-01-20 14:30 ` Andrew Burgess
  2026-01-24 23:19 ` Kevin Buettner
  3 siblings, 0 replies; 10+ messages in thread
From: Andrew Burgess @ 2026-01-20 10:38 UTC (permalink / raw)
  To: Tom de Vries, gdb-patches


Thanks for looking into this.  I have a few comments...

Tom de Vries <tdevries@suse.de> writes:

> With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run
> into:
> ...
>  (gdb) bt^M
>  #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>  #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>  #2  0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45^M
>  #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>  Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>  (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^M
>  #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>  #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>  #2  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>  #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>  #4  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>  #5  normal_func () at inline-frame-cycle-unwind.c:32^M
>  Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>  (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
>    at frame 5
> ...
>
> AFAIU, the mechanism of the test is as follows: the custom unwinder produces the
> frame-id for frame #5 at frame #4.  Consequently, when arriving at frame #5, a
> cycle is detected.
>
> [ It took me a while to understand this because of the following off-by-one
> confusion: for frame #0, we get pending_frame.level() == 1.  So when
> stop_at_level == 5, the custom unwinder calculates a frame-id for frame #4,
> not frame #5.  But the frame-id it calculates is the one for frame #5, so
> unwinding will stop at frame #5 because the frame-ids for frame #4 and
> frame #5 are identical. ]

I'm going to need to think about this some more, as still don't
understand this description of the problem.  For now I'm assuming this
is all correct, so I have a few comments on the changes below ...

>
> This relies on the test-case to calculate the offending frame-id, and the
> problem on s390x is that that calculation is incorrect.
>
> Fix this by using "maint print frame-id" to get all frame-ids, and using those
> instead.
>
> Tested on x86_64-linux and s390x-linux.
> ---
>  .../gdb.base/inline-frame-cycle-unwind.exp          | 13 +++++++++++++
>  gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py |  8 +++++---
>  2 files changed, 18 insertions(+), 3 deletions(-)
>
> diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> index 7fc47af624f..5c6504323ee 100644
> --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> @@ -72,6 +72,19 @@ gdb_continue_to_breakpoint "stop at test breakpoint"
>  gdb_test_no_output "source ${pyfile}"\
>      "import python scripts"
>  
> +foreach_with_prefix n { 0 1 2 3 4 5 6 } {
> +    set sp 0x0
> +    set pc 0x0

Are these set to 0 needed?  We don't try to use these unless we match
the regexp, in which case they'll be set to the values that are matched,
right? 

> +    gdb_test_multiple "maint print frame-id $n" "" {
> +	-re -wrap "frame-id for frame #$n: {stack=($hex),code=($hex),.*}" {
> +	    set sp $expect_out(1,string)
> +	    set pc $expect_out(2,string)
> +	    gdb_test_no_output "python frame_id_sp.append($sp)"
> +	    gdb_test_no_output "python frame_id_pc.append($pc)"
> +	}
> +    }
> +}
> +
>  # Test with and without filters.
>  foreach bt_cmd { "bt" "bt -no-filters" } {
>      with_test_prefix "$bt_cmd" {
> diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> index 55dea989512..25a67b1a7c9 100644
> --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> @@ -26,6 +26,9 @@ stop_at_level = None
>  # function called recursively.
>  stack_adjust = None

I think you should delete STACK_ADJUST, and the code at the end of this
file that sets it up.

The comment at the end of the file which describes what the setup code
(at the end of the file) is doing should probably then be moved to the
start of the file, and ideally extended to explain that frame_id_sp and
frame_id_pc need to be filled in by the .exp script in order for the
test to work correctly.

The use of STACK_ADJUST within TestUnwinder.__call__ can then be
removed, by maybe replace it with a check, something like:

  if len(frame_id_sp) < stop_at_level or len(frame_id_pc) < stop_at_level:
    raise gdb.GdbError("invalid stack_adjust")

Thanks,
Andrew

>  
> +frame_id_sp = []
> +frame_id_pc = []
> +
>  
>  class FrameId(object):
>      def __init__(self, sp, pc):
> @@ -55,9 +58,8 @@ class TestUnwinder(Unwinder):
>          if stop_at_level not in [1, 3, 5]:
>              raise gdb.GdbError("invalid stop_at_level")
>  
> -        sp_desc = pending_frame.architecture().registers().find("sp")
> -        sp = pending_frame.read_register(sp_desc) + stack_adjust
> -        pc = (gdb.lookup_symbol("normal_func"))[0].value().address
> +        sp = frame_id_sp[stop_at_level]
> +        pc = frame_id_pc[stop_at_level]
>          unwinder = pending_frame.create_unwind_info(FrameId(sp, pc))
>  
>          for reg in pending_frame.architecture().registers("general"):
>
> base-commit: 2271dee682787051c0628c869d7cdb220bdd0e67
> -- 
> 2.51.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2025-12-11 13:39 [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative) Tom de Vries
  2026-01-03 15:12 ` [PING][PATCH] " Tom de Vries
  2026-01-20 10:38 ` [PATCH] " Andrew Burgess
@ 2026-01-20 14:30 ` Andrew Burgess
  2026-01-20 20:38   ` Andrew Burgess
  2026-01-21 13:32   ` Tom de Vries
  2026-01-24 23:19 ` Kevin Buettner
  3 siblings, 2 replies; 10+ messages in thread
From: Andrew Burgess @ 2026-01-20 14:30 UTC (permalink / raw)
  To: Tom de Vries, gdb-patches

Tom de Vries <tdevries@suse.de> writes:

> With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run
> into:
> ...
>  (gdb) bt^M
>  #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>  #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>  #2  0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45^M
>  #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>  Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>  (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^M
>  #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>  #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>  #2  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>  #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>  #4  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>  #5  normal_func () at inline-frame-cycle-unwind.c:32^M
>  Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>  (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
>    at frame 5
> ...
>
> AFAIU, the mechanism of the test is as follows: the custom unwinder produces the
> frame-id for frame #5 at frame #4.  Consequently, when arriving at frame #5, a
> cycle is detected.

I don't believe this is how it works.  See below for what I think happens.

>
> [ It took me a while to understand this because of the following off-by-one
> confusion: for frame #0, we get pending_frame.level() == 1.  So when
> stop_at_level == 5, the custom unwinder calculates a frame-id for frame #4,
> not frame #5.  But the frame-id it calculates is the one for frame #5, so
> unwinding will stop at frame #5 because the frame-ids for frame #4 and
> frame #5 are identical. ]

I went back and looked at the unpatched test again, and I don't believe
this "off-by-one" issue is a thing, at least, I don't see one based on
your description.

It does appear that for frame #0 we get pending_frame.level() == 1, but
this isn't what's really happening.

Frame #0 is inline, so the Python frame unwinder is never run for this
frame.  The first frame for which the Python frame unwinder is run is
frame #1, hence pending_frame.level() == 1.

The frame-id calculated within TestUnwinder.__call__ is the frame-id for
the previous (outer, older) frame.  So, when pending_frame.level() == 5
we are calculating the frame-id for frame #6.  As frame #6 then appears
to be identical to frame #5, a cycle is detected and the backtrace ends.

> This relies on the test-case to calculate the offending frame-id, and the
> problem on s390x is that that calculation is incorrect.

Agreed.

>
> Fix this by using "maint print frame-id" to get all frame-ids, and using those
> instead.

I suspect this is the better approach.  I'm not sure 'maint print
frame-id' existed when I wrote this test (but I could be wrong).  Still,
it would be nice if we could understand exactly why this is going wrong
in this case.

My main question here is that we're only calculating a "fake" frame-id
for frame #6, so this should be the first frame that GDB doesn't
display.  That GDB is cutting off after #3 would seem to indicate that
#4, or maybe #5 (as the non-inline frame) is a duplicate of an earlier
frame, but I don't understand how that can be the case.

>
> Tested on x86_64-linux and s390x-linux.
> ---
>  .../gdb.base/inline-frame-cycle-unwind.exp          | 13 +++++++++++++
>  gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py |  8 +++++---
>  2 files changed, 18 insertions(+), 3 deletions(-)
>
> diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> index 7fc47af624f..5c6504323ee 100644
> --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.exp
> @@ -72,6 +72,19 @@ gdb_continue_to_breakpoint "stop at test breakpoint"
>  gdb_test_no_output "source ${pyfile}"\
>      "import python scripts"
>  
> +foreach_with_prefix n { 0 1 2 3 4 5 6 } {
> +    set sp 0x0
> +    set pc 0x0
> +    gdb_test_multiple "maint print frame-id $n" "" {
> +	-re -wrap "frame-id for frame #$n: {stack=($hex),code=($hex),.*}" {
> +	    set sp $expect_out(1,string)
> +	    set pc $expect_out(2,string)
> +	    gdb_test_no_output "python frame_id_sp.append($sp)"
> +	    gdb_test_no_output "python frame_id_pc.append($pc)"
> +	}
> +    }
> +}
> +
>  # Test with and without filters.
>  foreach bt_cmd { "bt" "bt -no-filters" } {
>      with_test_prefix "$bt_cmd" {
> diff --git a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> index 55dea989512..25a67b1a7c9 100644
> --- a/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> +++ b/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
> @@ -26,6 +26,9 @@ stop_at_level = None
>  # function called recursively.
>  stack_adjust = None
>  
> +frame_id_sp = []
> +frame_id_pc = []
> +
>  
>  class FrameId(object):
>      def __init__(self, sp, pc):
> @@ -55,9 +58,8 @@ class TestUnwinder(Unwinder):
>          if stop_at_level not in [1, 3, 5]:
>              raise gdb.GdbError("invalid stop_at_level")
>  
> -        sp_desc = pending_frame.architecture().registers().find("sp")
> -        sp = pending_frame.read_register(sp_desc) + stack_adjust
> -        pc = (gdb.lookup_symbol("normal_func"))[0].value().address
> +        sp = frame_id_sp[stop_at_level]
> +        pc = frame_id_pc[stop_at_level]
>          unwinder = pending_frame.create_unwind_info(FrameId(sp, pc))

I wonder if here you could:

        print(f"DEBUG: frame level: {pending_frame.level()}")
        sp_desc = pending_frame.architecture().registers().find("sp")
        prev_sp = int(pending_frame.read_register(sp_desc))
        print(f"DEBUG: previous-sp: {prev_sp:x}, adjustment: {int(stack_adjust):x}")
        adjusted_sp = int(prev_sp + stack_adjust)
        print(f"DEBUG: adjusted-sp: {adjusted_sp:x}")
        print(f"DEBUG: new-sp: {frame_id_sp[stop_at_level]:x}")
        addr = int((gdb.lookup_symbol("normal_func"))[0].value().address)
        print(f"DEBUG: previous-pc: {addr:x}")
        print(f"DEBUG: new-pc: {frame_id_pc[stop_at_level]:x}")

then we'd be able to see which part of the frame-id was being generated
incorrectly.

Thanks,
Andrew



>  
>          for reg in pending_frame.architecture().registers("general"):
>
> base-commit: 2271dee682787051c0628c869d7cdb220bdd0e67
> -- 
> 2.51.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2026-01-20 14:30 ` Andrew Burgess
@ 2026-01-20 20:38   ` Andrew Burgess
  2026-01-21 13:09     ` Tom de Vries
  2026-01-21 13:32   ` Tom de Vries
  1 sibling, 1 reply; 10+ messages in thread
From: Andrew Burgess @ 2026-01-20 20:38 UTC (permalink / raw)
  To: Tom de Vries, gdb-patches


Andrew Burgess <aburgess@redhat.com> writes:

> Tom de Vries <tdevries@suse.de> writes:
>
>> With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run
>> into:
>> ...
>>  (gdb) bt^M
>>  #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>>  #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>>  #2  0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45^M
>>  #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>>  Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>>  (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^M
>>  #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>>  #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>>  #2  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>>  #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>>  #4  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>>  #5  normal_func () at inline-frame-cycle-unwind.c:32^M
>>  Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>>  (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
>>    at frame 5
>> ...

I took a look at this issue.  I'm running out of time today, so I
thought I'd just dump what I've seen so far.

Here's a table I made for x86-64:

  | Level |    $frame_base |            $sp |    $code | Type   |
  |-------+----------------+----------------+----------+--------+
  |     0 | 0x7fffffffa560 | 0x7fffffffa550 | 0x401128 | inline |
  |     1 | 0x7fffffffa560 | 0x7fffffffa550 | 0x401106 | normal |
  |     2 | 0x7fffffffa570 | 0x7fffffffa560 | 0x401128 | inline |
  |     3 | 0x7fffffffa570 | 0x7fffffffa560 | 0x401106 | normal |
  |     4 | 0x7fffffffa580 | 0x7fffffffa570 | 0x401128 | inline |
  |     5 | 0x7fffffffa580 | 0x7fffffffa570 | 0x401106 | normal |
  |     6 | 0x7fffffffa590 | 0x7fffffffa580 | 0x401189 | normal |
  |-------+----------------+----------------+----------+--------+

The $frame_base and $code are what we see in the frame-id, and $sp is
the stack pointer ($sp) register in each frame.

Notice the relationship between $frame_base and $sp as we move up the
stack; as we move past each inline/normal frame pair the previous
$frame_base becomes the $sp value.  This makes sense to me.

As a result of this the difference between $frame_base values is the
same as the difference between $frame_base and $sp values.

Here's the same table for s390:

  | Level |   $frame_base |           $sp |     $code | Type   |
  |-------+---------------+---------------+-----------+--------+
  |     0 | 0x3ffffff7898 | 0x3ffffff7758 | 0x1000620 | inline |
  |     1 | 0x3ffffff7898 | 0x3ffffff7758 | 0x10005f0 | normal |
  |     2 | 0x3ffffff7938 | 0x3ffffff77f8 | 0x1000620 | inline |
  |     3 | 0x3ffffff7938 | 0x3ffffff77f8 | 0x10005f0 | normal |
  |     4 | 0x3ffffff79d8 | 0x3ffffff7898 | 0x1000620 | inline |
  |     5 | 0x3ffffff79d8 | 0x3ffffff7898 | 0x10005f0 | normal |
  |     6 | 0x3ffffff7a78 | 0x3ffffff7938 | 0x1000698 | normal |
  |-------+---------------+---------------+-----------+--------+

There are a couple of things here that I find are unexpected, but are
probably a result of me not understanding the s390 ABI well enough.

First, and this is not relevant to the test failure, but the inline and
normal (non-inline) frames don't have the same $code address.  I double
checked the disassembly for frame #0, and it does appear like the
function has been inlined, but maybe I'm wrong on this.  This means that
GDB manages to find some address within the function and claims this is
the "start" of the inlined function.  Weird.  But like I said, I don't
think this is really an issue for this test.

Second, the difference between $frame_base values is 0xa0, and the
difference between $sp values is also 0xa0.  This is reassuring, each
recursive frame moves 0xa0 bytes down the stack.

But the difference between $sp and $frame_base values is 0x140 bytes.
This was a little unexpected.

But, I guess, this is fine.  The DWARF spec doesn't (as far as I know)
claim that the frame base has to mean anything specific, it's just an
address that's unique to a frame, and from which we can offset to find
frame local data.

However, the test tries to calculate the frame #5 $frame_base address by
taking the difference between frame #2 and frame #0 $sp value, and
adding this difference to the frame #5 $sp value.  For most
architectures this works fine.  But clearly this isn't going to work for
s390.

I don't have time right now, but tomorrow I'll double check the two
weird things above, I'd like to confirm these really are how the DWARF
defines things.  But assuming it is, then I think your proposed solution
is the right way to go; though I think the commit message needs updating
to reflect the above.

Having spent some time now running and debugging this test, I think one
neat thing about the way it was originally written, is that the Python
script could be used stand-alone, so long as the test program was
stopped in the right place you could source the Python script and it
would do the right thing.

You proposed patch changes this slightly so that the .exp script is
needed to setup some of the frame-id data.  That's absolutely fine, but
can make the test harder to run outside DejaGNU; if the stack addresses
change the user needs to figure out the updated values.  It's a really
minor thing, but how about the patch below (still needs a commit
message), but this does more of the work in the Python script again.

Thanks,
Andrew

--

diff --git i/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py w/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
index 80cfb864f21..974d40d9219 100644
--- i/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
+++ w/gdb/testsuite/gdb.base/inline-frame-cycle-unwind.py
@@ -13,6 +13,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import re
 import gdb
 from gdb.unwinder import Unwinder
 
@@ -21,11 +22,8 @@ from gdb.unwinder import Unwinder
 # was written for.
 stop_at_level = None
 
-# Set this to the stack frame size of frames 1, 3, and 5.  These
-# frames will all have the same stack frame size as they are the same
-# function called recursively.
-stack_adjust = None
-
+# List of FrameId instances, one for each stack frame.
+frame_ids = []
 
 class FrameId(object):
     def __init__(self, sp, pc):
@@ -49,16 +47,13 @@ class TestUnwinder(Unwinder):
         if stop_at_level is None or pending_frame.level() != stop_at_level:
             return None
 
-        if stack_adjust is None:
-            raise gdb.GdbError("invalid stack_adjust")
+        if len(frame_ids) < stop_at_level:
+            raise gdb.GdbError("not enough parsed frame-ids")
 
         if stop_at_level not in [1, 3, 5]:
             raise gdb.GdbError("invalid stop_at_level")
 
-        sp_desc = pending_frame.architecture().registers().find("sp")
-        sp = pending_frame.read_register(sp_desc) + stack_adjust
-        pc = (gdb.lookup_symbol("normal_func"))[0].value().address
-        unwinder = pending_frame.create_unwind_info(FrameId(sp, pc))
+        unwinder = pending_frame.create_unwind_info(frame_ids[stop_at_level])
 
         for reg in pending_frame.architecture().registers("general"):
             val = pending_frame.read_register(reg)
@@ -76,11 +71,22 @@ gdb.unwinder.register_unwinder(None, TestUnwinder(), True)
 #
 #   main -> normal_func -> inline_func -> normal_func -> inline_func -> normal_func -> inline_func
 #
-# Compute the stack frame size of normal_func, which has inline_func
-# inlined within it.
-f0 = gdb.newest_frame()
-f1 = f0.older()
-f2 = f1.older()
-f0_sp = f0.read_register("sp")
-f2_sp = f2.read_register("sp")
-stack_adjust = f2_sp - f0_sp
+# Iterate through frames 0 to 5, parse their frame-id and store it
+# into the global FRAME_IDS list.
+for i in range(6):
+    # Select the specific frame.  Use to_string to suppress output.
+    gdb.execute(f"frame {i}", to_string=True)
+
+    # Get the frame-id in a verbose text form.
+    output = gdb.execute("maint print frame-id", to_string=True)
+
+    # Parse the frame-id in OUTPUT, find the stack and code addresses.
+    match = re.search(r"stack=(0x[0-9a-fA-F]+).*?code=(0x[0-9a-fA-F]+)", output)
+
+    if match:
+        # Create the FrameId object.
+        sp_addr = int(match.group(1), 16)
+        pc_addr = int(match.group(2), 16)
+        frame_ids.append(FrameId(sp_addr, pc_addr))
+    else:
+        raise gdb.GdbError("Could not parse frame-id for frame #%d" % i)


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2026-01-20 20:38   ` Andrew Burgess
@ 2026-01-21 13:09     ` Tom de Vries
  0 siblings, 0 replies; 10+ messages in thread
From: Tom de Vries @ 2026-01-21 13:09 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 1/20/26 9:38 PM, Andrew Burgess wrote:
> Having spent some time now running and debugging this test, I think one
> neat thing about the way it was originally written, is that the Python
> script could be used stand-alone, so long as the test program was
> stopped in the right place you could source the Python script and it
> would do the right thing.
> 
> You proposed patch changes this slightly so that the .exp script is
> needed to setup some of the frame-id data.  That's absolutely fine, but
> can make the test harder to run outside DejaGNU; if the stack addresses
> change the user needs to figure out the updated values.  It's a really
> minor thing, but how about the patch below (still needs a commit
> message), but this does more of the work in the Python script again.

Hi Andrew,

I've taken your patch, and added a bit onto it:
- capture all available frame IDs: for frames #0 - #6.  Frame #6 was
   missing.
- print the captured frame IDs
- added a comment in TestUnwinder.__call__ that explains in detail what
   the normal behavior is, and how we're deviating from it to make the
   backtrace stop.
- added commit message

I've submitted this as a v2 ( 
https://sourceware.org/pipermail/gdb-patches/2026-January/224308.html ).

I hope my understanding of what the test-case is trying to do is correct 
now.

Thanks,
- Tom


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2026-01-20 14:30 ` Andrew Burgess
  2026-01-20 20:38   ` Andrew Burgess
@ 2026-01-21 13:32   ` Tom de Vries
  2026-01-21 16:50     ` Andrew Burgess
  1 sibling, 1 reply; 10+ messages in thread
From: Tom de Vries @ 2026-01-21 13:32 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 1/20/26 3:30 PM, Andrew Burgess wrote:
> Tom de Vries<tdevries@suse.de> writes:
> 
>> With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run
>> into:
>> ...
>>   (gdb) bt^M
>>   #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>>   #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   #2  0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45^M
>>   #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>>   (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^M
>>   #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>>   #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   #2  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>>   #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   #4  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>>   #5  normal_func () at inline-frame-cycle-unwind.c:32^M
>>   Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>>   (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
>>     at frame 5
>> ...
>>
>> AFAIU, the mechanism of the test is as follows: the custom unwinder produces the
>> frame-id for frame #5 at frame #4.  Consequently, when arriving at frame #5, a
>> cycle is detected.
> I don't believe this is how it works.  See below for what I think happens.
> 
>> [ It took me a while to understand this because of the following off-by-one
>> confusion: for frame #0, we get pending_frame.level() == 1.  So when
>> stop_at_level == 5, the custom unwinder calculates a frame-id for frame #4,
>> not frame #5.  But the frame-id it calculates is the one for frame #5, so
>> unwinding will stop at frame #5 because the frame-ids for frame #4 and
>> frame #5 are identical. ]
> I went back and looked at the unpatched test again, and I don't believe
> this "off-by-one" issue is a thing, at least, I don't see one based on
> your description.
> 
> It does appear that for frame #0 we get pending_frame.level() == 1, but
> this isn't what's really happening.
> 
> Frame #0 is inline, so the Python frame unwinder is never run for this
> frame.  The first frame for which the Python frame unwinder is run is
> frame #1, hence pending_frame.level() == 1.
> 
> The frame-id calculated within TestUnwinder.__call__ is the frame-id for
> the previous (outer, older) frame.  So, when pending_frame.level() == 5
> we are calculating the frame-id for frame #6.  As frame #6 then appears
> to be identical to frame #5, a cycle is detected and the backtrace ends.

After reading the documentation ( 
https://www.sourceware.org/gdb/current/onlinedocs/gdb.html/Unwinding-Frames-in-Python.html 
):
...
For the frames it can sniff an unwinder provides two additional methods: 
it can return frame’s ID, and it can fetch registers from the previous 
frame.

   ...

You implement a frame unwinder in Python as a class with which has two 
attributes, name and enabled, with obvious meanings, and a single method 
__call__, which examines a given frame and returns an object (an 
instance of gdb.UnwindInfo class) describing it. If an unwinder does not 
recognize a frame, it should return None. The code in GDB that enables 
writing unwinders in Python uses this object to return frame’s ID and 
previous frame registers when GDB core asks for them.
...
my understanding is that for pending_frame.level() == 5, we calculate 
the frame-id for frame #5.

On x86_64-linux, indeed that's the case, the output of maint print 
frame-id 5 matches the sp/pc calculated for pending_frame.level() == 5. 
This is shown in more detail in the commit message of the v2 I've submitted.

Thanks,
- Tom

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2026-01-21 13:32   ` Tom de Vries
@ 2026-01-21 16:50     ` Andrew Burgess
  0 siblings, 0 replies; 10+ messages in thread
From: Andrew Burgess @ 2026-01-21 16:50 UTC (permalink / raw)
  To: Tom de Vries, gdb-patches

Tom de Vries <tdevries@suse.de> writes:

> On 1/20/26 3:30 PM, Andrew Burgess wrote:
>> Tom de Vries<tdevries@suse.de> writes:
>> 
>>> With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I run
>>> into:
>>> ...
>>>   (gdb) bt^M
>>>   #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>>>   #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>>>   #2  0x000000000100065c in inline_func () at inline-frame-cycle-unwind.c:45^M
>>>   #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>>>   Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>>>   (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^M
>>>   #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>>>   #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>>>   #2  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>>>   #3  normal_func () at inline-frame-cycle-unwind.c:32^M
>>>   #4  0x0000000000401157 in inline_func () at inline-frame-cycle-unwind.c:45^M
>>>   #5  normal_func () at inline-frame-cycle-unwind.c:32^M
>>>   Backtrace stopped: previous frame identical to this frame (corrupt stack?)^M
>>>   (gdb) PASS: $exp: bt: cycle at level 5: backtrace when the unwind is broken \
>>>     at frame 5
>>> ...
>>>
>>> AFAIU, the mechanism of the test is as follows: the custom unwinder produces the
>>> frame-id for frame #5 at frame #4.  Consequently, when arriving at frame #5, a
>>> cycle is detected.
>> I don't believe this is how it works.  See below for what I think happens.
>> 
>>> [ It took me a while to understand this because of the following off-by-one
>>> confusion: for frame #0, we get pending_frame.level() == 1.  So when
>>> stop_at_level == 5, the custom unwinder calculates a frame-id for frame #4,
>>> not frame #5.  But the frame-id it calculates is the one for frame #5, so
>>> unwinding will stop at frame #5 because the frame-ids for frame #4 and
>>> frame #5 are identical. ]
>> I went back and looked at the unpatched test again, and I don't believe
>> this "off-by-one" issue is a thing, at least, I don't see one based on
>> your description.
>> 
>> It does appear that for frame #0 we get pending_frame.level() == 1, but
>> this isn't what's really happening.
>> 
>> Frame #0 is inline, so the Python frame unwinder is never run for this
>> frame.  The first frame for which the Python frame unwinder is run is
>> frame #1, hence pending_frame.level() == 1.
>> 
>> The frame-id calculated within TestUnwinder.__call__ is the frame-id for
>> the previous (outer, older) frame.  So, when pending_frame.level() == 5
>> we are calculating the frame-id for frame #6.  As frame #6 then appears
>> to be identical to frame #5, a cycle is detected and the backtrace ends.
>
> After reading the documentation ( 
> https://www.sourceware.org/gdb/current/onlinedocs/gdb.html/Unwinding-Frames-in-Python.html 
> ):
> ...
> For the frames it can sniff an unwinder provides two additional methods: 
> it can return frame’s ID, and it can fetch registers from the previous 
> frame.
>
>    ...
>
> You implement a frame unwinder in Python as a class with which has two 
> attributes, name and enabled, with obvious meanings, and a single method 
> __call__, which examines a given frame and returns an object (an 
> instance of gdb.UnwindInfo class) describing it. If an unwinder does not 
> recognize a frame, it should return None. The code in GDB that enables 
> writing unwinders in Python uses this object to return frame’s ID and 
> previous frame registers when GDB core asks for them.
> ...
> my understanding is that for pending_frame.level() == 5, we calculate 
> the frame-id for frame #5.
>
> On x86_64-linux, indeed that's the case, the output of maint print 
> frame-id 5 matches the sp/pc calculated for pending_frame.level() == 5. 
> This is shown in more detail in the commit message of the v2 I've submitted.

You are 100% correct, and I feel I owe you an apology on this point.

I'll take a look at v2.

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative)
  2025-12-11 13:39 [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative) Tom de Vries
                   ` (2 preceding siblings ...)
  2026-01-20 14:30 ` Andrew Burgess
@ 2026-01-24 23:19 ` Kevin Buettner
  3 siblings, 0 replies; 10+ messages in thread
From: Kevin Buettner @ 2026-01-24 23:19 UTC (permalink / raw)
  To: Tom de Vries; +Cc: gdb-patches

Hi Tom,

On Thu, 11 Dec 2025 14:39:46 +0100
Tom de Vries <tdevries@suse.de> wrote:

> With test-case gdb.base/inline-frame-cycle-unwind.exp on s390x-linux, I
> run into:
> ...
>  (gdb) bt^M
>  #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>  #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>  #2  0x000000000100065c in inline_func () at
> inline-frame-cycle-unwind.c:45^M #3  normal_func () at
> inline-frame-cycle-unwind.c:32^M Backtrace stopped: previous frame
> identical to this frame (corrupt stack?)^M (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^M
>  #0  inline_func () at inline-frame-cycle-unwind.c:49^M
>  #1  normal_func () at inline-frame-cycle-unwind.c:32^M
>  #2  0x0000000000401157 in inline_func () at
> inline-frame-cycle-unwind.c:45^M #3  normal_func () at
> inline-frame-cycle-unwind.c:32^M #4  0x0000000000401157 in inline_func ()
> at inline-frame-cycle-unwind.c:45^M #5  normal_func () at
> inline-frame-cycle-unwind.c:32^M Backtrace stopped: previous frame
> identical to this frame (corrupt stack?)^M (gdb) PASS: $exp: bt: cycle at
> level 5: backtrace when the unwind is broken \ at frame 5
> ...
> 
> AFAIU, the mechanism of the test is as follows: the custom unwinder
> produces the frame-id for frame #5 at frame #4.  Consequently, when
> arriving at frame #5, a cycle is detected.
> 
> [ It took me a while to understand this because of the following
> off-by-one confusion: for frame #0, we get pending_frame.level() == 1.
> So when stop_at_level == 5, the custom unwinder calculates a frame-id for
> frame #4, not frame #5.  But the frame-id it calculates is the one for
> frame #5, so unwinding will stop at frame #5 because the frame-ids for
> frame #4 and frame #5 are identical. ]
> 
> This relies on the test-case to calculate the offending frame-id, and the
> problem on s390x is that that calculation is incorrect.
> 
> Fix this by using "maint print frame-id" to get all frame-ids, and using
> those instead.

Thanks for looking into this.  I like this approach better the one
that I took - fixing the frame-id source so the test works uniformly
across architectures is cleaner than adjusting expectations
per architecture.

One issue: I think that your patch leaves some dead code in the
Python file.  With stack_adjust no longer used, it seems to me that
the following should be removed:

1. The variable declaration / initialization:

   stack_adjust = None

2. The check:

    if stack_adjust is None:
      raise gdb.GdbError("invalid stack_adjust")

3. The computation at module load time:

    f0 = gdb.newest_frame()
    f1 = f0.older()
    f2 = f1.older()
    f0_sp = f0.read_register("sp")
    f2_sp = f2.read_register("sp")
    stack_adjust = f2_sp - f0_sp

Kevin


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-01-24 23:20 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-11 13:39 [PATCH] [gdb/testsuite] Fix gdb.base/inline-frame-cycle-unwind.exp for s390x (alternative) Tom de Vries
2026-01-03 15:12 ` [PING][PATCH] " Tom de Vries
2026-01-19 18:36   ` [PING^2][PATCH] " Tom de Vries
2026-01-20 10:38 ` [PATCH] " Andrew Burgess
2026-01-20 14:30 ` Andrew Burgess
2026-01-20 20:38   ` Andrew Burgess
2026-01-21 13:09     ` Tom de Vries
2026-01-21 13:32   ` Tom de Vries
2026-01-21 16:50     ` Andrew Burgess
2026-01-24 23:19 ` Kevin Buettner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox