mirror of
https://github.com/bminor/binutils-gdb.git
synced 2025-11-16 12:34:43 +00:00
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.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.*"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user