[2/2,gdb/symtab] Handle DW_OP_entry_value at function entry

Message ID 20240906144225.29330-2-tdevries@suse.de
State New
Headers
Series [1/2,gdb/testsuite] Add proc _location_no_split |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm fail Test failed

Commit Message

Tom de Vries Sept. 6, 2024, 2:42 p.m. UTC
  On riscv64-linux, with test-case gdb.base/vla-optimized-out.exp I ran into:
...
(gdb) p sizeof (a)^M
$2 = <optimized out>^M
(gdb) FAIL: $exp: o1: printed size of optimized out vla
...

The variable a has type 0xbf:
...
 <1><bf>: Abbrev Number: 12 (DW_TAG_array_type)
    <c0>   DW_AT_type        : <0xe3>
    <c4>   DW_AT_sibling     : <0xdc>
 <2><c8>: Abbrev Number: 13 (DW_TAG_subrange_type)
    <c9>   DW_AT_type        : <0xdc>
    <cd>   DW_AT_upper_bound : 13 byte block:
                               a3 1 5a 23 1 8 20 24 8 20 26 31 1c
			       (DW_OP_entry_value: (DW_OP_reg10 (a0));
			        DW_OP_plus_uconst: 1; DW_OP_const1u: 32;
				DW_OP_shl; DW_OP_const1u: 32; DW_OP_shra;
				DW_OP_lit1; DW_OP_minus)
...
which has an upper bound using an DW_OP_entry_value, and since the
corresponding call site contains no information to resolve the value of a0 at
function entry:
...
 <2><6b>: Abbrev Number: 6 (DW_TAG_call_site)
    <6c>   DW_AT_call_return_pc: 0x638
    <74>   DW_AT_call_origin : <0x85>
...
evaluting the dwarf expression fails, and we get <optimized out>.

My first thought was to try breaking at *f1 instead f1 to see if that would
help, but actually the breakpoint resolved to the same address.

In other words, the inferior is stopped at function entry.

Fix this by resolving DW_OP_entry_value when stopped at function entry by
simply evaluating the expression.

This handles these two cases (x86_64, using reg rdi):
- DW_OP_entry_value: (DW_OP_regx: 5 (rdi))
- DW_OP_entry_value: (DW_OP_bregx: 5 (rdi) 0; DW_OP_deref_size: 4)

Tested on x86_64-linux.

Tested gdb.base/vla-optimized-out.exp on riscv64-linux.

Tested an earlier version of gdb.dwarf2/dw2-entry-value-2.exp on
riscv64-linux, but atm I'm running into trouble on that machine (cfarm92) so
I haven't tested the current version there.
---
 gdb/dwarf2/expr.c                             |  69 +++++++---
 gdb/dwarf2/expr.h                             |   4 +
 gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.c  |  32 +++++
 .../gdb.dwarf2/dw2-entry-value-2.exp          | 120 ++++++++++++++++++
 gdb/testsuite/lib/dwarf.exp                   |  11 ++
 5 files changed, 220 insertions(+), 16 deletions(-)
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.c
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.exp
  

Patch

diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c
index cb80dbf60b1..42c9e359ec4 100644
--- a/gdb/dwarf2/expr.c
+++ b/gdb/dwarf2/expr.c
@@ -862,6 +862,34 @@  dwarf_expr_context::read_mem (gdb_byte *buf, CORE_ADDR addr,
 
 /* See expr.h.  */
 
+value *
+dwarf_expr_context::deref (CORE_ADDR addr, int size, struct type *type)
+{
+  gdb_byte *buf = (gdb_byte *) alloca (size);
+  this->read_mem (buf, addr, size);
+
+  if (type == nullptr)
+    type = this->address_type ();
+
+  /* If the size of the object read from memory is different
+     from the type length, we need to zero-extend it.  */
+  if (type->length () != size)
+    {
+      gdbarch *arch = this->m_per_objfile->objfile->arch ();
+      bfd_endian byte_order = gdbarch_byte_order (arch);
+      ULONGEST datum
+	= extract_unsigned_integer (buf, size, byte_order);
+
+      buf = (gdb_byte *) alloca (type->length ());
+      store_unsigned_integer (buf, type->length (),
+			      byte_order, datum);
+    }
+
+  return value_from_contents_and_address (type, buf, addr);
+}
+
+/* See expr.h.  */
+
 void
 dwarf_expr_context::push_dwarf_reg_entry_value (call_site_parameter_kind kind,
 						call_site_parameter_u kind_u,
@@ -1894,7 +1922,6 @@  dwarf_expr_context::execute_stack_op (const gdb_byte *op_ptr,
 	case DW_OP_GNU_deref_type:
 	  {
 	    int addr_size = (op == DW_OP_deref ? this->m_addr_size : *op_ptr++);
-	    gdb_byte *buf = (gdb_byte *) alloca (addr_size);
 	    CORE_ADDR addr = fetch_address (0);
 	    struct type *type;
 
@@ -1909,21 +1936,7 @@  dwarf_expr_context::execute_stack_op (const gdb_byte *op_ptr,
 	    else
 	      type = address_type;
 
-	    this->read_mem (buf, addr, addr_size);
-
-	    /* If the size of the object read from memory is different
-	       from the type length, we need to zero-extend it.  */
-	    if (type->length () != addr_size)
-	      {
-		ULONGEST datum =
-		  extract_unsigned_integer (buf, addr_size, byte_order);
-
-		buf = (gdb_byte *) alloca (type->length ());
-		store_unsigned_integer (buf, type->length (),
-					byte_order, datum);
-	      }
-
-	    result_val = value_from_contents_and_address (type, buf, addr);
+	    result_val = this->deref (addr, addr_size, type);
 	    break;
 	  }
 
@@ -2252,6 +2265,18 @@  dwarf_expr_context::execute_stack_op (const gdb_byte *op_ptr,
 	    if (kind_u.dwarf_reg != -1)
 	      {
 		op_ptr += len;
+
+		if (m_frame == get_current_frame ()
+		    && find_function_type (get_frame_pc (m_frame)) != nullptr)
+		  {
+		    /* We're stopped at the start of the function.
+		       Handle as DW_OP_regx.  */
+		    result_val
+		      = value_from_ulongest (address_type, kind_u.dwarf_reg);
+		    this->m_location = DWARF_VALUE_REGISTER;
+		    break;
+		  }
+
 		this->push_dwarf_reg_entry_value (CALL_SITE_PARAMETER_DWARF_REG,
 						  kind_u,
 						  -1 /* deref_size */);
@@ -2266,6 +2291,18 @@  dwarf_expr_context::execute_stack_op (const gdb_byte *op_ptr,
 		if (deref_size == -1)
 		  deref_size = this->m_addr_size;
 		op_ptr += len;
+
+		if (m_frame == get_current_frame ()
+		    && find_function_type (get_frame_pc (m_frame)) != nullptr)
+		  {
+		    /* We're stopped at the start of the function.
+		       Handle as DW_OP_bregx;DW_OP_deref_size.  */
+		    CORE_ADDR addr
+		      = read_addr_from_reg (this->m_frame, kind_u.dwarf_reg);
+		    result_val = this->deref (addr, deref_size);
+		    break;
+		  }
+
 		this->push_dwarf_reg_entry_value (CALL_SITE_PARAMETER_DWARF_REG,
 						  kind_u, deref_size);
 		goto no_push;
diff --git a/gdb/dwarf2/expr.h b/gdb/dwarf2/expr.h
index b02cc531640..c6c2b90aded 100644
--- a/gdb/dwarf2/expr.h
+++ b/gdb/dwarf2/expr.h
@@ -252,6 +252,10 @@  struct dwarf_expr_context
      but with the address being 0.  In this situation, we arrange for
      memory reads to come from the passed-in buffer.  */
   void read_mem (gdb_byte *buf, CORE_ADDR addr, size_t length);
+
+  /* Deref ADDR with size SIZE and return a value of type TYPE.
+     If TYPE == nullptr, defaults to this->address_type ().  */
+  value *deref (CORE_ADDR addr, int size, struct type *type = nullptr);
 };
 
 /* Return the value of register number REG (a DWARF register number),
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.c b/gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.c
new file mode 100644
index 00000000000..356f8162d04
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.c
@@ -0,0 +1,32 @@ 
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2024 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 var = 2;
+
+static
+void bar (int *p)
+{
+  asm ("bar_label: .globl bar_label");
+}
+
+int
+main()
+{
+  asm ("main_label: .globl main_label");
+  bar (&var);
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.exp b/gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.exp
new file mode 100644
index 00000000000..3bbb72996ac
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-entry-value-2.exp
@@ -0,0 +1,120 @@ 
+# Copyright 2024 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/>.
+
+# Check that we can get DW_OP_entry_value at function entry.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile .c -debug.S
+
+set test "get dwarf regnum for first argument register"
+if { [is_x86_64_m64_target] } {
+    set dwarf_regnum 5
+} elseif { [istarget riscv64*] } {
+    set dwarf_regnum 10
+} else {
+    set reason "architecture-specific setting missing"
+    unsupported "$test ($reason)"
+    return
+}
+pass $test
+
+# Set up the DWARF for the test.
+
+set asm_file [standard_output_file $srcfile2]
+Dwarf::assemble $asm_file {
+    global srcdir subdir srcfile
+
+    get_func_info main
+    get_func_info bar
+
+    cu {} {
+	DW_TAG_compile_unit {
+		{DW_AT_name $srcfile}
+	} {
+	    declare_labels integer
+
+	    integer: DW_TAG_base_type {
+		{DW_AT_byte_size 8 DW_FORM_sdata}
+		{DW_AT_encoding @DW_ATE_signed}
+		{DW_AT_name integer}
+	    }
+
+	    DW_TAG_subprogram {
+		{ DW_AT_name main }
+		{ DW_AT_low_pc $main_start DW_FORM_addr }
+		{ DW_AT_high_pc $main_end DW_FORM_addr }
+	    } {
+		DW_TAG_variable {
+		    { DW_AT_name argc }
+		    { DW_AT_type :$integer }
+		    { DW_AT_location {
+			DW_OP_entry_value { {DW_OP_regx $::dwarf_regnum} }
+		    } SPECIAL_expr }
+		}
+	    }
+
+	    DW_TAG_subprogram {
+		{ DW_AT_name bar }
+		{ DW_AT_low_pc $bar_start DW_FORM_addr }
+		{ DW_AT_high_pc $bar_end DW_FORM_addr }
+	    } {
+		DW_TAG_variable {
+		    { DW_AT_name foo }
+		    { DW_AT_type :$integer }
+		    { DW_AT_location {
+			DW_OP_entry_value { {DW_OP_bregx $::dwarf_regnum 0} {DW_OP_deref_size 4 } }
+			DW_OP_stack_value
+		    } SPECIAL_expr }
+		}
+	    }
+	}
+    }
+}
+
+if { [prepare_for_testing "failed to prepare" $testfile \
+	  [list $srcfile $asm_file] {nodebug}] } {
+    return -1
+}
+
+if { ![runto *main] } {
+    return
+}
+
+with_test_prefix "at main+0" {
+    gdb_test "p argc" " = 1"
+
+    gdb_test "stepi"
+}
+
+with_test_prefix "at main+1" {
+    gdb_test "p argc" " = <optimized out>"
+}
+
+gdb_breakpoint "*bar"
+gdb_continue_to_breakpoint "bar"
+
+with_test_prefix "at bar+0" {
+    gdb_test "p foo" " = 2"
+
+    gdb_test "stepi"
+}
+
+with_test_prefix "at bar+1" {
+    gdb_test "p foo" " = <optimized out>"
+}
diff --git a/gdb/testsuite/lib/dwarf.exp b/gdb/testsuite/lib/dwarf.exp
index 58e1a116bd8..e06ebdaa8b2 100644
--- a/gdb/testsuite/lib/dwarf.exp
+++ b/gdb/testsuite/lib/dwarf.exp
@@ -1335,6 +1335,17 @@  namespace eval Dwarf {
 		_op .2byte $argvec(label)
 	    }
 
+	    DW_OP_entry_value {
+		_get_args $line $opcode body
+		set l1 [new_label "expr_start"]
+		set l2 [new_label "expr_end"]
+		_op .uleb128 "$l2 - $l1" "expression"
+		define_label $l1
+		_location_no_split $argvec(body) $dwarf_version $addr_size \
+		    $offset_size
+		define_label $l2
+	    }
+
 	    DW_OP_implicit_value {
 		set l1 [new_label "value_start"]
 		set l2 [new_label "value_end"]