binutils-gdb/gdb/testsuite/gdb.dwarf2/dw2-inline-many-frames.exp

380 lines
12 KiB
Plaintext
Raw Normal View History

gdb: Better frame tracking for inline frames This commit improves GDB's handling of inline functions when there are more than one inline function in a stack, so for example if we have a stack like: main -> aaa -> bbb -> ccc -> ddd And aaa, bbb, and ccc are all inline within main GDB should (when given sufficient debug information) be able to step from main through aaa, bbb, and ccc. Unfortunately, this currently doesn't work, here's an example session: (gdb) start Temporary breakpoint 1 at 0x4003b0: file test.c, line 38. Starting program: /project/gdb/tests/inline/test Temporary breakpoint 1, main () at test.c:38 38 global_var = 0; (gdb) step 39 return aaa () + 1; (gdb) step aaa () at test.c:39 39 return aaa () + 1; (gdb) step bbb () at test.c:39 39 return aaa () + 1; (gdb) step ccc () at test.c:39 39 return aaa () + 1; (gdb) step ddd () at test.c:32 32 return global_var; (gdb) bt #0 ddd () at test.c:32 #1 0x00000000004003c1 in ccc () at test.c:39 #2 bbb () at test.c:26 #3 aaa () at test.c:14 #4 main () at test.c:39 Notice that once we get to line 39 in main, GDB keeps reporting line 39 in main as the location despite understanding that the inferior is stepping through the nested inline functions with each use of step. The problem is that as soon as the inferior stops we call skip_inline_frames (from inline-frame.c) which calculates the inferiors current state in relation to inline functions - it figures out if we're in an inline function, and if we are counts how many inline frames there are at the current location. So, in our example above, when we step from line 38 in main to line 39 we stop at a location that is simultaneously in all of main, aaa, bbb, and ccc. The block structure reflects the order in which the functions would be called, with ccc being the most inner block and main being the most outer block. When we stop GDB naturally finds the block for ccc, however within skip_inline_frames we spot that bbb, aaa, and main are super-blocks of the current location and that each layer represents an inline function. The skip_inline_frames then records the depth of inline functions (3 in this case for aaa, bbb, and ccc) and also the symbol of the outermost inline function (in this case 'aaa' as main isn't an inline function, it just has things inline within it). Now GDB understands the stack to be main -> aaa -> bbb -> ccc, however, the state initialised in skip_inline_frames starts off indicating that we should hide 3 frames from the user, so we report that we're in main at line 39. The location of main, line 39 is derived by asking the inline function state for the last symbol in the stack (aaa in this case), and then asking for it's location - the location of an inlined function symbol is its call site, so main, line 39 in this case. If the user then asks GDB to step we don't actually move the inferior at all, instead we spot that we are in an inline function stack, lookup the inline state data, and reduce the skip depth by 1. We then report to the user that GDB has stopped. GDB now understands that we are in 'aaa'. In order to get the precise location we again ask GDB for the last symbol from the inline data structure, and we are again told 'aaa', we then get the location from 'aaa', and report that we are in main, line 39. Hopefully it's clear what the mistake here is, once we've reduced the inline skip depth we should not be using 'aaa' to compute the precise location, instead we should be using 'bbb'. That is what this patch does. Now, when we call skip_inline_frames instead of just recording the last skipped symbol we now record all symbols in the inline frame stack. When we ask GDB for the last skipped symbol we return a symbol based on how many frames we are skipping, not just the last know symbol. With this fix in place, the same session as above now looks much better: (gdb) start Temporary breakpoint 1 at 0x4003b0: file test.c, line 38. Starting program: /project/gdb/tests/inline/test Temporary breakpoint 1, main () at test.c:38 38 global_var = 0; (gdb) s 39 return aaa () + 1; (gdb) s aaa () at test.c:14 14 return bbb () + 1; (gdb) s bbb () at test.c:26 26 return ccc () + 1; (gdb) s ccc () at test.c:20 20 return ddd () + 1; (gdb) s ddd () at test.c:32 32 return global_var; (gdb) bt #0 ddd () at test.c:32 #1 0x00000000004003c1 in ccc () at test.c:20 #2 bbb () at test.c:26 #3 aaa () at test.c:14 #4 main () at test.c:39 gdb/ChangeLog: * frame.c (find_frame_sal): Move call to get_next_frame into more inner scope. * inline-frame.c (inilne_state) <inline_state>: Update argument types. (inilne_state) <skipped_symbol>: Rename to... (inilne_state) <skipped_symbols>: ...this, and change to a vector. (skip_inline_frames): Build vector of skipped symbols and use this to reate the inline_state. (inline_skipped_symbol): Add a comment and some assertions, fetch skipped symbol from the list. gdb/testsuite/ChangeLog: * gdb.dwarf2/dw2-inline-many-frames.c: New file. * gdb.dwarf2/dw2-inline-many-frames.exp: New file. Change-Id: I99def5ffb44eb9e58cda4b449bf3d91ab0386c62
2019-11-11 23:41:13 +01:00
# Copyright 2019-2020 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 shows the importance of not corrupting the order of line
# table information. When multiple lines are given for the same
# address the compiler usually lists these in the order in which we
# would expect to encounter them. When stepping through nested inline
# frames the last line given for an address is assumed by GDB to be
# the most inner frame, and this is what GDB displays.
#
# If we corrupt the order of the line table entries then GDB will
# display the wrong line as being the inner most frame.
load_lib dwarf.exp
# This test can only be run on targets which support DWARF-2 and use gas.
if {![dwarf2_support]} {
return 0
}
# The .c files use __attribute__.
if [get_compiler_info] {
return -1
}
if !$gcc_compiled {
return 0
}
standard_testfile dw2-inline-many-frames.c dw2-inline-many-frames.S
# Extract the start, length, and end for function called NAME and
# create suitable variables in the callers scope.
proc get_func_info { name } {
global srcdir subdir srcfile
upvar 1 "${name}_start" func_start
upvar 1 "${name}_len" func_len
upvar 1 "${name}_end" func_end
lassign [function_range ${name} [list ${srcdir}/${subdir}/$srcfile]] \
func_start func_len
set func_end "$func_start + $func_len"
}
set asm_file [standard_output_file $srcfile2]
Dwarf::assemble $asm_file {
global srcdir subdir srcfile srcfile2
declare_labels ranges_label lines_label
declare_labels aaa_label bbb_label ccc_label
declare_labels ggg_label hhh_label iii_label
get_func_info main
get_func_info ddd
get_func_info eee
get_func_info fff
get_func_info jjj
get_func_info kkk
set call_in_main [gdb_get_line_number "main call aaa"]
set call_in_aaa [gdb_get_line_number "aaa return"]
set call_in_bbb [gdb_get_line_number "bbb return"]
set call_in_ccc [gdb_get_line_number "ccc return"]
set call_in_fff [gdb_get_line_number "fff return"]
set call_in_ggg [gdb_get_line_number "ggg return"]
set call_in_hhh [gdb_get_line_number "hhh return"]
set call_in_iii [gdb_get_line_number "iii return"]
cu {} {
compile_unit {
{language @DW_LANG_C}
{name dw2-inline-stepping.c}
{low_pc 0 addr}
{stmt_list ${lines_label} DW_FORM_sec_offset}
{ranges ${ranges_label} DW_FORM_sec_offset}
} {
subprogram {
{external 1 flag}
{name ddd}
{low_pc $ddd_start addr}
{high_pc "$ddd_start + $ddd_len" addr}
}
subprogram {
{external 1 flag}
{name eee}
{low_pc $eee_start addr}
{high_pc "$eee_start + $eee_len" addr}
}
subprogram {
{external 1 flag}
{name jjj}
{low_pc $jjj_start addr}
{high_pc "$jjj_start + $jjj_len" addr}
}
subprogram {
{external 1 flag}
{name kkk}
{low_pc $kkk_start addr}
{high_pc "$kkk_start + $kkk_len" addr}
}
aaa_label: subprogram {
{name aaa}
{inline 3 data1}
}
bbb_label: subprogram {
{name bbb}
{inline 3 data1}
}
ccc_label: subprogram {
{name ccc}
{inline 3 data1}
}
ggg_label: subprogram {
{name ggg}
{inline 3 data1}
}
hhh_label: subprogram {
{name hhh}
{inline 3 data1}
}
iii_label: subprogram {
{name iii}
{inline 3 data1}
}
subprogram {
{external 1 flag}
{name main}
{low_pc $main_start addr}
{high_pc "$main_start + $main_len" addr}
} {
inlined_subroutine {
{abstract_origin %$aaa_label}
{low_pc main_label2 addr}
{high_pc main_label3 addr}
{call_file 1 data1}
{call_line $call_in_main data1}
} {
inlined_subroutine {
{abstract_origin %$bbb_label}
{low_pc main_label2 addr}
{high_pc main_label3 addr}
{call_file 1 data1}
{call_line $call_in_aaa data1}
} {
inlined_subroutine {
{abstract_origin %$ccc_label}
{low_pc main_label2 addr}
{high_pc main_label3 addr}
{call_file 1 data1}
{call_line $call_in_bbb data1}
}
}
}
}
subprogram {
{external 1 flag}
{name fff}
{low_pc $fff_start addr}
{high_pc "$fff_start + $fff_len" addr}
} {
inlined_subroutine {
{abstract_origin %$ggg_label}
{low_pc fff_label addr}
{high_pc main_label2 addr}
{call_file 1 data1}
{call_line $call_in_fff data1}
} {
inlined_subroutine {
{abstract_origin %$hhh_label}
{low_pc fff_label addr}
{high_pc fff_label2 addr}
{call_file 1 data1}
{call_line $call_in_ggg data1}
} {
inlined_subroutine {
{abstract_origin %$iii_label}
{low_pc fff_label addr}
{high_pc fff_label2 addr}
{call_file 1 data1}
{call_line $call_in_hhh data1}
}
}
}
}
}
}
lines {version 2} lines_label {
include_dir "${srcdir}/${subdir}"
file_name "$srcfile" 1
program {
{DW_LNE_set_address $main_start}
{DW_LNS_advance_line [expr [gdb_get_line_number "main prologue"] - 1]}
{DW_LNS_copy}
{DW_LNE_set_address main_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "main set global_var"] - [gdb_get_line_number "main prologue"]]}
{DW_LNS_copy}
{DW_LNE_set_address main_label2}
{DW_LNS_advance_line [expr [gdb_get_line_number "main call aaa"] - [gdb_get_line_number "main set global_var"]]}
{DW_LNS_copy}
{DW_LNE_set_address main_label2}
{DW_LNS_advance_line [expr [gdb_get_line_number "aaa return"] - [gdb_get_line_number "main call aaa"]]}
{DW_LNS_copy}
{DW_LNE_set_address main_label2}
{DW_LNS_advance_line [expr [gdb_get_line_number "bbb return"] - [gdb_get_line_number "aaa return"]]}
{DW_LNS_copy}
{DW_LNE_set_address main_label2}
{DW_LNS_advance_line [expr [gdb_get_line_number "ccc return"] - [gdb_get_line_number "bbb return"]]}
{DW_LNS_copy}
{DW_LNE_set_address main_label3}
{DW_LNS_advance_line [expr [gdb_get_line_number "main end"] - [gdb_get_line_number "ccc return"]]}
{DW_LNS_copy}
{DW_LNE_set_address $main_end}
{DW_LNE_end_sequence}
{DW_LNE_set_address $ddd_start}
{DW_LNS_advance_line [expr [gdb_get_line_number "ddd prologue"] - 1]}
{DW_LNS_copy}
{DW_LNE_set_address ddd_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "ddd return"] - [gdb_get_line_number "ddd prologue"]]}
{DW_LNS_copy}
{DW_LNE_set_address ddd_label2}
{DW_LNS_advance_line [expr [gdb_get_line_number "ddd end"] - [gdb_get_line_number "ddd return"]]}
{DW_LNS_copy}
{DW_LNE_set_address $ddd_end}
{DW_LNE_end_sequence}
{DW_LNE_set_address $eee_start}
{DW_LNS_advance_line [expr [gdb_get_line_number "eee prologue"] - 1]}
{DW_LNS_copy}
{DW_LNE_set_address eee_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "eee return"] - [gdb_get_line_number "eee prologue"]]}
{DW_LNS_copy}
{DW_LNE_set_address eee_label2}
{DW_LNS_advance_line [expr [gdb_get_line_number "eee end"] - [gdb_get_line_number "eee return"]]}
{DW_LNS_copy}
{DW_LNE_set_address $eee_end}
{DW_LNE_end_sequence}
{DW_LNE_set_address $fff_start}
{DW_LNS_advance_line [expr [gdb_get_line_number "fff prologue"] - 1]}
{DW_LNS_copy}
{DW_LNE_set_address fff_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "fff return"] - [gdb_get_line_number "fff prologue"]]}
{DW_LNS_copy}
{DW_LNE_set_address fff_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "ggg return"] - [gdb_get_line_number "fff return"]]}
{DW_LNS_copy}
{DW_LNE_set_address fff_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "hhh return"] - [gdb_get_line_number "ggg return"]]}
{DW_LNS_copy}
{DW_LNE_set_address fff_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "iii return"] - [gdb_get_line_number "hhh return"]]}
{DW_LNS_copy}
{DW_LNE_set_address fff_label2}
{DW_LNS_advance_line [expr [gdb_get_line_number "fff end"] - [gdb_get_line_number "fff return"]]}
{DW_LNS_copy}
{DW_LNE_set_address $fff_end}
{DW_LNE_end_sequence}
{DW_LNE_set_address $jjj_start}
{DW_LNS_advance_line [expr [gdb_get_line_number "jjj prologue"] - 1]}
{DW_LNS_copy}
{DW_LNE_set_address jjj_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "jjj return"] - [gdb_get_line_number "jjj prologue"]]}
{DW_LNS_copy}
{DW_LNE_set_address jjj_label2}
{DW_LNS_advance_line [expr [gdb_get_line_number "jjj end"] - [gdb_get_line_number "jjj return"]]}
{DW_LNS_copy}
{DW_LNE_set_address $jjj_end}
{DW_LNE_end_sequence}
{DW_LNE_set_address $kkk_start}
{DW_LNS_advance_line [expr [gdb_get_line_number "kkk prologue"] - 1]}
{DW_LNS_copy}
{DW_LNE_set_address kkk_label}
{DW_LNS_advance_line [expr [gdb_get_line_number "kkk return"] - [gdb_get_line_number "kkk prologue"]]}
{DW_LNS_copy}
{DW_LNE_set_address $kkk_end}
{DW_LNE_end_sequence}
}
}
ranges {is_64 [is_64_target]} {
ranges_label: sequence {
{range {${main_start}} ${main_end}}
{range {${ddd_start}} ${ddd_end}}
{range {${eee_start}} ${eee_end}}
{range {${fff_start}} ${fff_end}}
{range {${jjj_start}} ${jjj_end}}
{range {${kkk_start}} ${kkk_end}}
}
}
}
if { [prepare_for_testing "failed to prepare" ${testfile} \
[list $srcfile $asm_file] {nodebug}] } {
return -1
}
if ![runto_main] {
return -1
}
# First we step through all of the functions until we get the 'kkk'.
set patterns [list "main call aaa" \
"aaa return" \
"bbb return" \
"ccc return" \
"ddd return" \
"eee return" \
"fff return" \
"ggg return" \
"hhh return" \
"iii return" \
"jjj return" \
"kkk return" ]
foreach p $patterns {
gdb_test "step" "/\\* $p \\*/" \
"step to '$p'"
}
# Now check the backtrace.
set line_in_main [gdb_get_line_number "main call aaa"]
set line_in_aaa [gdb_get_line_number "aaa return"]
set line_in_bbb [gdb_get_line_number "bbb return"]
set line_in_ccc [gdb_get_line_number "ccc return"]
set line_in_ddd [gdb_get_line_number "ddd return"]
set line_in_eee [gdb_get_line_number "eee return"]
set line_in_fff [gdb_get_line_number "fff return"]
set line_in_ggg [gdb_get_line_number "ggg return"]
set line_in_hhh [gdb_get_line_number "hhh return"]
set line_in_iii [gdb_get_line_number "iii return"]
set line_in_jjj [gdb_get_line_number "jjj return"]
set line_in_kkk [gdb_get_line_number "kkk return"]
gdb_test "bt" [multi_line \
"#0 kkk \\(\\) at \[^\r\n\]+${srcfile}:${line_in_kkk}" \
"#1 $hex in jjj \\(\\) at \[^\r\n\]+${srcfile}:${line_in_jjj}" \
"#2 $hex in iii \\(\\) at \[^\r\n\]+${srcfile}:${line_in_iii}" \
"#3 hhh \\(\\) at \[^\r\n\]+${srcfile}:${line_in_hhh}" \
"#4 ggg \\(\\) at \[^\r\n\]+${srcfile}:${line_in_ggg}" \
"#5 fff \\(\\) at \[^\r\n\]+${srcfile}:${line_in_fff}" \
"#6 $hex in eee \\(\\) at \[^\r\n\]+${srcfile}:${line_in_eee}" \
"#7 $hex in ddd \\(\\) at \[^\r\n\]+${srcfile}:${line_in_ddd}" \
"#8 $hex in ccc \\(\\) at \[^\r\n\]+${srcfile}:${line_in_ccc}" \
"#9 bbb \\(\\) at \[^\r\n\]+${srcfile}:${line_in_bbb}" \
"#10 aaa \\(\\) at \[^\r\n\]+${srcfile}:${line_in_aaa}" \
"#11 main \\(\\) at \[^\r\n\]+${srcfile}:${line_in_main}" ]
# Now check we can use 'up' to inspect each frame correctly.
set patterns [list \
"jjj return" \
"iii return" \
"hhh return" \
"ggg return" \
"fff return" \
"eee return" \
"ddd return" \
"ccc return" \
"bbb return" \
"aaa return" \
"main call aaa" ]
foreach p $patterns {
gdb_test "up" "/\\* $p \\*/" \
"up to '$p'"
}