[PATCH v2] gdb: Check bounds before reading DWARF expression operands

Jielun Wu <[email protected]> Wed, 5 Aug 2026 12:06:40 +0800
Newsgroups gmane.comp.gdb.patches
Message-ID <[email protected]>
Some DWARF expression opcodes read fixed-width operands or block payloads
before checking that the bytes are available in the current expression.
Malformed expressions can consequently consume opcode or unrelated bytes
and fail later with misleading results.

Add helpers that validate byte ranges and fixed-width integer reads.  Use
them for the unchecked operands in
dwarf_expr_context::execute_stack_op, including the size and payload of
DW_OP_const_type.  Validate entry-value blocks before constructing their
nested expression view.

Also guard dwarf_block_to_dwarf_reg_deref when its block ends after the
base register offset, and add direct selftests for the two dwarf-block
register parsers.  Add a DWARF assembler test covering truncated operands
and payloads.

Tested on x86_64-linux.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34239
Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34299
Tested-By: Guinevere Larsen <[email protected]>
Signed-off-by: Jielun Wu <[email protected]>
---
Changes since v1:
- Addressed review comments from Simon and Guinevere.
- Clarified the byte helper comments and the validate-before-extract pattern.
- Changed the fixed-width integer helpers to templated reference helpers.
- Read fixed-width operands directly into the destination object where practical.
- Added direct selftests for dwarf_block_to_dwarf_reg and
  dwarf_block_to_dwarf_reg_deref.
- Used foreach_with_prefix in the DWARF assembler testcase.

Guinevere tested v1 and reported no regressions.  I kept her Tested-By
trailer because v2 addresses review comments without any significant
functional change.

Tested on x86_64-linux:
- make -C build -j32
- make -C build/gdb/testsuite check \
  TESTS="gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp"
- make -C build/gdb/testsuite check TESTS="gdb.gdb/unittest.exp"
- make -C build/gdb/testsuite check \
  TESTS="gdb.gdb/unittest.exp gdb.dwarf2/*.exp"

 gdb/dwarf2/expr.c                             | 224 +++++++++++++-----
 .../gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp  | 174 ++++++++++++++
 2 files changed, 345 insertions(+), 53 deletions(-)
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp

diff --git a/gdb/dwarf2/expr.c b/gdb/dwarf2/expr.c
index 3a6b8f58199..de4cc8ffed6 100644
--- a/gdb/dwarf2/expr.c
+++ b/gdb/dwarf2/expr.c
@@ -34,6 +34,7 @@
 #include "gdbarch.h"
 #include "objfiles.h"
 #include "extract-store-integer.h"
+#include "gdbsupport/selftest.h"
 
 /* This holds gdbarch-specific types used by the DWARF expression
    evaluator.  See comments in execute_stack_op.  */
@@ -1370,6 +1371,57 @@ safe_skip_leb128 (const gdb_byte *buf, const gdb_byte *buf_end)
     error (_("DWARF expression error: ran off end of buffer reading leb128 value"));
   return buf;
 }
