llvm / llvm/llvm-project

DW_OP_piece 0 does not consume the location description on the stack

Open
#209,099 1 comment 0 reactions 0 assignees View on GitHub
lldb
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

LLDB returns success for `DW_OP_piece 0` before consuming its preceding source location. The stale source remains on the expression stack and can later override the actual composite result.

[DWARF v5](https://dwarfstd.org/doc/DWARF5.pdf) Section 2.6.1.2 describes composite locations as a sequence of simple location descriptions followed by composition operations. LLVM's [location-description stack extension](https://llvm.org/docs/AMDGPUDwarfExtensionAllowLocationDescriptionOnTheDwarfExpressionStack/AMDGPUDwarfExtensionAllowLocationDescriptionOnTheDwarfExpressionStack.html) similarly defines `DW_OP_piece` as an incremental composite-location operation.

DWARF does not explicitly prohibit a zero-sized `DW_OP_piece`. If LLDB accepts it as a piece that contributes no bytes, it must still treat the operation as a composition boundary rather than leave its source live as an ordinary stack entry.

On x86-64, one compact equivalent case is:

```text
DW_OP_reg0
DW_OP_piece 4
```

and:

```text
DW_OP_reg3
DW_OP_piece 0
DW_OP_reg0
DW_OP_piece 4
```

Here `DW_OP_reg0` is RAX and `DW_OP_reg3` is RBX. The first expression describes the first four bytes of RAX. The second expression adds a zero-sized RBX piece before it. That piece contributes no bytes, so the resulting composite location should still describe the same four-byte RAX fragment.

GDB treats the zero-piece form consistently with the base form. In LLDB, the stale RBX source remains on the expression stack and masks the four-byte composite result assembled from RAX.

## Source Evidence

In [`DWARFExpression.cpp`](https://github.com/llvm/llvm-project/blob/main/lldb/source/Expression/DWARFExpression.cpp), `Evaluate_DW_OP_piece` resets the evaluator's location-description kind and then immediately returns for zero-sized pieces:

```cpp
static llvm::Error Evaluate_DW_OP_piece(EvalContext &eval_ctx,
uint64_t piece_byte_size) {
LocationDescriptionKind piece_locdesc = eval_ctx.loc_desc_kind;
// Reset for the next piece.
eval_ctx.loc_desc_kind = Memory;

if (piece_byte_size == 0)
return llvm::Error::success();

Value curr_piece;

if (eval_ctx.stack.empty()) {
...
} else {
Value curr_piece_source_value(eval_ctx.stack.back());
eval_ctx.stack.pop_back();
UpdateValueTypeFromLocationDescription(eval_ctx, piece_locdesc,
&curr_piece_source_value);
...
}
```

The stack-pop path is below the zero-size early return, so `DW_OP_piece 0` does not consume the current source fragment.

This matters because LLDB returns the composite piece buffer only when the expression stack is empty:

```cpp
if (stack.empty()) {
if (eval_ctx.pieces.GetBuffer().GetByteSize())
return eval_ctx.pieces;
return llvm::createStringError("stack empty after evaluation");
}

UpdateValueTypeFromLocationDescription(eval_ctx, eval_ctx.loc_desc_kind,
&stack.back());
return stack.back();
```

After the final `DW_OP_piece 4` appends the real RAX fragment to `eval_ctx.pieces`, the RBX source left by `DW_OP_piece 0` keeps the stack nonempty. LLDB consequently returns that stale register location instead of returning the completed composite value.

Contributor guide

Open the contributing guide

Research direction

Start in lldb/source/Expression/DWARFExpression.cpp at Evaluate_DW_OP_piece, then trace how eval_ctx.stack and eval_ctx.pieces are handled for the two x86-64 expressions in the report. Reproduce the zero-sized-piece case and verify that the source is consumed and the completed four-byte composite location is returned instead of the stale register location.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
devtools
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.