[lldb] [llvm] [llvm][lldb] Handle oversized LEB128 operands in DWARF expressions (PR #219149)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 27 05:05:31 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: MrEven132
<details>
<summary>Changes</summary>
`DWARFExpression::Operation::extract` used unchecked ULEB128 and SLEB128 reads. When an operand exceeded the decoder's 64-bit representation, `DataExtractor` returned zero without advancing the offset, so later payload bytes could be interpreted as DWARF opcodes.
Use checked LEB128 reads for expression operands. For `DW_OP_constu` and `DW_OP_consts`, consume a complete well-formed arbitrary-width encoding and retain the low address-sized bits required by the DWARF generic type. Other oversized or malformed LEB operands now put the operation in an operand decoding error state instead of producing a partial operation.
LLDB reports the failed opcode and expression offset, while the LLVM compact printer reports a decoding error without changing the existing fallback behavior for unknown vendor operations.
Add LLVM and LLDB unit tests covering oversized unsigned and signed constants, full operand consumption, oversized non-generic ULEB128 and SLEB128 operands, unterminated input, iterator synchronization, and compact printing.
Fixes #<!-- -->202293
---
Full diff: https://github.com/llvm/llvm-project/pull/219149.diff
6 Files Affected:
- (modified) lldb/source/Expression/DWARFExpression.cpp (+5)
- (modified) lldb/unittests/Expression/DWARFExpressionTest.cpp (+72)
- (modified) llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h (+5)
- (modified) llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp (+5)
- (modified) llvm/lib/DebugInfo/DWARF/LowLevel/DWARFExpression.cpp (+75-8)
- (modified) llvm/unittests/DebugInfo/DWARF/DWARFExpressionCompactPrinterTest.cpp (+51)
``````````diff
diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp
index e62b6945dc3ed..cf8e2967cb4f7 100644
--- a/lldb/source/Expression/DWARFExpression.cpp
+++ b/lldb/source/Expression/DWARFExpression.cpp
@@ -1447,6 +1447,11 @@ llvm::Expected<Value> DWARFExpression::Evaluate(
DW_OP_value_to_name(opcode));
}
+ if (op->isOperandError())
+ return llvm::createStringError(
+ "unable to decode operands for %s at offset 0x%" PRIx64,
+ DW_OP_value_to_name(opcode), op_offset);
+
if (std::optional<unsigned> arity = OperationArity(opcode)) {
if (stack.size() < *arity)
return llvm::createStringError(
diff --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp b/lldb/unittests/Expression/DWARFExpressionTest.cpp
index 76d2d4efcf557..02fd560372a3c 100644
--- a/lldb/unittests/Expression/DWARFExpressionTest.cpp
+++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp
@@ -733,6 +733,78 @@ TEST(DWARFExpression, GenericBinaryOpsAllowDifferentSignedness) {
ExpectScalar(4));
}
+TEST(DWARFExpression, OversizedLEB128Constants) {
+ auto evaluate = [](uint8_t opcode, uint8_t low_byte,
+ uint8_t expected_literal) {
+ std::vector<uint8_t> expr = {
+ opcode,
+ // These operands encode positive 2^64 plus the low seven bits.
+ low_byte, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02,
+ static_cast<uint8_t>(DW_OP_lit0 + expected_literal), DW_OP_eq,
+ DW_OP_stack_value};
+ DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle,
+ /*addr_size=*/8);
+ return DWARFExpression::Evaluate(
+ /*exe_ctx=*/nullptr, /*reg_ctx=*/nullptr, /*module_sp=*/{}, extractor,
+ /*unit=*/nullptr, lldb::eRegisterKindLLDB,
+ /*initial_value_ptr=*/nullptr, /*object_address_ptr=*/nullptr);
+ };
+
+ // Generic constants are address-sized, so the high 2^64 bit is discarded.
+ // The comparisons also verify that the complete LEB128 operands are consumed.
+ EXPECT_THAT_EXPECTED(evaluate(DW_OP_constu, 0x80, 0),
+ ExpectScalar(64, 1, false));
+ EXPECT_THAT_EXPECTED(evaluate(DW_OP_consts, 0x80, 0),
+ ExpectScalar(64, 1, false));
+ EXPECT_THAT_EXPECTED(evaluate(DW_OP_constu, 0x85, 5),
+ ExpectScalar(64, 1, false));
+ EXPECT_THAT_EXPECTED(evaluate(DW_OP_consts, 0x85, 5),
+ ExpectScalar(64, 1, false));
+}
+
+TEST(DWARFExpression, RejectsOversizedLEB128Operand) {
+ auto evaluate = [](llvm::ArrayRef<uint8_t> expr) {
+ DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle,
+ /*addr_size=*/8);
+ return DWARFExpression::Evaluate(
+ /*exe_ctx=*/nullptr, /*reg_ctx=*/nullptr, /*module_sp=*/{}, extractor,
+ /*unit=*/nullptr, lldb::eRegisterKindLLDB,
+ /*initial_value_ptr=*/nullptr, /*object_address_ptr=*/nullptr);
+ };
+
+ std::vector<uint8_t> oversized_uleb = {DW_OP_lit0,
+ DW_OP_plus_uconst,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x02,
+ DW_OP_stack_value};
+ EXPECT_THAT_EXPECTED(
+ evaluate(oversized_uleb),
+ llvm::FailedWithMessage(
+ "unable to decode operands for DW_OP_plus_uconst at offset 0x1"));
+
+ const uint8_t oversized_sleb[] = {DW_OP_breg0, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x02, DW_OP_stack_value};
+ EXPECT_THAT_EXPECTED(
+ evaluate(oversized_sleb),
+ llvm::FailedWithMessage(
+ "unable to decode operands for DW_OP_breg0 at offset 0x0"));
+
+ const uint8_t unterminated_constant[] = {DW_OP_constu, 0x80};
+ EXPECT_THAT_EXPECTED(
+ evaluate(unterminated_constant),
+ llvm::FailedWithMessage(
+ "unable to decode operands for DW_OP_constu at offset 0x0"));
+}
+
TEST(DWARFExpression, RelationalOpsProduceGenericResult) {
struct TestCase {
uint8_t opcode;
diff --git a/llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h b/llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h
index 2bf448fb21708..724b3aee5584c 100644
--- a/llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h
+++ b/llvm/include/llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h
@@ -90,6 +90,7 @@ class DWARFExpression {
uint8_t Opcode; ///< The Op Opcode, DW_OP_<something>.
Description Desc;
bool Error = false;
+ bool OperandError = false;
uint64_t EndOffset;
SmallVector<uint64_t> Operands;
SmallVector<uint64_t> OperandEndOffsets;
@@ -109,6 +110,10 @@ class DWARFExpression {
}
uint64_t getEndOffset() const { return EndOffset; }
bool isError() const { return Error; }
+ /// Whether a known operation failed because an operand could not be
+ /// decoded. Unknown operations can be in the general error state without
+ /// having an operand error.
+ bool isOperandError() const { return Error && OperandError; }
private:
LLVM_ABI bool extract(DataExtractor Data, uint8_t AddressSize,
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp b/llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp
index 7a80ae027db1e..782b150e14c42 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFExpressionPrinter.cpp
@@ -297,6 +297,11 @@ static bool printCompactDWARFExpr(
while (I != E) {
const DWARFExpression::Operation &Op = *I;
+ if (Op.isOperandError()) {
+ OS << "<decoding error>";
+ return false;
+ }
+
uint8_t Opcode = Op.getCode();
switch (Opcode) {
case dwarf::DW_OP_regx: {
diff --git a/llvm/lib/DebugInfo/DWARF/LowLevel/DWARFExpression.cpp b/llvm/lib/DebugInfo/DWARF/LowLevel/DWARFExpression.cpp
index a0b5c42fa78cb..97df3bc6d8bb7 100644
--- a/llvm/lib/DebugInfo/DWARF/LowLevel/DWARFExpression.cpp
+++ b/llvm/lib/DebugInfo/DWARF/LowLevel/DWARFExpression.cpp
@@ -7,8 +7,10 @@
//===----------------------------------------------------------------------===//
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h"
+#include "llvm/Support/Error.h"
#include <cassert>
#include <cstdint>
+#include <optional>
#include <vector>
using namespace llvm;
@@ -158,9 +160,54 @@ static Desc getSubOpDesc(unsigned Opcode, unsigned SubOpcode) {
return getDescImpl(Descriptions, SubOpcode);
}
+static std::optional<uint64_t> extractLEB128(const DataExtractor &Data,
+ uint64_t &Offset, bool Signed) {
+ DataExtractor::Cursor Cursor(Offset);
+ uint64_t Value = Signed ? static_cast<uint64_t>(Data.getSLEB128(Cursor))
+ : Data.getULEB128(Cursor);
+ if (!Cursor) {
+ consumeError(Cursor.takeError());
+ return std::nullopt;
+ }
+ Offset = Cursor.tell();
+ return Value;
+}
+
+static std::optional<uint64_t> extractGenericConstant(const DataExtractor &Data,
+ uint64_t &Offset,
+ uint8_t AddressSize,
+ bool Signed) {
+ if (std::optional<uint64_t> Value = extractLEB128(Data, Offset, Signed))
+ return Value;
+
+ // Generic constants are truncated to the target's address-sized generic
+ // type. If the mathematical value does not fit the decoder's 64-bit return
+ // type, scan the complete operand while retaining only those low bits.
+ StringRef Bytes = Data.getData();
+ const unsigned BitSize = AddressSize * 8;
+ unsigned Shift = 0;
+ uint64_t Value = 0;
+ while (Offset < Bytes.size()) {
+ uint8_t Byte = static_cast<uint8_t>(Bytes[Offset++]);
+ if (Shift < BitSize) {
+ unsigned RemainingBits = BitSize - Shift;
+ uint64_t Slice = Byte & 0x7f;
+ if (RemainingBits < 7)
+ Slice &= (uint64_t(1) << RemainingBits) - 1;
+ Value |= Slice << Shift;
+ }
+ if ((Byte & 0x80) == 0)
+ return Value;
+ if (Shift < BitSize)
+ Shift += 7;
+ }
+ return std::nullopt;
+}
+
bool DWARFExpression::Operation::extract(DataExtractor Data,
uint8_t AddressSize, uint64_t Offset,
std::optional<DwarfFormat> Format) {
+ OperandError = false;
EndOffset = Offset;
Opcode = Data.getU8(&Offset);
@@ -173,11 +220,21 @@ bool DWARFExpression::Operation::extract(DataExtractor Data,
for (unsigned Operand = 0; Operand < Desc.Op.size(); ++Operand) {
unsigned Size = Desc.Op[Operand];
unsigned Signed = Size & Operation::SignBit;
+ auto ExtractLEBOperand = [&](bool IsSigned) {
+ std::optional<uint64_t> Value = extractLEB128(Data, Offset, IsSigned);
+ if (!Value) {
+ OperandError = true;
+ return false;
+ }
+ Operands[Operand] = *Value;
+ return true;
+ };
switch (Size & ~Operation::SignBit) {
case Operation::SizeSubOpLEB:
assert(Operand == 0 && "SubOp operand must be the first operand");
- Operands[Operand] = Data.getULEB128(&Offset);
+ if (!ExtractLEBOperand(/*IsSigned=*/false))
+ return false;
Desc = getSubOpDesc(Opcode, Operands[Operand]);
if (Desc.Version == Operation::DwarfNA)
return false;
@@ -214,17 +271,26 @@ bool DWARFExpression::Operation::extract(DataExtractor Data,
Data.getUnsigned(&Offset, dwarf::getDwarfOffsetByteSize(*Format));
break;
case Operation::SizeLEB:
- if (Signed)
- Operands[Operand] = Data.getSLEB128(&Offset);
- else
- Operands[Operand] = Data.getULEB128(&Offset);
+ if (Opcode == DW_OP_constu || Opcode == DW_OP_consts) {
+ if (std::optional<uint64_t> Value = extractGenericConstant(
+ Data, Offset, AddressSize, /*Signed=*/Signed))
+ Operands[Operand] = *Value;
+ else {
+ OperandError = true;
+ return false;
+ }
+ } else if (!ExtractLEBOperand(Signed)) {
+ return false;
+ }
break;
case Operation::BaseTypeRef:
- Operands[Operand] = Data.getULEB128(&Offset);
+ if (!ExtractLEBOperand(/*IsSigned=*/false))
+ return false;
break;
case Operation::NvidiaMuxArg:
assert(Operand == 1);
- Operands[Operand] = Data.getULEB128(&Offset);
+ if (!ExtractLEBOperand(/*IsSigned=*/false))
+ return false;
// The selector names an NVIDIA specific operation, and the number and
// type of the operands that follow it are implied by that operation.
// No NVIDIA operation is known here, so where this operation ends is
@@ -239,7 +305,8 @@ bool DWARFExpression::Operation::extract(DataExtractor Data,
case 1:
case 2:
case 4:
- Operands[Operand] = Data.getULEB128(&Offset);
+ if (!ExtractLEBOperand(/*IsSigned=*/false))
+ return false;
break;
case 3: // global as uint32
Operands[Operand] = Data.getU32(&Offset);
diff --git a/llvm/unittests/DebugInfo/DWARF/DWARFExpressionCompactPrinterTest.cpp b/llvm/unittests/DebugInfo/DWARF/DWARFExpressionCompactPrinterTest.cpp
index ff2f45334dbfc..48978cfb5d5c4 100644
--- a/llvm/unittests/DebugInfo/DWARF/DWARFExpressionCompactPrinterTest.cpp
+++ b/llvm/unittests/DebugInfo/DWARF/DWARFExpressionCompactPrinterTest.cpp
@@ -290,6 +290,57 @@ TEST(NVIDIAMux, Full_DW_OP_LLVM_NVIDIA_mux_MissingSelector) {
EXPECT_EQ(OS.str(), "<decoding error> e9 0d");
}
+TEST(LEB128Operands, OversizedGenericConstants) {
+ constexpr uint8_t Opcodes[] = {DW_OP_constu, DW_OP_consts};
+ for (uint8_t Opcode : Opcodes) {
+ const uint8_t Enc[] = {Opcode,
+ // Positive 2^64 + 5 as either ULEB128 or SLEB128.
+ 0x85, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x02, DW_OP_stack_value};
+ DataExtractor DE(Enc, true);
+ DWARFExpression Expr(DE, 8);
+
+ auto It = Expr.begin();
+ ASSERT_FALSE(It->isError());
+ EXPECT_FALSE(It->isOperandError());
+ EXPECT_EQ(It->getRawOperand(0), 5u);
+ EXPECT_EQ(It->getEndOffset(), 11u);
+ ++It;
+ ASSERT_FALSE(It->isError());
+ EXPECT_EQ(It->getCode(), DW_OP_stack_value);
+ }
+}
+
+TEST(LEB128Operands, OversizedNonGenericOperand) {
+ const uint8_t Enc[] = {DW_OP_plus_uconst,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x80,
+ 0x02,
+ DW_OP_stack_value};
+ DataExtractor DE(Enc, true);
+ DWARFExpression Expr(DE, 8);
+
+ auto It = Expr.begin();
+ EXPECT_TRUE(It->isError());
+ EXPECT_TRUE(It->isOperandError());
+ ++It;
+ EXPECT_EQ(It, Expr.end());
+}
+
+TEST_F(DWARFExpressionCompactPrinterTest,
+ OversizedNonGenericOperandFailsCompactPrinting) {
+ TestExprPrinterFailure(
+ {DW_OP_breg0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x02},
+ "<decoding error>");
+}
+
// NVPTX packs virtual register names into DWARF register numbers, so compact
// printing without a callback must recover the name and return true.
TEST(NVPTXPackedRegister, Compact_DW_OP_regx_NoMRI) {
``````````
</details>
https://github.com/llvm/llvm-project/pull/219149
More information about the llvm-commits
mailing list