I noticed that the function name displayed in the TUI status bar was
wrong for tail call frames. I spotted this after commit:
commit
f163103c364f72be00f7580ac7500e10c18230a3
Date: Mon Jan 12 10:44:35 2026 +0000
gdb: fix 'info frame' for tail calls with no debug information
And playing with the test binary from that commit in the TUI.
Unlike the problem fixed by this earlier commit, the TUI status bar
function name is wrong for tail call functions both with and without
DWARF.
The problem is in the function tui_get_function_from_frame (in
tui/tui-status.c), which the TUI uses to get the function name that
will be displayed. This function calls print_address_symbolic passing
in the result of a call to get_frame_pc to get the actual function
name.
The problem is that for a tail calls, get_frame_pc will return a pc
outside the current function. Fix this by switching to use the
function get_frame_address_in_block instead.
I wanted the tests for this fix to live in gdb.tui/ as the code being
fixed is very TUI specific. As a result this does mean that the
tailcall-msym.exp test is copied from gdb.base/ (see the earlier
commit), but I don't see this as a huge problem.
There's an additional test that creates a tail call function using the
DWARF assembler, this tests the with DWARF case.
One maybe surprising change in this commit is the one to lib/gdb.exp.
This was needed because of the use of 'runto callee' in the new
tests. As we're running in TUI mode, styling will be enabled in the
output. Now most TUI tests compile "normal" .c files into an
executable with file and line debug information. As a result, when we
hit a breakpoint we see GDB output like:
Breakpoint 1, function () at file.c:25
The pattern that matches this within 'proc runto' (lib/gdb.exp)
uses '.*' to match both the function name and the filename. This
means that the styling escape sequences are also matched.
However, when building the tests added in this commit, which lack file
and line debug information, we get a breakpoint hit line like this:
Breakpoint 1, 0x000000000040110a in callee ()
The pattern in 'proc runto' that matches this uses '[0-9xa-f]*' to
match the hex address string. As a consequence, if the address is
styled, then this pattern no longer matches.
To work around this I've added an optional pattern which should match
any ansi styling escape sequence before and after the address. This
should not cause any problems for unstyled testing, but now allows
this pattern to match if styling is enabled.
Approved-By: Tom Tromey <tom@tromey.com>
--- /dev/null
+/* Copyright 2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+volatile int global_var = 0;
+
+void
+callee (void)
+{
+ asm ("callee_label: .globl callee_label");
+ ++global_var;
+}
+
+/* When we generate the DWARF with the DWARF assembler, we split caller in
+ half. Everything up to the 'callee ();' call is left as caller, and
+ everything after that becomes dummy_func. */
+
+void
+caller (void)
+{
+ asm ("caller_label: .globl caller_label");
+ callee ();
+ ++global_var;
+ ++global_var;
+}
+
+int
+main (void)
+{
+ asm ("main_label: .globl main_label");
+ caller ();
+ return 0;
+}
--- /dev/null
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This test has a similar objective as gdb.tui/tailcall-msym.exp;
+# check that the TUI status bar will display the correct function name
+# when dealing with a tailcall function.
+#
+# Where gdb.tui/tailcall-msym.exp creates a tailcall with no debug
+# information, this test uses the DWARF assembler to create a tailcall
+# function with DWARF information.
+
+load_lib dwarf.exp
+tuiterm_env
+
+# This test requires the DWARF assembler.
+require dwarf2_support
+
+standard_testfile .c .S
+
+get_func_info caller
+
+# Compile with debug info first so that GDB can find the exact
+# instruction offsets we need.
+if { [prepare_for_testing "failed to prepare" ${testfile}-dbg ${srcfile}] } {
+ return
+}
+
+if {![runto callee]} {
+ return
+}
+
+# Move up to the caller frame to see the call site.
+gdb_test "up" ".*caller.*" "go up to caller"
+
+# Find the return address within 'caller'. This is just the $pc
+# value.
+set return_addr [get_hexadecimal_valueof "\$pc" "*UNKNOWN*" \
+ "get \$pc within caller"]
+
+# Define the start and end of DUMMY_FUNC, which sits over the second
+# half of CALLER.
+set dummy_func_start $return_addr
+set dummy_func_end $caller_end
+
+# Update the end of CALLER so it finishes with the call instruction.
+set caller_end $return_addr
+
+# Now generate a DWARF file. Both MAIN and CALLEE here have their
+# actual address ranges. CALLER is truncated to end at the call
+# (making it look like a tail call function), and DUMMY_FUNC sits
+# immediately after CALLER.
+set asm_file [standard_output_file ${srcfile2}]
+Dwarf::assemble $asm_file {
+ get_func_info main
+ get_func_info callee
+
+ cu {} {
+ DW_TAG_compile_unit {
+ DW_AT_language @DW_LANG_C99
+ DW_AT_name "tailcall.c"
+ DW_AT_producer "GDB DWARF Assembler"
+ } {
+ DW_TAG_subprogram {
+ DW_AT_name callee
+ DW_AT_low_pc $callee_start DW_FORM_addr
+ DW_AT_high_pc $callee_end DW_FORM_addr
+ DW_AT_external 1 DW_FORM_flag
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_name caller
+ DW_AT_low_pc $::caller_start DW_FORM_addr
+ DW_AT_high_pc $::caller_end DW_FORM_addr
+ DW_AT_external 1 DW_FORM_flag
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_name dummy_func
+ DW_AT_low_pc $::dummy_func_start DW_FORM_addr
+ DW_AT_high_pc $::dummy_func_end DW_FORM_addr
+ }
+
+ DW_TAG_subprogram {
+ DW_AT_name main
+ DW_AT_low_pc $main_start DW_FORM_addr
+ DW_AT_high_pc $main_end DW_FORM_addr
+ DW_AT_external 1 DW_FORM_flag
+ }
+ }
+ }
+}
+
+# Build the actual test executable.
+if { [build_executable ${testfile}.exp ${testfile} \
+ [list ${srcfile} ${asm_file}] {nodebug}] } {
+ return
+}
+
+Term::clean_restart 24 80 $testfile
+
+if {![runto callee]} {
+ return
+}
+
+if {![Term::enter_tui]} {
+ unsupported "TUI not supported"
+ return
+}
+
+# Check the function name on display in the status bar. The interesting
+# case here is 'caller', which is a tailcall function in an objfile with
+# no debug information.
+foreach func { callee caller main } {
+ Term::check_region_contents "status bar says $func" \
+ 0 15 80 1 "In: $func"
+
+ if { $func ne "main" } {
+ Term::command "up"
+ }
+}
--- /dev/null
+/* This testcase is part of GDB, the GNU debugger.
+
+ Copyright 2026 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+volatile int global_var = 0;
+
+void
+callee (void)
+{
+ /* Nothing. */
+}
+
+void
+caller (void)
+{
+ callee ();
+
+ /* Filler so that there is some code after the call. We manually add
+ symbols to this executable, and create a fake 'dummy_func' that
+ overlaps this code. */
+ ++global_var;
+ ++global_var;
+}
+
+int
+main (void)
+{
+ caller ();
+ return 0;
+}
--- /dev/null
+# Copyright 2026 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This test checks GDB's ability to display the correct function name
+# in the TUI status bar when we have a tailcall function in a file
+# with no debug information.
+#
+# The setup of this test is a direct copy of gdb.base/tailcall-msym.exp,
+# only the last part where we actually activate the TUI is different.
+# Still, I wanted the test to live in gdb.tui/ as this really is testing
+# TUI specific code.
+
+tuiterm_env
+
+standard_testfile
+
+# Compile with debug info first so we can find the instruction boundaries.
+if { [prepare_for_testing "prepare with debug" ${testfile} ${srcfile}] } {
+ return
+}
+
+if {![runto callee]} {
+ return
+}
+
+# At this point the stack is main->caller->callee, and the inferior is
+# stopped in 'callee'. Move up to the 'caller' function so we can
+# find all the addresses we need.
+gdb_test "up" ".*caller.*" "go up to caller"
+
+set func_start -1
+set func_end -1
+set return_addr -1
+
+# Get the start address of 'caller'.
+gdb_test_multiple "info address caller" "get caller start address" {
+ -re -wrap "Symbol \"caller\" is a function at address ($hex)\." {
+ set func_start $expect_out(1,string)
+ pass $gdb_test_name
+ }
+}
+
+# Get the return address within 'caller' (current PC in this frame).
+gdb_test_multiple "print/x \$pc" "get return address" {
+ -re -wrap " = ($hex)" {
+ set return_addr $expect_out(1,string)
+ pass $gdb_test_name
+ }
+}
+
+gdb_test_multiple "disassemble caller" "get caller end address" {
+ -re "^\\s+($hex) \[^\r\n\]+\r\n" {
+ set func_end $expect_out(1,string)
+ exp_continue
+ }
+ -re "^$gdb_prompt $" {
+ gdb_assert { $func_end != -1 } $gdb_test_name
+ }
+ -re "^\[^\r\n\]*\r\n" {
+ exp_continue
+ }
+}
+
+# Check both addresses were found.
+if { $func_start == -1 || $func_end == -1 || $return_addr == -1} {
+ fail "could not determine addresses"
+ return
+}
+
+# Calculate the reduced length we want the 'caller' function to be.
+# This will stop the function immediately after the call instruction,
+# as if the function was a tailcall.
+set fake_len [expr {$return_addr - $func_start}]
+
+# Calculate the length of the dummy function. This is almost the
+# remainder of the original 'caller' function. The FUNC_END is
+# actually the address of the last instruction in 'caller', so this
+# new length will be one instruction short, but finding the actual end
+# is more complex, and really isn't necessary. This is good enough.
+set dummy_len [expr {$func_end - $return_addr}]
+
+# Recompile the source file into an assembly file. We're going to
+# modify this assembly file later.
+set asm_file [standard_output_file ${testfile}.s]
+if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "$asm_file" assembly {}] != "" } {
+ untested "failed to compile to assembly"
+ return
+}
+
+# Create and modify some symbols within the assembler file. There are
+# two things we want to do. First, reduce the length of the function
+# 'caller' such that the call instruction (the one going to 'callee')
+# is the last instruction in the function, this appears to make
+# 'caller' a tail call function. Second, create a new function called
+# 'dummy_func' immediately after 'caller', this means that if GDB gets
+# things wrong it will report 'dummy_func' in the backtrace rather
+# than 'caller'.
+set fd [open $asm_file a]
+puts $fd ""
+puts $fd "/* Artificial symbols added by testsuite. */"
+
+# Create 'dummy_func'. The length here is short to avoid overlapping
+# other functions.
+puts $fd ".global dummy_func"
+puts $fd ".type dummy_func, %function"
+puts $fd "dummy_func = caller + $fake_len"
+puts $fd ".size dummy_func, $dummy_len"
+
+# Emit a new size for function 'caller', the assembler seems happy
+# enough to just use this new length instead of the original length
+# the compiler emitted.
+#
+# If this is ever a problem then we'll need to parse through the
+# assembler file and remove the original .size directive.
+puts $fd ".size caller, $fake_len"
+close $fd
+
+# Rebuild the test executable from the modified assembler file. Don't
+# include debug as we want GDB to use the msymbols.
+set real_testfile ${testfile}-updated
+if { [build_executable "build" $real_testfile $asm_file {nodebug}] } {
+ return
+}
+
+Term::clean_restart 24 80 $real_testfile
+
+if {![runto callee]} {
+ return
+}
+
+if {![Term::enter_tui]} {
+ unsupported "TUI not supported"
+ return
+}
+
+# Check the function name on display in the status bar. The interesting
+# case here is 'caller', which is a tailcall function in an objfile with
+# no debug information.
+foreach func { callee caller main } {
+ Term::check_region_contents "status bar says $func" \
+ 0 15 80 1 "In: $func"
+
+ if { $func ne "main" } {
+ Term::command "up"
+ }
+}
# the "at foo.c:36" output we get with -g.
# the "in func" output we get without -g.
+ set opt_ansi_re "(:?\033\\\[.*m)?"
gdb_expect {
-re "(?:Break|Temporary break).* at .*:.*$decimal.*$gdb_prompt $" {
if { $print_pass } {
}
return 1
}
- -re "(?:Breakpoint|Temporary breakpoint) $bkptno_numopt_re, \[0-9xa-f\]* in .*$gdb_prompt $" {
+ -re "(?:Breakpoint|Temporary breakpoint) $bkptno_numopt_re, ${opt_ansi_re}\[0-9xa-f\]*${opt_ansi_re} in .*$gdb_prompt $" {
if { $print_pass } {
pass $test_name
}
{
string_file stream;
- print_address_symbolic (get_frame_arch (fi), get_frame_pc (fi),
+ print_address_symbolic (get_frame_arch (fi),
+ get_frame_address_in_block (fi),
&stream, demangle, "");
/* Use simple heuristics to isolate the function name. The symbol