From: Simon Marchi Date: Mon, 26 Jan 2026 06:15:19 +0000 (-0500) Subject: gdb/dwarf: fix DW_OP_call{2,4} in .dwo files X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1f5f99c4b310e8c5c760d76246e0b20e8532c294;p=thirdparty%2Fbinutils-gdb.git gdb/dwarf: fix DW_OP_call{2,4} in .dwo files Bug 31772 shows a failure of gdb.ada/finish-var-size.exp when ran with the fission board, modified to generate DWARF 5. Ultimately, the problem shows up when executing a DW_OP_call{2,4} operation from a .dwo file. The DW_OP_call{2,4} ops receive a CU-relative offset. Function dwarf2_fetch_die_loc_cu_off receives that offset and transforms it in a section-relative offset by adding the section offset of the containing CU: sect_offset sect_off = per_cu->sect_off () + to_underlying (offset_in_cu); The problem is that `per_cu->sect_off ()` returns the offset of the skeleton CU in the main file, when what we really want is the offset of the split unit in the .dwo file. To obtain that, we need to get the loaded CU (dwarf2_cu), from which we can know if the DIEs were actually read from a .dwo file or not. And if so, get the section offset of the dwo unit, rather than the skeleton unit. Calling load_cu here does not have a negative performance impact, because it would be called by dwarf2_fetch_die_loc_sect_off anyway. Add a section_offset method to dwarf2_cu to get the effective section offset of the unit. This mirrors what we do for the section in dwarf2_cu::section. Add a test that reproduces this exact case. GDB does not support DW_OP_call_ref, so don't test that. It would also be unexpected anyway to have cross-unit references in a .dwo file (but no completely unthinkable, given we support having multiple CUs in a .dwo file). Change-Id: If5faac252b32ed9751d29681590b668225708275 Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=31772 Approved-By: Tom Tromey --- diff --git a/gdb/dwarf2/cu.h b/gdb/dwarf2/cu.h index b3ade5a2e6e..1361898accc 100644 --- a/gdb/dwarf2/cu.h +++ b/gdb/dwarf2/cu.h @@ -60,6 +60,11 @@ struct dwarf2_cu variants. */ const dwarf2_section_info §ion () const; + /* The section offset of the beginning of this unit in its section. + + For a split DWARF unit, this is the offset in the .dwo section. */ + sect_offset section_offset () const; + /* TU version of handle_DW_AT_stmt_list for read_type_unit_scope. Create the set of symtabs used by this TU, or if this TU is sharing symtabs with another TU and the symtabs have already been created diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c index 6061df0ecb8..f23a9af19f3 100644 --- a/gdb/dwarf2/read.c +++ b/gdb/dwarf2/read.c @@ -6041,6 +6041,20 @@ dwarf2_cu::section () const return *this->per_cu->section (); } +/* See cu.h. + + This function is defined in this file (instead of cu.c) because it needs + to see the definition of struct dwo_unit. */ + +sect_offset +dwarf2_cu::section_offset () const +{ + if (this->dwo_unit != nullptr) + return this->dwo_unit->sect_off; + else + return this->per_cu->sect_off (); +} + void dwarf2_cu::setup_type_unit_groups (struct die_info *die) { @@ -17161,7 +17175,19 @@ dwarf2_fetch_die_loc_cu_off (cu_offset offset_in_cu, dwarf2_per_cu *per_cu, dwarf2_per_objfile *per_objfile, gdb::function_view get_frame_pc) { - sect_offset sect_off = per_cu->sect_off () + to_underlying (offset_in_cu); + /* For split DWARF, the section offset of PER_CU is the offset of the + skeleton CU in the main file, but OFFSET_IN_CU is relative to the start + of the CU in the .dwo file. We need to use the section offset of the unit + in the .dwo file in that case. */ + dwarf2_cu *cu = per_objfile->get_cu (per_cu); + if (cu == nullptr) + cu = load_cu (per_cu, per_objfile, false); + + /* We know this can't be a dummy CU, since we're executing something from + it. */ + gdb_assert (cu != nullptr); + + sect_offset sect_off = cu->section_offset () + to_underlying (offset_in_cu); return dwarf2_fetch_die_loc_sect_off (sect_off, per_cu, per_objfile, get_frame_pc); diff --git a/gdb/testsuite/gdb.dwarf2/fission-call.c b/gdb/testsuite/gdb.dwarf2/fission-call.c new file mode 100644 index 00000000000..6a0e311ef41 --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/fission-call.c @@ -0,0 +1,22 @@ +/* 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 . */ + +int +main (void) +{ + return 0; +} diff --git a/gdb/testsuite/gdb.dwarf2/fission-call.exp b/gdb/testsuite/gdb.dwarf2/fission-call.exp new file mode 100644 index 00000000000..af62fc20fbb --- /dev/null +++ b/gdb/testsuite/gdb.dwarf2/fission-call.exp @@ -0,0 +1,119 @@ +# 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 . + +# Test DW_OP_call2 and DW_OP_call4 in a .dwo file (split DWARF), to verify +# that GDB correctly computes offsets when evaluating these operators. +# See PR gdb/31772. +# +# The CU in the .dwo file is placed after a type unit, so that it is not at +# offset 0. This is important to test that GDB correctly adds the CU offset +# within the section when computing the target of DW_OP_call2/call4. + +load_lib dwarf.exp + +require dwarf2_support + +standard_testfile .c -dw.S + +set asm_file [standard_output_file $srcfile2] + +Dwarf::assemble $asm_file { + # Skeleton CU in the main file. + cu { + version 5 + dwo_id 0x1234 + } { + compile_unit { + DW_AT_dwo_name ${::gdb_test_file_name}-dw.dwo DW_FORM_strp + } {} + } + + # Type unit in the .dwo file. This is placed before the CU to ensure + # it is not at offset 0 in .debug_info.dwo. + tu { + fission 1 + version 5 + } 0xCAFE "the_type" { + type_unit {} { + the_type: base_type { + DW_AT_byte_size 4 DW_FORM_sdata + DW_AT_encoding @DW_ATE_signed + DW_AT_name dummy_type + } + } + } + + # Compilation unit in the .dwo file. + cu { + fission 1 + version 5 + dwo_id 0x1234 + label cu_start + } { + compile_unit {} { + declare_labels int_type dwarf_proc + + int_type: DW_TAG_base_type { + DW_AT_byte_size 4 DW_FORM_sdata + DW_AT_encoding @DW_ATE_signed + DW_AT_name int + } + + # DWARF procedure that multiplies the top of stack by 2. + dwarf_proc: DW_TAG_dwarf_procedure { + DW_AT_location { + DW_OP_lit2 + DW_OP_mul + } SPECIAL_expr + } + + # Variable using DW_OP_call2 (2-byte CU-relative offset). + # Pushes 5, calls the procedure (5 * 2 = 10). + DW_TAG_variable { + DW_AT_name var_call2 + DW_AT_type :$int_type + DW_AT_location { + DW_OP_lit5 + DW_OP_call2 "$dwarf_proc - $cu_start" + DW_OP_stack_value + } SPECIAL_expr + } + + # Variable using DW_OP_call4 (4-byte CU-relative offset). + # Pushes 6, calls the procedure (6 * 2 = 12). + DW_TAG_variable { + DW_AT_name var_call4 + DW_AT_type :$int_type + DW_AT_location { + DW_OP_lit6 + DW_OP_call4 "$dwarf_proc - $cu_start" + DW_OP_stack_value + } SPECIAL_expr + } + } + } +} + +set obj [standard_output_file "${testfile}-dw.o"] +if {[build_executable_and_dwo_files "$testfile.exp" "${binfile}" {} \ + [list $asm_file {nodebug split-dwo} $obj] \ + [list $srcfile {nodebug}]]} { + return +} + +clean_restart ${testfile} + +gdb_test "print var_call2" " = 10" "DW_OP_call2" +gdb_test "print var_call4" " = 12" "DW_OP_call4" diff --git a/gdb/testsuite/lib/dwarf.exp b/gdb/testsuite/lib/dwarf.exp index 9bd385fd407..9fa5c4e297d 100644 --- a/gdb/testsuite/lib/dwarf.exp +++ b/gdb/testsuite/lib/dwarf.exp @@ -1443,6 +1443,14 @@ namespace eval Dwarf { _op .sleb128 $reg } + proc _handle_DW_OP_call2 {offset} { + _op .2byte $offset + } + + proc _handle_DW_OP_call4 {offset} { + _op .4byte $offset + } + proc _handle_default_OP {} { # Do nothing; if arguments are passed, Tcl will cause an # error.