+
+/* Return BUF advanced by BYTES bytes.  Throw an error if fewer than BYTES
+   bytes remain before BUF_END.  */
+
+static const gdb_byte *
+safe_skip_bytes (const gdb_byte *buf, const gdb_byte *buf_end,
+		 ULONGEST bytes)
+{
+  gdb_assert (buf <= buf_end);
+  if (bytes > (ULONGEST) (buf_end - buf))
+    error (_("DWARF expression error: ran off end of buffer reading bytes"));
+  return buf + bytes;
+}
+
+/* Read a LEN-byte unsigned integer at BUF, assign it to RESULT, and return
+   BUF advanced by LEN bytes.  "Unsigned" describes how the bytes are decoded;
+   RESULT's type is chosen by the caller.  Throw an error if fewer than LEN
+   bytes remain before BUF_END.  */
+
+template<typename T>
+static const gdb_byte *
+safe_read_unsigned_integer (const gdb_byte *buf, const gdb_byte *buf_end,
+			    int len, bfd_endian byte_order, T &result)
+{
+  gdb_assert (len >= 0);
+  gdb_assert ((size_t) len <= sizeof (T));
+
+  /* Validate the source range before extract_unsigned_integer reads it.  */
+  const gdb_byte *next = safe_skip_bytes (buf, buf_end, len);
+  result = static_cast<T> (extract_unsigned_integer (buf, len, byte_order));
+  return next;
+}
+
+/* Read a LEN-byte signed integer at BUF, assign it to RESULT, and return BUF
+   advanced by LEN bytes.  "Signed" describes how the bytes are decoded;
+   RESULT's type is chosen by the caller.  Throw an error if fewer than LEN
+   bytes remain before BUF_END.  */
+
+template<typename T>
+static const gdb_byte *
+safe_read_signed_integer (const gdb_byte *buf, const gdb_byte *buf_end,
+			  int len, bfd_endian byte_order, T &result)
+{
+  gdb_assert (len >= 0);
+  gdb_assert ((size_t) len <= sizeof (T));
+
+  /* Validate the source range before extract_signed_integer reads it.  */
+  const gdb_byte *next = safe_skip_bytes (buf, buf_end, len);
+  result = static_cast<T> (extract_signed_integer (buf, len, byte_order));
+  return next;
+}
 
 
 /* Check that the current operator is either at the end of an
@@ -1488,6 +1540,9 @@ dwarf_block_to_dwarf_reg_deref (gdb::array_view<const gdb_byte> block,
     return -1;
   if (offset != 0)
     return -1;
+  gdb_assert (buf <= buf_end);
+  if (buf == buf_end)
+    return -1;
 
   if (*buf == DW_OP_deref)
     {
@@ -1688,9 +1743,9 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	  break;
 
 	case DW_OP_addr:
-	  result = extract_unsigned_integer (op_ptr,
-					     this->m_addr_size, byte_order);
-	  op_ptr += this->m_addr_size;
+	  op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
+					       this->m_addr_size,
+					       byte_order, result);
 	  /* Some versions of GCC emit DW_OP_addr before
 	     DW_OP_GNU_push_tls_address.  In this case the value is an
 	     index, not an address.  We don't support things like
@@ -1723,44 +1778,44 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	  break;
 
 	case DW_OP_const1u:
-	  result = extract_unsigned_integer (op_ptr, 1, byte_order);
+	  op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+					       byte_order, result);
 	  result_val = value_from_ulongest (address_type, result);
-	  op_ptr += 1;
 	  break;
 	case DW_OP_const1s:
-	  result = extract_signed_integer (op_ptr, 1, byte_order);
+	  op_ptr = safe_read_signed_integer (op_ptr, op_end, 1,
+					     byte_order, result);
 	  result_val = value_from_ulongest (address_type, result);
-	  op_ptr += 1;
 	  break;
 	case DW_OP_const2u:
-	  result = extract_unsigned_integer (op_ptr, 2, byte_order);
+	  op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 2,
+					       byte_order, result);
 	  result_val = value_from_ulongest (address_type, result);
-	  op_ptr += 2;
 	  break;
 	case DW_OP_const2s:
-	  result = extract_signed_integer (op_ptr, 2, byte_order);
+	  op_ptr = safe_read_signed_integer (op_ptr, op_end, 2,
+					     byte_order, result);
 	  result_val = value_from_ulongest (address_type, result);
-	  op_ptr += 2;
 	  break;
 	case DW_OP_const4u:
-	  result = extract_unsigned_integer (op_ptr, 4, byte_order);
+	  op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
+					       byte_order, result);
 	  result_val = value_from_ulongest (address_type, result);
-	  op_ptr += 4;
 	  break;
 	case DW_OP_const4s:
-	  result = extract_signed_integer (op_ptr, 4, byte_order);
+	  op_ptr = safe_read_signed_integer (op_ptr, op_end, 4,
+					     byte_order, result);
 	  result_val = value_from_ulongest (address_type, result);
-	  op_ptr += 4;
 	  break;
 	case DW_OP_const8u:
-	  result = extract_unsigned_integer (op_ptr, 8, byte_order);
+	  op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 8,
+					       byte_order, result);
 	  result_val = value_from_ulongest (address_type, result);
-	  op_ptr += 8;
 	  break;
 	case DW_OP_const8s:
-	  result = extract_signed_integer (op_ptr, 8, byte_order);
+	  op_ptr = safe_read_signed_integer (op_ptr, op_end, 8,
+					     byte_order, result);
 	  result_val = value_from_ulongest (address_type, result);
-	  op_ptr += 8;
 	  break;
 	case DW_OP_constu:
 	  op_ptr = safe_read_uleb128 (op_ptr, op_end, &uoffset);
@@ -1836,12 +1891,10 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	    uint64_t len;
 
 	    op_ptr = safe_read_uleb128 (op_ptr, op_end, &len);
-	    if (op_ptr + len > op_end)
-	      error (_("DW_OP_implicit_value: too few bytes available."));
 	    this->m_len = len;
 	    this->m_data = op_ptr;
 	    this->m_location = DWARF_VALUE_LITERAL;
-	    op_ptr += len;
+	    op_ptr = safe_skip_bytes (op_ptr, op_end, len);
 	    dwarf_expr_require_composition (op_ptr, op_end,
 					    "DW_OP_implicit_value");
 	  }
@@ -1861,9 +1914,9 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	    int ref_addr_size = this->m_per_cu->ref_addr_size ();
 
 	    /* The referred-to DIE of sect_offset kind.  */
