[Lldb-commits] [lldb] [lldb] Evaluate exprloc array bounds (PR #204119)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Jun 16 04:08:35 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: firmiana402
<details>
<summary>Changes</summary>
LLDB currently parses array subrange properties such as `DW_AT_upper_bound` as unsigned form values in `DWARFASTParser::ParseChildArrayInfo`.
This works for constant forms, but not for expression/block forms. For example, a valid dynamic bound such as:
```text
DW_AT_upper_bound: DW_OP_fbreg -8; DW_OP_deref
```
was treated as the raw block/form value instead of being evaluated as a DWARF expression.
As a result, LLDB currently uses the byte size of the expression block as the array bound instead of evaluating the expression, which leads to an incorrect number of array elements.
# Fix
This patch adds a small helper function for array properties that: keeps the existing handling for constants, evaluates block-form array properties with `DWARFExpression::Evaluate` when an execution context is available, and conservatively treats unavailable or failing evaluations as unavailable.
`DW_AT_count`, `DW_AT_lower_bound`, `DW_AT_upper_bound`, `DW_AT_bit_stride`, and `DW_AT_byte_stride` now use this helper in `ParseChildArrayInfo`.
# Test
Added a shell test with a frame-relative dynamic upper bound:
```text
DW_AT_upper_bound: DW_OP_fbreg -8; DW_OP_deref
```
The test verifies that LLDB evaluates the bound expression and prints all expected array children, rather than using the expression block size as the bound.
---
Full diff: https://github.com/llvm/llvm-project/pull/204119.diff
2 Files Affected:
- (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.cpp (+67-11)
- (added) lldb/test/Shell/SymbolFile/DWARF/x86/array-upper-bound-exprloc.s (+180)
``````````diff
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.cpp
index a8eafc94215dc..fe67dadf1b072 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParser.cpp
@@ -9,10 +9,16 @@
#include "DWARFASTParser.h"
#include "DWARFAttribute.h"
#include "DWARFDIE.h"
+#include "DWARFFormValue.h"
+#include "DWARFUnit.h"
#include "SymbolFileDWARF.h"
+#include "lldb/Core/Value.h"
+#include "lldb/Expression/DWARFExpression.h"
+#include "lldb/Symbol/ObjectFile.h"
#include "lldb/Symbol/SymbolFile.h"
#include "lldb/Target/StackFrame.h"
+#include "lldb/Utility/DataExtractor.h"
#include "lldb/ValueObject/ValueObject.h"
#include <optional>
@@ -21,6 +27,49 @@ using namespace lldb_private;
using namespace lldb_private::plugin::dwarf;
using namespace llvm::dwarf;
+static std::optional<uint64_t>
+EvaluateUnsignedArrayProperty(const DWARFFormValue &form_value,
+ const DWARFDIE &parent_die,
+ const ExecutionContext *exe_ctx) {
+ // Array properties may be encoded directly as constants.
+ if (std::optional<uint64_t> value = form_value.getAsUnsignedConstant())
+ return value;
+ if (std::optional<int64_t> value = form_value.getAsSignedConstant())
+ if (*value >= 0)
+ return *value;
+
+ // Otherwise, evaluate expression blocks in the current execution context.
+ // Without a context there is no frame/register state to use for operations
+ // such as DW_OP_fbreg.
+ if (!DWARFFormValue::IsBlockForm(form_value.Form()) || !exe_ctx)
+ return std::nullopt;
+
+ DWARFUnit *unit = parent_die.GetCU();
+ SymbolFileDWARF *dwarf = parent_die.GetDWARF();
+ if (!unit || !dwarf || !dwarf->GetObjectFile())
+ return std::nullopt;
+
+ lldb::RegisterContextSP reg_ctx_sp;
+ if (lldb::StackFrameSP frame_sp = exe_ctx->GetFrameSP())
+ reg_ctx_sp = frame_sp->GetRegisterContext();
+
+ DataExtractor data(form_value.BlockData(), form_value.Unsigned(),
+ unit->GetByteOrder(), unit->GetAddressByteSize());
+ ExecutionContext exe_ctx_copy(*exe_ctx);
+
+ // Evaluate the DWARF expression to obtain the dynamic property value.
+ llvm::Expected<Value> result = DWARFExpression::Evaluate(
+ &exe_ctx_copy, reg_ctx_sp.get(), dwarf->GetObjectFile()->GetModule(),
+ data, unit, eRegisterKindDWARF,
+ /*initial_value_ptr=*/nullptr, /*object_address_ptr=*/nullptr);
+ if (!result) {
+ llvm::consumeError(result.takeError());
+ return std::nullopt;
+ }
+
+ return result->GetScalar().ULongLong();
+}
+
std::optional<SymbolFile::ArrayInfo>
DWARFASTParser::ParseChildArrayInfo(const DWARFDIE &parent_die,
const ExecutionContext *exe_ctx) {
@@ -38,9 +87,8 @@ DWARFASTParser::ParseChildArrayInfo(const DWARFDIE &parent_die,
continue;
std::optional<uint64_t> num_elements;
- uint64_t lower_bound = 0;
- uint64_t upper_bound = 0;
- bool upper_bound_valid = false;
+ std::optional<uint64_t> lower_bound = 0;
+ std::optional<uint64_t> upper_bound;
for (size_t i = 0; i < attributes.Size(); ++i) {
const dw_attr_t attr = attributes.AttributeAtIndex(i);
DWARFFormValue form_value;
@@ -65,24 +113,32 @@ DWARFASTParser::ParseChildArrayInfo(const DWARFDIE &parent_die,
}
}
} else
- num_elements = form_value.Unsigned();
+ num_elements =
+ EvaluateUnsignedArrayProperty(form_value, parent_die, exe_ctx);
break;
case DW_AT_bit_stride:
- array_info.bit_stride = form_value.Unsigned();
+ if (std::optional<uint64_t> bit_stride =
+ EvaluateUnsignedArrayProperty(form_value, parent_die,
+ exe_ctx))
+ array_info.bit_stride = *bit_stride;
break;
case DW_AT_byte_stride:
- array_info.byte_stride = form_value.Unsigned();
+ if (std::optional<uint64_t> byte_stride =
+ EvaluateUnsignedArrayProperty(form_value, parent_die,
+ exe_ctx))
+ array_info.byte_stride = *byte_stride;
break;
case DW_AT_lower_bound:
- lower_bound = form_value.Unsigned();
+ lower_bound =
+ EvaluateUnsignedArrayProperty(form_value, parent_die, exe_ctx);
break;
case DW_AT_upper_bound:
- upper_bound_valid = true;
- upper_bound = form_value.Unsigned();
+ upper_bound =
+ EvaluateUnsignedArrayProperty(form_value, parent_die, exe_ctx);
break;
default:
@@ -92,8 +148,8 @@ DWARFASTParser::ParseChildArrayInfo(const DWARFDIE &parent_die,
}
if (!num_elements || *num_elements == 0) {
- if (upper_bound_valid && upper_bound >= lower_bound)
- num_elements = upper_bound - lower_bound + 1;
+ if (lower_bound && upper_bound && *upper_bound >= *lower_bound)
+ num_elements = *upper_bound - *lower_bound + 1;
}
array_info.element_orders.push_back(num_elements);
diff --git a/lldb/test/Shell/SymbolFile/DWARF/x86/array-upper-bound-exprloc.s b/lldb/test/Shell/SymbolFile/DWARF/x86/array-upper-bound-exprloc.s
new file mode 100644
index 0000000000000..2582b3c89d72d
--- /dev/null
+++ b/lldb/test/Shell/SymbolFile/DWARF/x86/array-upper-bound-exprloc.s
@@ -0,0 +1,180 @@
+# Test that exprloc-valued subrange bounds are evaluated instead of being
+# treated as static constants.
+
+# REQUIRES: lld, native, target-x86_64, system-linux
+
+# RUN: llvm-mc -triple x86_64-unknown-linux-gnu %s -filetype=obj -o %t.o
+# RUN: ld.lld %t.o -o %t -e main
+# RUN: %lldb %t -b -o "breakpoint set -n after_init" -o run \
+# RUN: -o "frame variable --show-all-children array" -o exit | FileCheck %s
+
+# CHECK-LABEL: frame variable --show-all-children array
+# CHECK: (unsigned int[]) array = ([0] = 1, [1] = 3, [2] = 170, [3] = 187, [4] = 204, [5] = 221)
+
+ .text
+ .globl main
+ .type main, at function
+main:
+.Lfunc_begin:
+ .cfi_startproc
+ pushq %rbp
+ .cfi_def_cfa_offset 16
+ .cfi_offset %rbp, -16
+ movq %rsp, %rbp
+ .cfi_def_cfa_register %rbp
+ subq $64, %rsp
+ movq $5, -8(%rbp)
+ movl $1, -48(%rbp)
+ movl $3, -44(%rbp)
+ movl $170, -40(%rbp)
+ movl $187, -36(%rbp)
+ movl $204, -32(%rbp)
+ movl $221, -28(%rbp)
+ .globl after_init
+after_init:
+ nop
+ xorl %eax, %eax
+ addq $64, %rsp
+ popq %rbp
+ .cfi_def_cfa %rsp, 8
+ retq
+.Lfunc_end:
+ .cfi_endproc
+ .size main, .Lfunc_end-main
+
+ .section .debug_info,"", at progbits
+.Lcu_begin:
+ .4byte .Lcu_end-.Lcu_start
+.Lcu_start:
+ .2byte 4 # DWARF version
+ .4byte .Labbrev_begin
+ .byte 8 # Address size
+
+ .uleb128 1 # DW_TAG_compile_unit
+ .ascii "exprloc-array.c\0" # DW_AT_name
+ .ascii "hand-written DWARF\0" # DW_AT_producer
+ .byte 0x0c # DW_AT_language
+ .quad .Lfunc_begin # DW_AT_low_pc
+ .4byte .Lfunc_end-.Lfunc_begin # DW_AT_high_pc
+
+ .uleb128 2 # DW_TAG_subprogram
+ .ascii "main\0" # DW_AT_name
+ .quad .Lfunc_begin # DW_AT_low_pc
+ .4byte .Lfunc_end-.Lfunc_begin # DW_AT_high_pc
+ .byte .Lframe_base_end-.Lframe_base_begin # DW_AT_frame_base exprloc size
+.Lframe_base_begin:
+ .byte 0x56 # DW_OP_reg6
+.Lframe_base_end:
+
+ .uleb128 3 # DW_TAG_variable
+ .ascii "array\0" # DW_AT_name
+ .4byte .Larray_type-.Lcu_begin # DW_AT_type
+ .uleb128 .Larray_loc_end-.Larray_loc_begin # DW_AT_location exprloc size
+.Larray_loc_begin:
+ .byte 0x91 # DW_OP_fbreg
+ .sleb128 -48 # offset
+.Larray_loc_end:
+
+.Lu32_type:
+ .uleb128 4 # DW_TAG_base_type
+ .ascii "unsigned int\0" # DW_AT_name
+ .byte 4 # DW_AT_byte_size
+ .byte 7 # DW_ATE_unsigned
+
+.Larray_type:
+ .uleb128 5 # DW_TAG_array_type
+ .4byte .Lu32_type-.Lcu_begin # DW_AT_type
+ .uleb128 6 # DW_TAG_subrange_type
+ .4byte .Lu32_type-.Lcu_begin # DW_AT_type
+ .byte 0 # DW_AT_lower_bound
+ .uleb128 .Lupper_bound_end-.Lupper_bound_begin # DW_AT_upper_bound exprloc size
+.Lupper_bound_begin:
+ .byte 0x91 # DW_OP_fbreg
+ .sleb128 -8 # offset
+ .byte 0x06 # DW_OP_deref
+.Lupper_bound_end:
+ .byte 0 # End of array type children
+
+ .byte 0 # End of subprogram children
+ .byte 0 # End of compile unit children
+.Lcu_end:
+
+ .section .debug_abbrev,"", at progbits
+.Labbrev_begin:
+ .uleb128 1 # Abbreviation code
+ .uleb128 0x11 # DW_TAG_compile_unit
+ .byte 1 # DW_CHILDREN_yes
+ .uleb128 0x03 # DW_AT_name
+ .uleb128 0x08 # DW_FORM_string
+ .uleb128 0x25 # DW_AT_producer
+ .uleb128 0x08 # DW_FORM_string
+ .uleb128 0x13 # DW_AT_language
+ .uleb128 0x0b # DW_FORM_data1
+ .uleb128 0x11 # DW_AT_low_pc
+ .uleb128 0x01 # DW_FORM_addr
+ .uleb128 0x12 # DW_AT_high_pc
+ .uleb128 0x06 # DW_FORM_data4
+ .byte 0
+ .byte 0
+
+ .uleb128 2 # Abbreviation code
+ .uleb128 0x2e # DW_TAG_subprogram
+ .byte 1 # DW_CHILDREN_yes
+ .uleb128 0x03 # DW_AT_name
+ .uleb128 0x08 # DW_FORM_string
+ .uleb128 0x11 # DW_AT_low_pc
+ .uleb128 0x01 # DW_FORM_addr
+ .uleb128 0x12 # DW_AT_high_pc
+ .uleb128 0x06 # DW_FORM_data4
+ .uleb128 0x40 # DW_AT_frame_base
+ .uleb128 0x18 # DW_FORM_exprloc
+ .byte 0
+ .byte 0
+
+ .uleb128 3 # Abbreviation code
+ .uleb128 0x34 # DW_TAG_variable
+ .byte 0 # DW_CHILDREN_no
+ .uleb128 0x03 # DW_AT_name
+ .uleb128 0x08 # DW_FORM_string
+ .uleb128 0x49 # DW_AT_type
+ .uleb128 0x13 # DW_FORM_ref4
+ .uleb128 0x02 # DW_AT_location
+ .uleb128 0x18 # DW_FORM_exprloc
+ .byte 0
+ .byte 0
+
+ .uleb128 4 # Abbreviation code
+ .uleb128 0x24 # DW_TAG_base_type
+ .byte 0 # DW_CHILDREN_no
+ .uleb128 0x03 # DW_AT_name
+ .uleb128 0x08 # DW_FORM_string
+ .uleb128 0x0b # DW_AT_byte_size
+ .uleb128 0x0b # DW_FORM_data1
+ .uleb128 0x3e # DW_AT_encoding
+ .uleb128 0x0b # DW_FORM_data1
+ .byte 0
+ .byte 0
+
+ .uleb128 5 # Abbreviation code
+ .uleb128 0x01 # DW_TAG_array_type
+ .byte 1 # DW_CHILDREN_yes
+ .uleb128 0x49 # DW_AT_type
+ .uleb128 0x13 # DW_FORM_ref4
+ .byte 0
+ .byte 0
+
+ .uleb128 6 # Abbreviation code
+ .uleb128 0x21 # DW_TAG_subrange_type
+ .byte 0 # DW_CHILDREN_no
+ .uleb128 0x49 # DW_AT_type
+ .uleb128 0x13 # DW_FORM_ref4
+ .uleb128 0x22 # DW_AT_lower_bound
+ .uleb128 0x0b # DW_FORM_data1
+ .uleb128 0x2f # DW_AT_upper_bound
+ .uleb128 0x18 # DW_FORM_exprloc
+ .byte 0
+ .byte 0
+
+ .byte 0
+
+ .section .note.GNU-stack,"", at progbits
``````````
</details>
https://github.com/llvm/llvm-project/pull/204119
More information about the lldb-commits
mailing list