* [PATCH] [gdb] Fix missing print frame when stepping out of function
@ 2026-03-12 12:02 Tom de Vries
2026-03-18 14:29 ` Andrew Burgess
2026-04-03 15:17 ` Tom de Vries
0 siblings, 2 replies; 7+ messages in thread
From: Tom de Vries @ 2026-03-12 12:02 UTC (permalink / raw)
To: gdb-patches
Consider test-case gdb.dwarf2/dw2-extend-inline-block.exp, specifically the
"contiguous block" prefix part, which can be stepped through like this:
...
$ gdb -q outputs/gdb.dwarf2/dw2-extend-inline-block/dw2-extend-inline-block-5
Reading symbols from dw2-extend-inline-block-5...
(gdb) start
...
Temporary breakpoint 1, main () at dw2-extend-inline-block.c:35
35 /* main:2 */
(gdb) s
36 /* main:3 */ foo (); /* foo call line */
(gdb)
foo () at dw2-extend-inline-block.c:26
26 /* foo:1 */
(gdb) s
27 /* foo:2 */
(gdb) s
28 /* foo:3 */
(gdb) s
main () at dw2-extend-inline-block.c:37
37 /* main:4 */
(gdb)
...
If we slightly modify the line program:
...
DW_LNE_set_address main_4
+ DW_LNS_advance_line 1
DW_LNS_copy
DW_LNE_set_address main_5
- DW_LNS_advance_line 1
DW_LNS_negate_stmt
DW_LNS_copy
...
we change the 36/0x401165 entry into a 37/0x401165 entry:
...
File name Line number Starting address View Stmt
dw2-extend-inline-block.c 34 0x401116 x
dw2-extend-inline-block.c 35 0x401129 x
dw2-extend-inline-block.c 26 0x401138 x
dw2-extend-inline-block.c 27 0x401147 x
dw2-extend-inline-block.c 28 0x401156 x
dw2-extend-inline-block.c 36 0x401156
-dw2-extend-inline-block.c 36 0x401165
+dw2-extend-inline-block.c 37 0x401165
dw2-extend-inline-block.c 37 0x401174 x
dw2-extend-inline-block.c 38 0x401183 x
dw2-extend-inline-block.c 39 0x401192 x
dw2-extend-inline-block.c 40 0x4011a1 x
dw2-extend-inline-block.c - 0x4011b7
...
As it happens, the fix to extend truncated inlined function blocks doesn't
work in this case. This is PR33930.
We can work around this by making sure that the inlined function block isn't
truncated in the first place:
...
- DW_AT_high_pc main_3 addr
+ DW_AT_high_pc main_4 addr
...
But then we still run into PR33981: the problem that gdb doesn't notify us
when stepping out of main:
...
(gdb) step^M
28 /* foo:3 */^M
(gdb) step^M
37 /* main:4 */^M
(gdb)
...
What happens is that the slightly different line program triggers a different
stepping path, which includes a case of "stepped to a different frame, but
it's not the start of a statement", which refreshes the stepping info and
consequently updates tp->control.step_frame_id to the frame id of main.
So by the time we're stopped at line 37, and are trying to figure out what to
print in print_stop_location, this condition evaluates to true:
...
if (tp->control.stop_step
&& (tp->control.step_frame_id
== get_frame_id (get_current_frame ()))
&& (tp->control.step_start_function
== find_symbol_for_pc (tp->stop_pc ())))
...
and we get:
...
/* Finished step in same frame and same file, just print source
line. */
source_flag = SRC_LINE;
...
It's good to realize here that because foo is inlined into main,
tp->control.step_start_function is not foo but main, so consequently the
step_start_function check (which checks if we are still in the same function)
also passes, even though we actually stepped from foo into main.
No longer refreshing the stepping info in the "stepped to a different frame,
but it's not the start of a statement" case does fix the problem.
But the refreshing is needed to be able to handle stepping out of say function
f1 into function f2 and immediately stepping back into f1 again. If we don't
refresh in between, it looks like we stayed in f1.
Fix this by:
- adding a new field thread_control_state::step_frame_id_changed,
- updating the new field in set_step_info when refreshing, and
- using the new field in print_stop_location.
Tested on x86_64-linux.
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33981
---
gdb/gdbthread.h | 4 ++
gdb/infrun.c | 10 ++++-
gdb/infrun.h | 5 ++-
.../gdb.dwarf2/dw2-extend-inline-block.exp | 45 +++++++++++++++++--
4 files changed, 57 insertions(+), 7 deletions(-)
diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
index 68d7aebbbf7..02cf8f01fca 100644
--- a/gdb/gdbthread.h
+++ b/gdb/gdbthread.h
@@ -146,6 +146,10 @@ struct thread_control_state
any inlined frames). */
struct frame_id step_stack_frame_id {};
+ /* True if step_frame_id was changed after the stepping command was
+ issued. */
+ bool step_frame_id_changed = false;
+
/* True if the thread is presently stepping over a breakpoint or
a watchpoint, either with an inline step over or a displaced (out
of line) step, and we're now expecting it to report a trap for
diff --git a/gdb/infrun.c b/gdb/infrun.c
index c05c2b0f42f..df55c9a3c66 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -3084,6 +3084,7 @@ clear_proceed_status_thread (struct thread_info *tp)
tp->control.step_range_end = 0;
tp->control.may_range_step = 0;
tp->control.step_frame_id = null_frame_id;
+ tp->control.step_frame_id_changed = false;
tp->control.step_stack_frame_id = null_frame_id;
tp->control.step_over_calls = STEP_OVER_UNDEBUGGABLE;
tp->control.step_start_function = nullptr;
@@ -4818,12 +4819,16 @@ fetch_inferior_event ()
void
set_step_info (thread_info *tp, const frame_info_ptr &frame,
- struct symtab_and_line sal)
+ struct symtab_and_line sal, bool refresh_p)
{
/* This can be removed once this function no longer implicitly relies on the
inferior_ptid value. */
gdb_assert (inferior_ptid == tp->ptid);
+ if (refresh_p
+ && tp->control.step_frame_id != null_frame_id
+ && tp->control.step_frame_id != get_frame_id (frame))
+ tp->control.step_frame_id_changed = true;
tp->control.step_frame_id = get_frame_id (frame);
tp->control.step_stack_frame_id = get_stack_frame_id (frame);
@@ -8331,7 +8336,7 @@ process_event_stop_test (struct execution_control_state *ecs)
paddress (gdbarch, ecs->event_thread->control.step_range_end),
ecs->event_thread->control.may_range_step);
if (refresh_step_info)
- set_step_info (ecs->event_thread, frame, stop_pc_sal);
+ set_step_info (ecs->event_thread, frame, stop_pc_sal, true);
infrun_debug_printf ("keep going");
@@ -9350,6 +9355,7 @@ print_stop_location (const target_waitstatus &ws)
should) carry around the function and does (or should) use
that when doing a frame comparison. */
if (tp->control.stop_step
+ && !tp->control.step_frame_id_changed
&& (tp->control.step_frame_id
== get_frame_id (get_current_frame ()))
&& (tp->control.step_start_function
diff --git a/gdb/infrun.h b/gdb/infrun.h
index 0a7cdadf1fa..7fc7523be3f 100644
--- a/gdb/infrun.h
+++ b/gdb/infrun.h
@@ -205,10 +205,11 @@ extern int thread_is_stepping_over_breakpoint (int thread);
triggers a non-steppable watchpoint. */
extern int stepping_past_nonsteppable_watchpoint (void);
-/* Record in TP the frame and location we're currently stepping through. */
+/* Record in TP the frame and location we're currently stepping through. If
+ REFRESH_P, we're refreshing step info. */
extern void set_step_info (thread_info *tp,
const frame_info_ptr &frame,
- struct symtab_and_line sal);
+ struct symtab_and_line sal, bool refresh_p = false);
/* Notify interpreters and observers that the current inferior has stopped with
signal SIG. */
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp
index 9e4798b53f3..3f92c1965d8 100644
--- a/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp
+++ b/gdb/testsuite/gdb.dwarf2/dw2-extend-inline-block.exp
@@ -52,8 +52,11 @@ get_func_info main
# Create DWARF for the test. In this case, inline function 'foo' is created
# with a contiguous address range that needs extending.
-proc build_dwarf_for_contiguous_block { asm_file } {
+proc build_dwarf_for_contiguous_block { asm_file {range_correct 0} {variant 0}} {
Dwarf::assemble $asm_file {
+ upvar range_correct range_correct
+ upvar variant variant
+
declare_labels lines_table inline_func
cu { } {
@@ -83,7 +86,11 @@ proc build_dwarf_for_contiguous_block { asm_file } {
DW_AT_call_file 1 data1
DW_AT_call_line $::foo_call_line data1
DW_AT_low_pc main_1 addr
- DW_AT_high_pc main_3 addr
+ if {$range_correct} {
+ DW_AT_high_pc main_4 addr
+ } else {
+ DW_AT_high_pc main_3 addr
+ }
}
}
}
@@ -120,10 +127,15 @@ proc build_dwarf_for_contiguous_block { asm_file } {
DW_LNS_copy
DW_LNE_set_address main_4
+ if {$variant == 1} {
+ DW_LNS_advance_line 1
+ }
DW_LNS_copy
DW_LNE_set_address main_5
- DW_LNS_advance_line 1
+ if {$variant == 0} {
+ DW_LNS_advance_line 1
+ }
DW_LNS_negate_stmt
DW_LNS_copy
@@ -146,6 +158,22 @@ proc build_dwarf_for_contiguous_block { asm_file } {
}
}
+# Like build_dwarf_for_contiguous_block, but use a slightly different line
+# info by setting variant == 1.
+# Use range_correct 1, so we're not testing the fix for PR33930.
+
+proc build_dwarf_for_contiguous_block_2 { asm_file } {
+ return [build_dwarf_for_contiguous_block $asm_file 1 1]
+}
+
+# Like build_dwarf_for_contiguous_block, but use a slightly different line
+# info by setting variant == 1.
+# Use range_correct 0, so we're testing the fix for PR33930.
+
+proc build_dwarf_for_contiguous_block_3 { asm_file } {
+ return [build_dwarf_for_contiguous_block $asm_file 0 1]
+}
+
# Assuming GDB is stopped at the entry $pc for 'foo', use 'maint info
# blocks' to check the block for 'foo' is correct. This function checks
# 'foo' created by 'build_dwarf_for_contiguous_block'.
@@ -555,6 +583,12 @@ set test_list \
[list "contiguous block" \
build_dwarf_for_contiguous_block \
check_contiguous_block] \
+ [list "contiguous block 2" \
+ build_dwarf_for_contiguous_block_2 \
+ check_contiguous_block] \
+ [list "contiguous block 3" \
+ build_dwarf_for_contiguous_block_3 \
+ check_contiguous_block] \
]
# Run all the tests.
@@ -566,6 +600,11 @@ foreach test_spec $test_list {
set build_dwarf_func [lindex $test_spec 1]
set check_block_func [lindex $test_spec 2]
+ if {$build_dwarf_func == "build_dwarf_for_contiguous_block_3"} {
+ # Work around PR33930.
+ continue
+ }
+
with_test_prefix $prefix {
set asm_file [standard_output_file ${testfile}-${suffix}.S]
$build_dwarf_func $asm_file
base-commit: c8b798145ff623a87ec4d39015cf832b0160899d
--
2.51.0
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH] [gdb] Fix missing print frame when stepping out of function
2026-03-12 12:02 [PATCH] [gdb] Fix missing print frame when stepping out of function Tom de Vries
@ 2026-03-18 14:29 ` Andrew Burgess
2026-03-19 18:03 ` Tom de Vries
2026-03-31 13:32 ` Tom de Vries
2026-04-03 15:17 ` Tom de Vries
1 sibling, 2 replies; 7+ messages in thread
From: Andrew Burgess @ 2026-03-18 14:29 UTC (permalink / raw)
To: Tom de Vries, gdb-patches
Tom de Vries <tdevries@suse.de> writes:
> Consider test-case gdb.dwarf2/dw2-extend-inline-block.exp, specifically the
> "contiguous block" prefix part, which can be stepped through like this:
> ...
> $ gdb -q outputs/gdb.dwarf2/dw2-extend-inline-block/dw2-extend-inline-block-5
> Reading symbols from dw2-extend-inline-block-5...
> (gdb) start
> ...
> Temporary breakpoint 1, main () at dw2-extend-inline-block.c:35
> 35 /* main:2 */
> (gdb) s
> 36 /* main:3 */ foo (); /* foo call line */
> (gdb)
> foo () at dw2-extend-inline-block.c:26
> 26 /* foo:1 */
> (gdb) s
> 27 /* foo:2 */
> (gdb) s
> 28 /* foo:3 */
> (gdb) s
> main () at dw2-extend-inline-block.c:37
> 37 /* main:4 */
> (gdb)
> ...
>
> If we slightly modify the line program:
> ...
> DW_LNE_set_address main_4
> + DW_LNS_advance_line 1
> DW_LNS_copy
>
> DW_LNE_set_address main_5
> - DW_LNS_advance_line 1
> DW_LNS_negate_stmt
> DW_LNS_copy
> ...
> we change the 36/0x401165 entry into a 37/0x401165 entry:
> ...
> File name Line number Starting address View Stmt
> dw2-extend-inline-block.c 34 0x401116 x
> dw2-extend-inline-block.c 35 0x401129 x
> dw2-extend-inline-block.c 26 0x401138 x
> dw2-extend-inline-block.c 27 0x401147 x
> dw2-extend-inline-block.c 28 0x401156 x
> dw2-extend-inline-block.c 36 0x401156
> -dw2-extend-inline-block.c 36 0x401165
> +dw2-extend-inline-block.c 37 0x401165
> dw2-extend-inline-block.c 37 0x401174 x
> dw2-extend-inline-block.c 38 0x401183 x
> dw2-extend-inline-block.c 39 0x401192 x
> dw2-extend-inline-block.c 40 0x4011a1 x
> dw2-extend-inline-block.c - 0x4011b7
> ...
>
> As it happens, the fix to extend truncated inlined function blocks doesn't
> work in this case. This is PR33930.
>
> We can work around this by making sure that the inlined function block isn't
> truncated in the first place:
> ...
> - DW_AT_high_pc main_3 addr
> + DW_AT_high_pc main_4 addr
> ...
>
> But then we still run into PR33981: the problem that gdb doesn't notify us
> when stepping out of main:
> ...
> (gdb) step^M
> 28 /* foo:3 */^M
> (gdb) step^M
> 37 /* main:4 */^M
> (gdb)
> ...
>
> What happens is that the slightly different line program triggers a different
> stepping path, which includes a case of "stepped to a different frame, but
> it's not the start of a statement", which refreshes the stepping info and
> consequently updates tp->control.step_frame_id to the frame id of main.
>
> So by the time we're stopped at line 37, and are trying to figure out what to
> print in print_stop_location, this condition evaluates to true:
> ...
> if (tp->control.stop_step
> && (tp->control.step_frame_id
> == get_frame_id (get_current_frame ()))
> && (tp->control.step_start_function
> == find_symbol_for_pc (tp->stop_pc ())))
> ...
> and we get:
> ...
> /* Finished step in same frame and same file, just print source
> line. */
> source_flag = SRC_LINE;
> ...
>
> It's good to realize here that because foo is inlined into main,
> tp->control.step_start_function is not foo but main, so consequently the
> step_start_function check (which checks if we are still in the same function)
> also passes, even though we actually stepped from foo into main.
>
> No longer refreshing the stepping info in the "stepped to a different frame,
> but it's not the start of a statement" case does fix the problem.
>
> But the refreshing is needed to be able to handle stepping out of say function
> f1 into function f2 and immediately stepping back into f1 again. If we don't
> refresh in between, it looks like we stayed in f1.
>
> Fix this by:
> - adding a new field thread_control_state::step_frame_id_changed,
> - updating the new field in set_step_info when refreshing, and
> - using the new field in print_stop_location.
>
> Tested on x86_64-linux.
>
> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33981
> ---
> gdb/gdbthread.h | 4 ++
> gdb/infrun.c | 10 ++++-
> gdb/infrun.h | 5 ++-
> .../gdb.dwarf2/dw2-extend-inline-block.exp | 45 +++++++++++++++++--
> 4 files changed, 57 insertions(+), 7 deletions(-)
>
> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
> index 68d7aebbbf7..02cf8f01fca 100644
> --- a/gdb/gdbthread.h
> +++ b/gdb/gdbthread.h
> @@ -146,6 +146,10 @@ struct thread_control_state
> any inlined frames). */
> struct frame_id step_stack_frame_id {};
>
> + /* True if step_frame_id was changed after the stepping command was
> + issued. */
> + bool step_frame_id_changed = false;
> +
> /* True if the thread is presently stepping over a breakpoint or
> a watchpoint, either with an inline step over or a displaced (out
> of line) step, and we're now expecting it to report a trap for
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index c05c2b0f42f..df55c9a3c66 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -3084,6 +3084,7 @@ clear_proceed_status_thread (struct thread_info *tp)
> tp->control.step_range_end = 0;
> tp->control.may_range_step = 0;
> tp->control.step_frame_id = null_frame_id;
> + tp->control.step_frame_id_changed = false;
> tp->control.step_stack_frame_id = null_frame_id;
> tp->control.step_over_calls = STEP_OVER_UNDEBUGGABLE;
> tp->control.step_start_function = nullptr;
> @@ -4818,12 +4819,16 @@ fetch_inferior_event ()
>
> void
> set_step_info (thread_info *tp, const frame_info_ptr &frame,
> - struct symtab_and_line sal)
> + struct symtab_and_line sal, bool refresh_p)
> {
> /* This can be removed once this function no longer implicitly relies on the
> inferior_ptid value. */
> gdb_assert (inferior_ptid == tp->ptid);
>
> + if (refresh_p
> + && tp->control.step_frame_id != null_frame_id
Unfortunately, this doesn't work. Comparing frame_ids ends up in
frame_id::operator== in frame.c, which always returns false if either
frame-id is null_frame_id. As a result, the != above always returns
true.
I think this is why you ended up having to add the refresh_p argument
maybe? As a *hack* to check this theory, I removed the check of
`refresh_p` here, and replaced the comparison to null_frame_id with:
memcmp (&tp->control.step_frame_id,
&null_frame_id,
sizeof (decltype (null_frame_id))) != 0
NOTE: This is NOT actual proposed code, just a hack to test this theory.
With these changes in place your modified dw2-extend-inline-block.exp
still passes.
But I want to go back to something you said earlier:
> It's good to realize here that because foo is inlined into main,
> tp->control.step_start_function is not foo but main, so consequently the
> step_start_function check (which checks if we are still in the same function)
> also passes, even though we actually stepped from foo into main.
I haven't looked into the history of this, but I wonder if there is a
good reason why step_start_function doesn't record the actual function?
Again, without looking, my guess would be that this is just the way it
has always been, so maybe the answer here is to have step_start_function
track the actual function symbol.
As a *hack* everywhere that step_start_function is set or referenced,
where we currently use `find_symbol_for_pc` I switched to use
`find_symbol_for_pc_sect_maybe_inline` passing nullptr for the section.
This would need dealing with properly for a real fix, but is good enough
for a quick test. With this change in place, and the GDB code parts of
your patch reverted, your modified test case still passes.
I think that it is worth at least investigating altering
step_start_function to track the actual function, including inline
functions, this seems like it would be a better fix. But if not then at
least we'll understand why it's not the right solution.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH] [gdb] Fix missing print frame when stepping out of function
2026-03-18 14:29 ` Andrew Burgess
@ 2026-03-19 18:03 ` Tom de Vries
2026-03-31 13:27 ` Tom de Vries
2026-03-31 13:32 ` Tom de Vries
1 sibling, 1 reply; 7+ messages in thread
From: Tom de Vries @ 2026-03-19 18:03 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
On 3/18/26 3:29 PM, Andrew Burgess wrote:
> Tom de Vries <tdevries@suse.de> writes:
>
>> Consider test-case gdb.dwarf2/dw2-extend-inline-block.exp, specifically the
>> "contiguous block" prefix part, which can be stepped through like this:
>> ...
>> $ gdb -q outputs/gdb.dwarf2/dw2-extend-inline-block/dw2-extend-inline-block-5
>> Reading symbols from dw2-extend-inline-block-5...
>> (gdb) start
>> ...
>> Temporary breakpoint 1, main () at dw2-extend-inline-block.c:35
>> 35 /* main:2 */
>> (gdb) s
>> 36 /* main:3 */ foo (); /* foo call line */
>> (gdb)
>> foo () at dw2-extend-inline-block.c:26
>> 26 /* foo:1 */
>> (gdb) s
>> 27 /* foo:2 */
>> (gdb) s
>> 28 /* foo:3 */
>> (gdb) s
>> main () at dw2-extend-inline-block.c:37
>> 37 /* main:4 */
>> (gdb)
>> ...
>>
>> If we slightly modify the line program:
>> ...
>> DW_LNE_set_address main_4
>> + DW_LNS_advance_line 1
>> DW_LNS_copy
>>
>> DW_LNE_set_address main_5
>> - DW_LNS_advance_line 1
>> DW_LNS_negate_stmt
>> DW_LNS_copy
>> ...
>> we change the 36/0x401165 entry into a 37/0x401165 entry:
>> ...
>> File name Line number Starting address View Stmt
>> dw2-extend-inline-block.c 34 0x401116 x
>> dw2-extend-inline-block.c 35 0x401129 x
>> dw2-extend-inline-block.c 26 0x401138 x
>> dw2-extend-inline-block.c 27 0x401147 x
>> dw2-extend-inline-block.c 28 0x401156 x
>> dw2-extend-inline-block.c 36 0x401156
>> -dw2-extend-inline-block.c 36 0x401165
>> +dw2-extend-inline-block.c 37 0x401165
>> dw2-extend-inline-block.c 37 0x401174 x
>> dw2-extend-inline-block.c 38 0x401183 x
>> dw2-extend-inline-block.c 39 0x401192 x
>> dw2-extend-inline-block.c 40 0x4011a1 x
>> dw2-extend-inline-block.c - 0x4011b7
>> ...
>>
>> As it happens, the fix to extend truncated inlined function blocks doesn't
>> work in this case. This is PR33930.
>>
>> We can work around this by making sure that the inlined function block isn't
>> truncated in the first place:
>> ...
>> - DW_AT_high_pc main_3 addr
>> + DW_AT_high_pc main_4 addr
>> ...
>>
>> But then we still run into PR33981: the problem that gdb doesn't notify us
>> when stepping out of main:
>> ...
>> (gdb) step^M
>> 28 /* foo:3 */^M
>> (gdb) step^M
>> 37 /* main:4 */^M
>> (gdb)
>> ...
>>
>> What happens is that the slightly different line program triggers a different
>> stepping path, which includes a case of "stepped to a different frame, but
>> it's not the start of a statement", which refreshes the stepping info and
>> consequently updates tp->control.step_frame_id to the frame id of main.
>>
>> So by the time we're stopped at line 37, and are trying to figure out what to
>> print in print_stop_location, this condition evaluates to true:
>> ...
>> if (tp->control.stop_step
>> && (tp->control.step_frame_id
>> == get_frame_id (get_current_frame ()))
>> && (tp->control.step_start_function
>> == find_symbol_for_pc (tp->stop_pc ())))
>> ...
>> and we get:
>> ...
>> /* Finished step in same frame and same file, just print source
>> line. */
>> source_flag = SRC_LINE;
>> ...
>>
>> It's good to realize here that because foo is inlined into main,
>> tp->control.step_start_function is not foo but main, so consequently the
>> step_start_function check (which checks if we are still in the same function)
>> also passes, even though we actually stepped from foo into main.
>>
>> No longer refreshing the stepping info in the "stepped to a different frame,
>> but it's not the start of a statement" case does fix the problem.
>>
>> But the refreshing is needed to be able to handle stepping out of say function
>> f1 into function f2 and immediately stepping back into f1 again. If we don't
>> refresh in between, it looks like we stayed in f1.
>>
>> Fix this by:
>> - adding a new field thread_control_state::step_frame_id_changed,
>> - updating the new field in set_step_info when refreshing, and
>> - using the new field in print_stop_location.
>>
>> Tested on x86_64-linux.
>>
>> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=33981
>> ---
>> gdb/gdbthread.h | 4 ++
>> gdb/infrun.c | 10 ++++-
>> gdb/infrun.h | 5 ++-
>> .../gdb.dwarf2/dw2-extend-inline-block.exp | 45 +++++++++++++++++--
>> 4 files changed, 57 insertions(+), 7 deletions(-)
>>
>> diff --git a/gdb/gdbthread.h b/gdb/gdbthread.h
>> index 68d7aebbbf7..02cf8f01fca 100644
>> --- a/gdb/gdbthread.h
>> +++ b/gdb/gdbthread.h
>> @@ -146,6 +146,10 @@ struct thread_control_state
>> any inlined frames). */
>> struct frame_id step_stack_frame_id {};
>>
>> + /* True if step_frame_id was changed after the stepping command was
>> + issued. */
>> + bool step_frame_id_changed = false;
>> +
>> /* True if the thread is presently stepping over a breakpoint or
>> a watchpoint, either with an inline step over or a displaced (out
>> of line) step, and we're now expecting it to report a trap for
>> diff --git a/gdb/infrun.c b/gdb/infrun.c
>> index c05c2b0f42f..df55c9a3c66 100644
>> --- a/gdb/infrun.c
>> +++ b/gdb/infrun.c
>> @@ -3084,6 +3084,7 @@ clear_proceed_status_thread (struct thread_info *tp)
>> tp->control.step_range_end = 0;
>> tp->control.may_range_step = 0;
>> tp->control.step_frame_id = null_frame_id;
>> + tp->control.step_frame_id_changed = false;
>> tp->control.step_stack_frame_id = null_frame_id;
>> tp->control.step_over_calls = STEP_OVER_UNDEBUGGABLE;
>> tp->control.step_start_function = nullptr;
>> @@ -4818,12 +4819,16 @@ fetch_inferior_event ()
>>
>> void
>> set_step_info (thread_info *tp, const frame_info_ptr &frame,
>> - struct symtab_and_line sal)
>> + struct symtab_and_line sal, bool refresh_p)
>> {
>> /* This can be removed once this function no longer implicitly relies on the
>> inferior_ptid value. */
>> gdb_assert (inferior_ptid == tp->ptid);
>>
>> + if (refresh_p
>> + && tp->control.step_frame_id != null_frame_id
>
> Unfortunately, this doesn't work. Comparing frame_ids ends up in
> frame_id::operator== in frame.c, which always returns false if either
> frame-id is null_frame_id. As a result, the != above always returns
> true.
>
> I think this is why you ended up having to add the refresh_p argument
> maybe? As a *hack* to check this theory, I removed the check of
> `refresh_p` here, and replaced the comparison to null_frame_id with:
>
> memcmp (&tp->control.step_frame_id,
> &null_frame_id,
> sizeof (decltype (null_frame_id))) != 0
>
> NOTE: This is NOT actual proposed code, just a hack to test this theory.
>
> With these changes in place your modified dw2-extend-inline-block.exp
> still passes.
>
> But I want to go back to something you said earlier:
>
>> It's good to realize here that because foo is inlined into main,
>> tp->control.step_start_function is not foo but main, so consequently the
>> step_start_function check (which checks if we are still in the same function)
>> also passes, even though we actually stepped from foo into main.
>
> I haven't looked into the history of this, but I wonder if there is a
> good reason why step_start_function doesn't record the actual function?
> Again, without looking, my guess would be that this is just the way it
> has always been, so maybe the answer here is to have step_start_function
> track the actual function symbol.
>
> As a *hack* everywhere that step_start_function is set or referenced,
> where we currently use `find_symbol_for_pc` I switched to use
> `find_symbol_for_pc_sect_maybe_inline` passing nullptr for the section.
> This would need dealing with properly for a real fix, but is good enough
> for a quick test. With this change in place, and the GDB code parts of
> your patch reverted, your modified test case still passes.
>
> I think that it is worth at least investigating altering
> step_start_function to track the actual function, including inline
> functions, this seems like it would be a better fix. But if not then at
> least we'll understand why it's not the right solution.
Hi,
just a small update on this.
I've given this a try (
https://github.com/vries/gdb/tree/gdb/fix-missing-print-frame-when-stepping-out-of-function-2
) and ran into trouble with just one test-case, gdb.opt/inline-cmds.exp ).
Basically, with current gdb we have:
...
63 x = 7;
(gdb) s
64 y = 8; /* set mi break here */
(gdb) s
66 result = func1 ();
(gdb)
...
and with the patch series posted above:
...
63 x = 7;
(gdb) s
64 y = 8; /* set mi break here */
(gdb) s
main () at
/data/vries/gdb/leap-16-0/build/../../src/gdb/testsuite/gdb.opt/inline-cmds.c:66
66 result = func1 ();
(gdb)
...
There is some code somewhere that allows entry into an inline function
to happen in two steps, even if the pc stays the same inbetween, and
that code seems to interact with the solution in the patch series.
I'll take a further look tomorrow.
Thanks,
- Tom
^ permalink raw reply [flat|nested] 7+ messages in thread* Re: [PATCH] [gdb] Fix missing print frame when stepping out of function
2026-03-19 18:03 ` Tom de Vries
@ 2026-03-31 13:27 ` Tom de Vries
2026-03-31 15:44 ` Andrew Burgess
0 siblings, 1 reply; 7+ messages in thread
From: Tom de Vries @ 2026-03-31 13:27 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
On 3/19/26 7:03 PM, Tom de Vries wrote:
>> I think that it is worth at least investigating altering
>> step_start_function to track the actual function, including inline
>> functions, this seems like it would be a better fix. But if not then at
>> least we'll understand why it's not the right solution.
>
> Hi,
>
> just a small update on this.
>
> I've given this a try ( https://github.com/vries/gdb/tree/gdb/fix-
> missing-print-frame-when-stepping-out-of-function-2 ) and ran into
> trouble with just one test-case, gdb.opt/inline-cmds.exp ).
>
> Basically, with current gdb we have:
> ...
> 63 x = 7;
> (gdb) s
> 64 y = 8; /* set mi break here */
> (gdb) s
> 66 result = func1 ();
> (gdb)
> ...
> and with the patch series posted above:
> ...
> 63 x = 7;
> (gdb) s
> 64 y = 8; /* set mi break here */
> (gdb) s
> main () at /data/vries/gdb/leap-16-0/build/../../src/gdb/testsuite/
> gdb.opt/inline-cmds.c:66
> 66 result = func1 ();
> (gdb)
> ...
>
> There is some code somewhere that allows entry into an inline function
> to happen in two steps, even if the pc stays the same inbetween, and
> that code seems to interact with the solution in the patch series.
>
> I'll take a further look tomorrow.
Hi Andrew,
this estimate turned out to be somewhat optimistic, but I've taken a
look today, and submitted a v2 with the RFC tag (
https://sourceware.org/pipermail/gdb-patches/2026-March/226269.html ),
containing the analysis of the regression mentioned above.
Thanks,
- Tom
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] [gdb] Fix missing print frame when stepping out of function
2026-03-31 13:27 ` Tom de Vries
@ 2026-03-31 15:44 ` Andrew Burgess
0 siblings, 0 replies; 7+ messages in thread
From: Andrew Burgess @ 2026-03-31 15:44 UTC (permalink / raw)
To: Tom de Vries, gdb-patches
Tom de Vries <tdevries@suse.de> writes:
> On 3/19/26 7:03 PM, Tom de Vries wrote:
>>> I think that it is worth at least investigating altering
>>> step_start_function to track the actual function, including inline
>>> functions, this seems like it would be a better fix. But if not then at
>>> least we'll understand why it's not the right solution.
>>
>> Hi,
>>
>> just a small update on this.
>>
>> I've given this a try ( https://github.com/vries/gdb/tree/gdb/fix-
>> missing-print-frame-when-stepping-out-of-function-2 ) and ran into
>> trouble with just one test-case, gdb.opt/inline-cmds.exp ).
>>
>> Basically, with current gdb we have:
>> ...
>> 63 x = 7;
>> (gdb) s
>> 64 y = 8; /* set mi break here */
>> (gdb) s
>> 66 result = func1 ();
>> (gdb)
>> ...
>> and with the patch series posted above:
>> ...
>> 63 x = 7;
>> (gdb) s
>> 64 y = 8; /* set mi break here */
>> (gdb) s
>> main () at /data/vries/gdb/leap-16-0/build/../../src/gdb/testsuite/
>> gdb.opt/inline-cmds.c:66
>> 66 result = func1 ();
>> (gdb)
>> ...
>>
>> There is some code somewhere that allows entry into an inline function
>> to happen in two steps, even if the pc stays the same inbetween, and
>> that code seems to interact with the solution in the patch series.
>>
>> I'll take a further look tomorrow.
>
> Hi Andrew,
>
> this estimate turned out to be somewhat optimistic, but I've taken a
> look today, and submitted a v2 with the RFC tag (
> https://sourceware.org/pipermail/gdb-patches/2026-March/226269.html ),
> containing the analysis of the regression mentioned above.
I'll take a look.
Thanks,
Andrew
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] [gdb] Fix missing print frame when stepping out of function
2026-03-18 14:29 ` Andrew Burgess
2026-03-19 18:03 ` Tom de Vries
@ 2026-03-31 13:32 ` Tom de Vries
1 sibling, 0 replies; 7+ messages in thread
From: Tom de Vries @ 2026-03-31 13:32 UTC (permalink / raw)
To: Andrew Burgess, gdb-patches
On 3/18/26 3:29 PM, Andrew Burgess wrote:
>> void
>> set_step_info (thread_info *tp, const frame_info_ptr &frame,
>> - struct symtab_and_line sal)
>> + struct symtab_and_line sal, bool refresh_p)
>> {
>> /* This can be removed once this function no longer implicitly relies on the
>> inferior_ptid value. */
>> gdb_assert (inferior_ptid == tp->ptid);
>>
>> + if (refresh_p
>> + && tp->control.step_frame_id != null_frame_id
> Unfortunately, this doesn't work. Comparing frame_ids ends up in
> frame_id::operator== in frame.c, which always returns false if either
> frame-id is null_frame_id. As a result, the != above always returns
> true.
>
I see, thanks for pointing that out.
> I think this is why you ended up having to add the refresh_p argument
> maybe?
To follow up on this (even though we're exploring a different direction
atm), AFAIR I didn't need either of the two things you mention here
(frame comparison and refresh_p) to make the patch work, but I added
them with the intention of making the change more robust.
Thanks,
- Tom
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] [gdb] Fix missing print frame when stepping out of function
2026-03-12 12:02 [PATCH] [gdb] Fix missing print frame when stepping out of function Tom de Vries
2026-03-18 14:29 ` Andrew Burgess
@ 2026-04-03 15:17 ` Tom de Vries
1 sibling, 0 replies; 7+ messages in thread
From: Tom de Vries @ 2026-04-03 15:17 UTC (permalink / raw)
To: gdb-patches
On 3/12/26 1:02 PM, Tom de Vries wrote:
> No longer refreshing the stepping info in the "stepped to a different frame,
> but it's not the start of a statement" case does fix the problem.
>
> But the refreshing is needed to be able to handle stepping out of say function
> f1 into function f2 and immediately stepping back into f1 again. If we don't
> refresh in between, it looks like we stayed in f1.
FWIW, in order to test this theory, I did:
...
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 2e53d2a6c30..ec2697dd437 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -8303,6 +8303,7 @@ process_event_stop_test (struct
execution_control_state *ecs)
stop_pc_sal.line = 0;
infrun_debug_printf ("stepped to a different frame, but "
"it's not the start of a statement");
+ refresh_step_info = false;
}
}
...
and ran into a failure here:
...
(gdb) PASS: gdb.dwarf2/dw2-step-out-of-function-no-stmt.exp: continue to
breakpoint: bar
step^M
0x0000000000401116 in foo ()^M
(gdb) FAIL: gdb.dwarf2/dw2-step-out-of-function-no-stmt.exp: step
...
I haven't looked into exactly why this is failing, but the apparently
the refresh_step_info does have a function here.
Thanks,
- Tom
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-04-03 15:18 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-12 12:02 [PATCH] [gdb] Fix missing print frame when stepping out of function Tom de Vries
2026-03-18 14:29 ` Andrew Burgess
2026-03-19 18:03 ` Tom de Vries
2026-03-31 13:27 ` Tom de Vries
2026-03-31 15:44 ` Andrew Burgess
2026-03-31 13:32 ` Tom de Vries
2026-04-03 15:17 ` Tom de Vries
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox