]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb/dwarf: fix DW_OP_call{2,4} in .dwo files
authorSimon Marchi <simon.marchi@polymtl.ca>
Mon, 26 Jan 2026 06:15:19 +0000 (01:15 -0500)
committerSimon Marchi <simon.marchi@polymtl.ca>
Fri, 13 Feb 2026 20:27:10 +0000 (15:27 -0500)
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 <tom@tromey.com>
gdb/dwarf2/cu.h
gdb/dwarf2/read.c
gdb/testsuite/gdb.dwarf2/fission-call.c [new file with mode: 0644]
gdb/testsuite/gdb.dwarf2/fission-call.exp [new file with mode: 0644]
gdb/testsuite/lib/dwarf.exp

index b3ade5a2e6ef91e4fcd03621d52ed308d7afb6f9..1361898accccc322dabffb45b3f9c92b9230d517 100644 (file)
@@ -60,6 +60,11 @@ struct dwarf2_cu
      variants.  */
   const dwarf2_section_info &section () 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
index 6061df0ecb8efc48a27a6cf88719e9034b36a3fb..f23a9af19f34648fc798cf38b62cb50e927cd7b8 100644 (file)
@@ -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<CORE_ADDR ()> 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 (file)
index 0000000..6a0e311
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.  */
+
+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 (file)
index 0000000..af62fc2
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+# 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"
index 9bd385fd4076db26185958267e0196b0f25bf377..9fa5c4e297daa785c412ffdaae020afdfafac847 100644 (file)
@@ -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.