-	    this->m_len = extract_unsigned_integer (op_ptr, ref_addr_size,
-						  byte_order);
-	    op_ptr += ref_addr_size;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
+						 ref_addr_size, byte_order,
+						 this->m_len);
 
 	    /* The byte offset into the data.  */
 	    op_ptr = safe_read_sleb128 (op_ptr, op_end, &len);
@@ -1972,7 +2025,8 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	  goto no_push;
 
 	case DW_OP_pick:
-	  offset = *op_ptr++;
+	  op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+					       byte_order, offset);
 	  result_val = fetch (offset);
 	  in_stack_memory = fetch_in_stack_memory (offset);
 	  break;
@@ -2016,7 +2070,16 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	case DW_OP_deref_type:
 	case DW_OP_GNU_deref_type:
 	  {
-	    int addr_size = (op == DW_OP_deref ? this->m_addr_size : *op_ptr++);
+	    int addr_size;
+
+	    if (op == DW_OP_deref)
+	      addr_size = this->m_addr_size;
+	    else
+	      {
+		op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+						     byte_order, addr_size);
+	      }
+
 	    CORE_ADDR addr = fetch_address (0);
 	    struct type *type;
 
@@ -2249,8 +2312,8 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	  break;
 
 	case DW_OP_skip:
-	  offset = extract_signed_integer (op_ptr, 2, byte_order);
-	  op_ptr += 2;
+	  op_ptr = safe_read_signed_integer (op_ptr, op_end, 2, byte_order,
+					     offset);
 	  op_ptr += offset;
 	  goto no_push;
 
@@ -2258,8 +2321,8 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	  {
 	    struct value *val;
 
-	    offset = extract_signed_integer (op_ptr, 2, byte_order);
-	    op_ptr += 2;
+	    op_ptr = safe_read_signed_integer (op_ptr, op_end, 2, byte_order,
+					       offset);
 	    val = fetch (0);
 	    dwarf_require_integral (val->type ());
 	    if (value_as_long (val) != 0)
@@ -2313,18 +2376,18 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 
 	case DW_OP_call2:
 	  {
-	    cu_offset cu_off
-	      = (cu_offset) extract_unsigned_integer (op_ptr, 2, byte_order);
-	    op_ptr += 2;
+	    cu_offset cu_off;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 2,
+						 byte_order, cu_off);
 	    this->dwarf_call (cu_off);
 	  }
 	  goto no_push;
 
 	case DW_OP_call4:
 	  {
-	    cu_offset cu_off
-	      = (cu_offset) extract_unsigned_integer (op_ptr, 4, byte_order);
-	    op_ptr += 4;
+	    cu_offset cu_off;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
+						 byte_order, cu_off);
 	    this->dwarf_call (cu_off);
 	  }
 	  goto no_push;
