# Copyright 2025-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 . # When compiling optimised code, GCC will sometimes truncate the address # range of an inline function, usually by a single instruction. # # It is possible to detect when this has happened by looking at the line # table, GCC will create two non-statement line table entries associated # with the call-line of the inline function, but the end address of the # inline function will be set to be the address of the first of these line # table entries. # # The problem here is that block end addresses are not inclusive, which # means the block ends before either of these line table entries. # # What we find is that we get a better debug experience if we extend the # inline function to actually end at the second line table entry, that is # the first line table entry becomes part of the inline function, while the # second entry remains outside the inline function. # # This test tries to create this situation using the DWARF assembler, and # then checks that GDB correctly extends the inline function to include the # first line table entry. load_lib dwarf.exp require dwarf2_support standard_testfile .c # Lines numbers we reference in the generated DWARF. set main_decl_line [gdb_get_line_number "main decl line"] set main_line_1 [gdb_get_line_number "main:1"] set main_line_4 [gdb_get_line_number "main:4"] set foo_call_line [gdb_get_line_number "foo call line"] set foo_line_1 [gdb_get_line_number "foo:1"] 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 {range_correct 0} {variant 0} } { Dwarf::assemble $asm_file { upvar range_correct range_correct upvar variant variant declare_labels lines_table inline_func cu { } { DW_TAG_compile_unit { DW_AT_producer "GNU C 14.1.0" DW_AT_language @DW_LANG_C DW_AT_name $::srcfile DW_AT_comp_dir /tmp DW_AT_low_pc 0 addr DW_AT_stmt_list $lines_table DW_FORM_sec_offset } { inline_func: subprogram { DW_AT_name foo DW_AT_inline @DW_INL_declared_inlined } subprogram { DW_AT_name main DW_AT_decl_file 1 data1 DW_AT_decl_line $::main_decl_line data1 DW_AT_decl_column 1 data1 DW_AT_low_pc $::main_start addr DW_AT_high_pc $::main_len data4 DW_AT_external 1 flag } { inlined_subroutine { DW_AT_abstract_origin %$inline_func DW_AT_call_file 1 data1 DW_AT_call_line $::foo_call_line data1 DW_AT_low_pc main_1 addr if {$range_correct} { DW_AT_high_pc main_4 addr } else { DW_AT_high_pc main_3 addr } } } } } lines {version 2 default_is_stmt 1} lines_table { include_dir "$::srcdir/$::subdir" file_name "$::srcfile" 1 program { DW_LNE_set_address main line $::main_line_1 DW_LNS_copy DW_LNE_set_address main_0 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_1 line $::foo_line_1 DW_LNS_copy DW_LNE_set_address main_2 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_3 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_3 line $::foo_call_line DW_LNS_negate_stmt 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 if {$variant == 0} { DW_LNS_advance_line 1 } DW_LNS_negate_stmt DW_LNS_copy DW_LNE_set_address main_6 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_7 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_8 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address "$::main_start + $::main_len" DW_LNE_end_sequence } } } } # 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'. proc check_contiguous_block {} { set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \ "get address of foo start"] set foo_end [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \ "get address of foo end"] gdb_test "maintenance info blocks" \ [multi_line \ "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \ " entry pc: $foo_start" \ " inline function: foo" \ " symbol count: $::decimal" \ " is contiguous"] \ "block for foo has expected content" } # Create DWARF for the test. In this case, inline function 'foo' is created # with two ranges, and it is the first range that needs extending. proc build_dwarf_for_first_block_range { asm_file dwarf_version } { Dwarf::assemble $asm_file { upvar dwarf_version dwarf_version declare_labels lines_table inline_func ranges_label cu { version $dwarf_version } { DW_TAG_compile_unit { DW_AT_producer "GNU C 14.1.0" DW_AT_language @DW_LANG_C DW_AT_name $::srcfile DW_AT_comp_dir /tmp DW_AT_low_pc 0 addr DW_AT_stmt_list $lines_table DW_FORM_sec_offset } { inline_func: subprogram { DW_AT_name foo DW_AT_inline @DW_INL_declared_inlined } subprogram { DW_AT_name main DW_AT_decl_file 1 data1 DW_AT_decl_line $::main_decl_line data1 DW_AT_decl_column 1 data1 DW_AT_low_pc $::main_start addr DW_AT_high_pc $::main_len data4 DW_AT_external 1 flag } { inlined_subroutine { DW_AT_abstract_origin %$inline_func DW_AT_call_file 1 data1 DW_AT_call_line $::foo_call_line data1 DW_AT_ranges $ranges_label DW_FORM_sec_offset } } } } lines {version 2 default_is_stmt 1} lines_table { include_dir "$::srcdir/$::subdir" file_name "$::srcfile" 1 program { DW_LNE_set_address main line $::main_line_1 DW_LNS_copy DW_LNE_set_address main_0 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_1 line $::foo_line_1 DW_LNS_copy DW_LNE_set_address main_2 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_3 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_3 line $::foo_call_line DW_LNS_negate_stmt DW_LNS_copy DW_LNE_set_address main_4 DW_LNS_copy DW_LNE_set_address main_5 DW_LNS_advance_line 1 DW_LNS_negate_stmt DW_LNS_copy DW_LNE_set_address main_6 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_7 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_8 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address "$::main_start + $::main_len" DW_LNE_end_sequence } } if { $dwarf_version == 5 } { rnglists {} { table {} { ranges_label: list_ { start_end main_1 main_3 start_end main_7 main_8 } } } } else { ranges { } { ranges_label: sequence { range main_1 main_3 range main_7 main_8 } } } } } # Wrapper around 'build_dwarf_for_first_block_range', creates DWARF 4 range # information. proc build_dwarf_for_first_block_range_4 { asm_file } { build_dwarf_for_first_block_range $asm_file 4 } # Wrapper around 'build_dwarf_for_first_block_range', creates DWARF 5 range # information. proc build_dwarf_for_first_block_range_5 { asm_file } { build_dwarf_for_first_block_range $asm_file 5 } # 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_first_block_range'. proc check_for_block_ranges_1 {} { set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \ "get address of foo start"] set foo_end [get_hexadecimal_valueof "&main_8" "*UNKNOWN*" \ "get address of foo end"] set main_4 [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \ "get address of main_4 label"] set main_7 [get_hexadecimal_valueof "&main_7" "*UNKNOWN*" \ "get address of main_7 label"] gdb_test "maintenance info blocks" \ [multi_line \ "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \ " entry pc: $foo_start" \ " inline function: foo" \ " symbol count: $::decimal" \ " address ranges:" \ " $foo_start\\.\\.$main_4" \ " $main_7\\.\\.$foo_end"] \ "block for foo has expected content" } # Create DWARF for the test. In this case, inline function 'foo' is created # with two ranges, and it is the second range that needs extending. proc build_dwarf_for_last_block_range { asm_file dwarf_version } { Dwarf::assemble $asm_file { upvar dwarf_version dwarf_version declare_labels lines_table inline_func ranges_label cu { version $dwarf_version } { DW_TAG_compile_unit { DW_AT_producer "GNU C 14.1.0" DW_AT_language @DW_LANG_C DW_AT_name $::srcfile DW_AT_comp_dir /tmp DW_AT_low_pc 0 addr DW_AT_stmt_list $lines_table DW_FORM_sec_offset } { inline_func: subprogram { DW_AT_name foo DW_AT_inline @DW_INL_declared_inlined } subprogram { DW_AT_name main DW_AT_decl_file 1 data1 DW_AT_decl_line $::main_decl_line data1 DW_AT_decl_column 1 data1 DW_AT_low_pc $::main_start addr DW_AT_high_pc $::main_len data4 DW_AT_external 1 flag } { inlined_subroutine { DW_AT_abstract_origin %$inline_func DW_AT_call_file 1 data1 DW_AT_call_line $::foo_call_line data1 DW_AT_ranges $ranges_label DW_FORM_sec_offset DW_AT_entry_pc main_1 addr } } } } lines {version 2 default_is_stmt 1} lines_table { include_dir "$::srcdir/$::subdir" file_name "$::srcfile" 1 program { DW_LNE_set_address main line $::main_line_1 DW_LNS_copy DW_LNE_set_address main_0 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_1 line $::foo_line_1 DW_LNS_copy DW_LNE_set_address main_2 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_3 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_3 line $::foo_call_line DW_LNS_negate_stmt DW_LNS_copy DW_LNE_set_address main_4 DW_LNS_copy DW_LNE_set_address main_5 DW_LNS_advance_line 1 DW_LNS_negate_stmt DW_LNS_copy DW_LNE_set_address main_6 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_7 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address main_8 DW_LNS_advance_line 1 DW_LNS_copy DW_LNE_set_address "$::main_start + $::main_len" DW_LNE_end_sequence } } if { $dwarf_version == 5 } { rnglists {} { table {} { ranges_label: list_ { start_end main_7 main_8 start_end main_1 main_3 } } } } else { ranges { } { ranges_label: sequence { range main_7 main_8 range main_1 main_3 } } } } } # Wrapper around 'build_dwarf_for_last_block_range', creates DWARF 4 range # information. proc build_dwarf_for_last_block_range_4 { asm_file } { build_dwarf_for_last_block_range $asm_file 4 } # Wrapper around 'build_dwarf_for_last_block_range', creates DWARF 5 range # information. proc build_dwarf_for_last_block_range_5 { asm_file } { build_dwarf_for_last_block_range $asm_file 5 } # 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_last_block_range'. proc check_for_block_ranges_2 {} { set foo_start [get_hexadecimal_valueof "&main_1" "*UNKNOWN*" \ "get address of foo start"] set foo_end [get_hexadecimal_valueof "&main_8" "*UNKNOWN*" \ "get address of foo end"] set main_4 [get_hexadecimal_valueof "&main_4" "*UNKNOWN*" \ "get address of main_4 label"] set main_7 [get_hexadecimal_valueof "&main_7" "*UNKNOWN*" \ "get address of main_7 label"] gdb_test "maintenance info blocks" \ [multi_line \ "\\\[\\(block \\*\\) $::hex\\\] $foo_start\\.\\.$foo_end" \ " entry pc: $foo_start" \ " inline function: foo" \ " symbol count: $::decimal" \ " address ranges:" \ " $main_7\\.\\.$foo_end" \ " $foo_start\\.\\.$main_4"] \ "block for foo has expected content" } # Buidl ASM_FILE, along with the global SRCFILE into an executable called # TESTFILE. Place a breakpoint in 'foo', run to the breakpoint, and use # BLOCK_CHECK_FUNC to ensure the block for 'foo' is correct. # # Then step through 'foo' and back into 'main'. proc run_test { asm_file testfile block_check_func } { if {[prepare_for_testing "failed to prepare" $testfile \ [list $::srcfile $asm_file] {nodebug}]} { return } if {![runto_main]} { return } gdb_breakpoint foo gdb_test "continue" \ [multi_line \ "Breakpoint $::decimal, foo \\(\\) \[^\r\n\]+:$::foo_line_1" \ "$::foo_line_1\\s+/\\* foo:1 \\*/"] \ "continue to b/p in foo" # Check that the block for `foo` has been extended. $block_check_func gdb_test "frame 1" \ [multi_line \ "#1 main \\(\\) at \[^\r\n\]+/$::srcfile:$::foo_call_line" \ "$::foo_call_line\\s+\[^\r\n\]+/\\* foo call line \\*/"] \ "frame 1 is for main" gdb_test "step" \ "^[expr {$::foo_line_1 + 1}]\\s+/\\* foo:2 \\*/" \ "step to second line of foo" gdb_test "step" \ "^[expr {$::foo_line_1 + 2}]\\s+/\\* foo:3 \\*/" \ "step to third line of foo" gdb_test "step" \ [multi_line \ "^main \\(\\) at \[^\r\n\]+:$::main_line_4" \ "$::main_line_4\\s+/\\* main:4 \\*/"] \ "set back to main" gdb_test "step" \ "^[expr {$::main_line_4 + 1}]\\s+/\\* main:5 \\*/" \ "step again in main" } # Test specifications, items are: # 1. Prefix string used to describe the test. # 2. Proc to call that builds the DWARF. # 3. Proc to call that runs 'maint info blocks' when stopped at the entry # $pc for 'foo' (the inline function), and checks that the block details # for 'foo' are correct. set test_list \ [list \ [list "block with ranges, extend first range, dwarf 4" \ build_dwarf_for_first_block_range_4 \ check_for_block_ranges_1] \ [list "block with ranges, extend first range, dwarf 5" \ build_dwarf_for_first_block_range_5 \ check_for_block_ranges_1] \ [list "block with ranges, extend last range, dwarf 4" \ build_dwarf_for_last_block_range_4 \ check_for_block_ranges_2] \ [list "block with ranges, extend last range, dwarf 5" \ build_dwarf_for_last_block_range_4 \ check_for_block_ranges_2] \ [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. set suffix 0 foreach test_spec $test_list { incr suffix set prefix [lindex $test_spec 0] 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 PR gdb/33930. continue } with_test_prefix $prefix { set asm_file [standard_output_file ${testfile}-${suffix}.S] $build_dwarf_func $asm_file run_test $asm_file ${testfile}-${suffix} $check_block_func } }