diff options
author | Andrew Burgess <aburgess@redhat.com> | 2025-01-15 15:09:30 +0000 |
---|---|---|
committer | Andrew Burgess <aburgess@redhat.com> | 2025-02-10 10:06:06 +0000 |
commit | f0848033b8da6be492f02f3c3880b4f26737e0f9 (patch) | |
tree | 9d90f67172d7093ed0941c82842699a1783d21b3 | |
parent | 6488f1e65adf232d1cfdded7f5fb1451f27eee78 (diff) | |
download | binutils-f0848033b8da6be492f02f3c3880b4f26737e0f9.zip binutils-f0848033b8da6be492f02f3c3880b4f26737e0f9.tar.gz binutils-f0848033b8da6be492f02f3c3880b4f26737e0f9.tar.bz2 |
gdb: fix selecting tail-call frames by name
I noticed that attempting to select a tail-call frame using 'frame
function NAME' wouldn't work:
(gdb) bt
#0 func_that_never_returns () at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/frame-selection.c:49
#1 0x0000000000401183 in func_that_tail_calls () at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/frame-selection.c:59
#2 0x00000000004011a5 in main () at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/frame-selection.c:70
(gdb) frame function func_that_tail_calls
No frame for function "func_that_tail_calls".
(gdb) up
#1 0x0000000000401183 in func_that_tail_calls () at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/frame-selection.c:59
59 func_that_never_returns ();
(gdb) disassemble
Dump of assembler code for function func_that_tail_calls:
0x000000000040117a <+0>: push %rbp
0x000000000040117b <+1>: mov %rsp,%rbp
0x000000000040117e <+4>: call 0x40116c <func_that_never_returns>
End of assembler dump.
(gdb)
The problem is that the 'function' mechanism uses get_frame_pc() and
then compares the address returned with the bounds of the function
we're looking for.
So in this case, the bounds of func_that_tail_calls are 0x40117a to
0x401183, with 0x401183 being the first address _after_ the function.
However, because func_that_tail_calls ends in a tail call, then the
get_frame_pc() is 0x401183, the first address after the function. As
a result, GDB fails to realise that frame #1 is inside the function
we're looking for, and the lookup fails.
The fix is to use get_frame_address_in_block, which will return an
adjusted address, in this case, 0x401182, which is within the function
bounds. Now the lookup works:
(gdb) frame function func_that_tail_calls
#1 0x0000000000401183 in func_that_tail_calls () at /tmp/build/gdb/testsuite/../../../src/gdb/testsuite/gdb.base/frame-selection.c:59
59 func_that_never_returns ();
(gdb)
I've extended the gdb.base/frame-selection.exp test to cover this
case.
-rw-r--r-- | gdb/stack.c | 6 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/frame-selection.c | 21 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/frame-selection.exp | 37 |
3 files changed, 62 insertions, 2 deletions
diff --git a/gdb/stack.c b/gdb/stack.c index 4a92449..6f986aa 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -2863,9 +2863,11 @@ find_frame_for_function (const char *function_name) do { + CORE_ADDR frame_pc = get_frame_address_in_block (frame); + for (size_t i = 0; (i < sals.size () && !found); i++) - found = (get_frame_pc (frame) >= func_bounds[i].low - && get_frame_pc (frame) < func_bounds[i].high); + found = (frame_pc >= func_bounds[i].low + && frame_pc < func_bounds[i].high); if (!found) { level = 1; diff --git a/gdb/testsuite/gdb.base/frame-selection.c b/gdb/testsuite/gdb.base/frame-selection.c index 237e155..18a58e4 100644 --- a/gdb/testsuite/gdb.base/frame-selection.c +++ b/gdb/testsuite/gdb.base/frame-selection.c @@ -15,6 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <stdlib.h> + int frame_2 (void) { @@ -40,6 +42,23 @@ recursive (int arg) return v; } +/* A function that never returns. */ +void __attribute__((noreturn)) +func_that_never_returns (void) +{ + exit (0); +} + +/* A function that tail calls. Calling a 'noreturn' function isn't + required for a tail call, but at low optimisation levels, gcc will apply + the tail call optimisation only for 'noreturn' calls. */ + +void +func_that_tail_calls (void) +{ + func_that_never_returns (); +} + int main (void) { @@ -48,5 +67,7 @@ main (void) i = frame_1 (); j = recursive (0); + func_that_tail_calls (); + return i + j; } diff --git a/gdb/testsuite/gdb.base/frame-selection.exp b/gdb/testsuite/gdb.base/frame-selection.exp index e8d9c87..32ed92d 100644 --- a/gdb/testsuite/gdb.base/frame-selection.exp +++ b/gdb/testsuite/gdb.base/frame-selection.exp @@ -186,3 +186,40 @@ with_test_prefix "second frame_2 breakpoint" { gdb_test "frame function recursive" "#1 $hex in recursive.*" \ "select frame for function recursive, third attempt" } + +# At one point using the 'function' sub-command (e.g. 'frame function +# ...') would fail to select a frame if the frame ended with a +# tail-call, and the address within the frame was outside the bounds +# of the function. +with_test_prefix "stack with tail call" { + gdb_breakpoint func_that_never_returns + gdb_continue_to_breakpoint func_that_never_returns + + gdb_test "bt" \ + [multi_line \ + "#0 func_that_never_returns \\(\\) at \[^\r\n\]+" \ + "#1 $hex in func_that_tail_calls \\(\\) at \[^\r\n\]+" \ + "#2 $hex in main \\(\\) at \[^\r\n\]+"] \ + "bt from func_that_never_returns" + + # Reset the frame addresses based on the new stack. + gdb_test "frame 0" "#0 func_that_never_returns.*" + set frame_0_address [ get_frame_address "frame 0" ] + gdb_test "frame 1" "#1 $hex in func_that_tail_calls.*" + set frame_1_address [ get_frame_address "frame 1" ] + gdb_test "frame 2" "#2 $hex in main.*" + set frame_2_address [ get_frame_address "frame 2" ] + + # Test 'select-frame function ...' command. + gdb_test_no_output "select-frame function func_that_never_returns" + check_frame "0" "${frame_0_address}" "func_that_never_returns" + gdb_test_no_output "select-frame function func_that_tail_calls" + check_frame "1" "${frame_1_address}" "func_that_tail_calls" + gdb_test_no_output "select-frame function main" + check_frame "2" "${frame_2_address}" "main" + + # Test 'frame function ...' command. + gdb_test "frame function func_that_never_returns" "#0 func_that_never_returns.*" + gdb_test "frame function func_that_tail_calls" "#1 $hex in func_that_tail_calls.*" + gdb_test "frame function main" "#2 $hex in main.*" +} |