@@ -2334,11 +2397,10 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	    ensure_have_per_cu (this->m_per_cu, "DW_OP_GNU_variable_value");
 	    int ref_addr_size = this->m_per_cu->ref_addr_size ();
 
-	    sect_offset sect_off
-	      = (sect_offset) extract_unsigned_integer (op_ptr,
-							ref_addr_size,
-							byte_order);
-	    op_ptr += ref_addr_size;
+	    sect_offset sect_off;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end,
+						 ref_addr_size, byte_order,
+						 sect_off);
 	    result_val = sect_variable_value (sect_off, this->m_per_cu,
 					      this->m_per_objfile);
 	    result_val = value_cast (address_type, result_val);
@@ -2353,15 +2415,13 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	    union call_site_parameter_u kind_u;
 
 	    op_ptr = safe_read_uleb128 (op_ptr, op_end, &len);
-	    if (op_ptr + len > op_end)
-	      error (_("DW_OP_entry_value: too few bytes available."));
+	    const gdb_byte *expr_ptr = op_ptr;
+	    op_ptr = safe_skip_bytes (op_ptr, op_end, len);
 
-	    auto entry_value_expr = gdb::make_array_view (op_ptr, len);
+	    auto entry_value_expr = gdb::make_array_view (expr_ptr, len);
 	    kind_u.dwarf_reg = dwarf_block_to_dwarf_reg (entry_value_expr);
 	    if (kind_u.dwarf_reg != -1)
 	      {
-		op_ptr += len;
-
 		if (trivial_entry_value (this->m_frame))
 		  {
 		    /* We can assume that DW_OP_entry_value (expr) == expr.
@@ -2384,7 +2444,6 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	      {
 		if (deref_size == -1)
 		  deref_size = this->m_addr_size;
-		op_ptr += len;
 
 		if (trivial_entry_value (this->m_frame))
 		  {
@@ -2410,9 +2469,9 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	  {
 	    union call_site_parameter_u kind_u;
 
-	    kind_u.param_cu_off
-	      = (cu_offset) extract_unsigned_integer (op_ptr, 4, byte_order);
-	    op_ptr += 4;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 4,
+						 byte_order,
+						 kind_u.param_cu_off);
 	    this->push_dwarf_reg_entry_value (CALL_SITE_PARAMETER_PARAM_OFFSET,
 					      kind_u,
 					      -1 /* deref_size */);
@@ -2429,9 +2488,10 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
 	    op_ptr = safe_read_uleb128 (op_ptr, op_end, &uoffset);
 	    cu_offset type_die_cu_off = (cu_offset) uoffset;
 
-	    n = *op_ptr++;
+	    op_ptr = safe_read_unsigned_integer (op_ptr, op_end, 1,
+						 byte_order, n);
 	    data = op_ptr;
-	    op_ptr += n;
+	    op_ptr = safe_skip_bytes (op_ptr, op_end, n);
 
 	    type = get_base_type (type_die_cu_off);
 
@@ -2524,3 +2584,61 @@ dwarf_expr_context::execute_stack_op (gdb::array_view<const gdb_byte> expr)
   this->m_recursion_depth--;
   gdb_assert (this->m_recursion_depth >= 0);
 }
+
+#if GDB_SELF_TEST
+namespace selftests {
+
+static void
+test_dwarf_block_to_dwarf_reg ()
+{
+  const gdb_byte reg0[] = { DW_OP_reg0 };
+  SELF_CHECK (dwarf_block_to_dwarf_reg (reg0) == 0);
+
+  const gdb_byte regx[] = { DW_OP_regx, 32 };
+  SELF_CHECK (dwarf_block_to_dwarf_reg (regx) == 32);
+
+  const gdb_byte trailing_op[] = { DW_OP_reg0, DW_OP_stack_value };
+  SELF_CHECK (dwarf_block_to_dwarf_reg (trailing_op) == -1);
+}
+
+static void
+test_dwarf_block_to_dwarf_reg_deref ()
+{
+  CORE_ADDR deref_size;
+
+  const gdb_byte deref[] = { DW_OP_breg0, 0, DW_OP_deref };
+  SELF_CHECK (dwarf_block_to_dwarf_reg_deref (deref, &deref_size) == 0);
+  SELF_CHECK (deref_size == (CORE_ADDR) -1);
+
+  const gdb_byte deref_size_4[]
+    = { DW_OP_bregx, 32, 0, DW_OP_deref_size, 4 };
+  SELF_CHECK (dwarf_block_to_dwarf_reg_deref (deref_size_4, &deref_size)
+	      == 32);
+  SELF_CHECK (deref_size == 4);
+
+  const gdb_byte missing_deref[] = { DW_OP_breg0, 0 };
+  SELF_CHECK (dwarf_block_to_dwarf_reg_deref (missing_deref, &deref_size)
+	      == -1);
+
+  const gdb_byte missing_size[]
+    = { DW_OP_breg0, 0, DW_OP_deref_size };
+  SELF_CHECK (dwarf_block_to_dwarf_reg_deref (missing_size, &deref_size)
+	      == -1);
+
+  const gdb_byte nonzero_offset[] = { DW_OP_breg0, 1, DW_OP_deref };
+  SELF_CHECK (dwarf_block_to_dwarf_reg_deref (nonzero_offset, &deref_size)
+	      == -1);
+}
+
+} /* namespace selftests.  */
+#endif /* GDB_SELF_TEST.  */
+
+INIT_GDB_FILE (dwarf2expr)
+{
+#if GDB_SELF_TEST
+  selftests::register_test ("dwarf_block_to_dwarf_reg",
+			    selftests::test_dwarf_block_to_dwarf_reg);
+  selftests::register_test ("dwarf_block_to_dwarf_reg_deref",
+			    selftests::test_dwarf_block_to_dwarf_reg_deref);
+#endif /* GDB_SELF_TEST.  */
+}
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp b/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
new file mode 100644
index 00000000000..27e9033a2c3
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-bad-dwarf-expr-bounds.exp
@@ -0,0 +1,174 @@
+# 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 malformed DWARF expressions whose opcodes have truncated operands.
+
+load_lib dwarf.exp
+
+require dwarf2_support
+
+standard_testfile main.c -dw.S
+
+set asm_file [standard_output_file $srcfile2]
+
+Dwarf::assemble $asm_file {
+    global srcfile
+
+    declare_labels int_label
+
+    cu {label cu_label version 5} {
+	DW_TAG_compile_unit {
+	    DW_AT_name $srcfile
+	    DW_AT_language @DW_LANG_C
+	} {
+	    int_label: DW_TAG_base_type {
+		DW_AT_name "int"
+		DW_AT_encoding @DW_ATE_signed
+		DW_AT_byte_size 4 DW_FORM_sdata
+	    }
+
+	    foreach {var op} {
+		truncated_addr DW_OP_addr
+		truncated_const1u DW_OP_const1u
+		truncated_const1s DW_OP_const1s
+		truncated_const2u DW_OP_const2u
+		truncated_const2s DW_OP_const2s
+		truncated_const4u DW_OP_const4u
+		truncated_const4s DW_OP_const4s
+		truncated_const8u DW_OP_const8u
+		truncated_const8s DW_OP_const8s
+		truncated_implicit_pointer DW_OP_implicit_pointer
+		truncated_gnu_implicit_pointer DW_OP_GNU_implicit_pointer
+		truncated_pick DW_OP_pick
+		truncated_deref_size DW_OP_deref_size
+		truncated_deref_type DW_OP_deref_type
+		truncated_gnu_deref_type DW_OP_GNU_deref_type
+		truncated_skip DW_OP_skip
+		truncated_bra DW_OP_bra
+		truncated_parameter_ref DW_OP_GNU_parameter_ref
+	    } {
+		DW_TAG_variable {
+		    DW_AT_name $var
+		    DW_AT_type :$int_label
+		    DW_AT_location {
+			variable _constants
+
+			_op .byte $_constants($op) $op
+		    } SPECIAL_expr
+		}
+	    }
+
+	    DW_TAG_variable {
+		DW_AT_name "truncated_implicit_value"
+		DW_AT_type :$int_label
+		DW_AT_location {
+		    variable _constants
+
+		    _op .byte $_constants(DW_OP_implicit_value) \
+			DW_OP_implicit_value
+		    _op .uleb128 4 "implicit value length"
+		    _op .2byte 0 "truncated implicit value bytes"
+		} SPECIAL_expr
+	    }
+
+	    foreach op {DW_OP_entry_value DW_OP_GNU_entry_value} {
+		set var "truncated_[string tolower $op]"
+		DW_TAG_variable {
+		    DW_AT_name $var
+		    DW_AT_type :$int_label
+		    DW_AT_location {
+			variable _constants
+
+			_op .byte $_constants($op) $op
+			_op .uleb128 4 "entry value expression length"
+			_op .2byte 0 "truncated entry value expression"
+		    } SPECIAL_expr
+		}
+	    }
+
+	    foreach op {DW_OP_const_type DW_OP_GNU_const_type} {
+		set op_name [string tolower $op]
+
+		DW_TAG_variable {
+		    DW_AT_name "truncated_${op_name}_size"
+		    DW_AT_type :$int_label
+		    DW_AT_location {
+			variable _constants
+			variable _cu_label
+
+			_op .byte $_constants($op) $op
+			_op .uleb128 "$int_label - $_cu_label" \
+			    "type DIE offset"
+		    } SPECIAL_expr
+		}
+
+		DW_TAG_variable {
+		    DW_AT_name "truncated_${op_name}_payload"
+		    DW_AT_type :$int_label
+		    DW_AT_location {
+			variable _constants
+			variable _cu_label
+
+			_op .byte $_constants($op) $op
+			_op .uleb128 "$int_label - $_cu_label" \
+			    "type DIE offset"
+			_op .byte 4 "constant block length"
+			_op .2byte 0 "truncated constant block"
+		    } SPECIAL_expr
+		}
+	    }
+	}
+    }
+}
+
+if {[prepare_for_testing "failed to prepare" ${testfile} \
+	 [list $srcfile $asm_file] nodebug]} {
+    return
+}
+
+if {![runto_main]} {
+    return
+}
+
+foreach_with_prefix var {
+    truncated_addr
+    truncated_const1u
+    truncated_const1s
+    truncated_const2u
+    truncated_const2s
+    truncated_const4u
+    truncated_const4s
+    truncated_const8u
+    truncated_const8s
+    truncated_implicit_value
+    truncated_implicit_pointer
+    truncated_gnu_implicit_pointer
+    truncated_pick
+    truncated_deref_size
+    truncated_deref_type
+    truncated_gnu_deref_type
+    truncated_skip
+    truncated_bra
+    truncated_dw_op_entry_value
+    truncated_dw_op_gnu_entry_value
+    truncated_parameter_ref
+    truncated_dw_op_const_type_size
+    truncated_dw_op_const_type_payload
+    truncated_dw_op_gnu_const_type_size
+    truncated_dw_op_gnu_const_type_payload
+} {
+    gdb_test "print $var" \
+	"DWARF expression error: ran off end of buffer reading bytes"
+}
-- 
2.34.1