Skip to content

Commit c455c4e

Browse files
authored
[lldb] Implement DW_CFA_val_offset and DW_CFA_val_offset_sf (#150732)
The test for this is artificial as I'm not aware of any upstream targets that use DW_CFA_val_offset RegisterContextUnwind::ReadFrameAddress now reports how it's attempting to obtain the CFA unless all success/failure cases emit logs that clearly identify the method it was attempting. Previously several of the existing failure paths emit no message or a message that's indistinguishable from those on other paths.
1 parent 258997c commit c455c4e

File tree

6 files changed

+295
-3
lines changed

6 files changed

+295
-3
lines changed

lldb/include/lldb/Target/UnwindLLDB.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class UnwindLLDB : public lldb_private::Unwind {
4949
// target mem (target_memory_location)
5050
eRegisterInRegister, // register is available in a (possible other)
5151
// register (register_number)
52+
eRegisterIsRegisterPlusOffset, // register is available in a (possible
53+
// other) register (register_number) with
54+
// an offset applied
5255
eRegisterSavedAtHostMemoryLocation, // register is saved at a word in
5356
// lldb's address space
5457
eRegisterValueInferred, // register val was computed (and is in
@@ -64,6 +67,11 @@ class UnwindLLDB : public lldb_private::Unwind {
6467
void *host_memory_location;
6568
uint64_t inferred_value; // eRegisterValueInferred - e.g. stack pointer ==
6669
// cfa + offset
70+
struct {
71+
uint32_t
72+
register_number; // in eRegisterKindLLDB register numbering system
73+
uint64_t offset;
74+
} reg_plus_offset;
6775
} location;
6876
};
6977

lldb/source/Symbol/DWARFCallFrameInfo.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,32 @@ DWARFCallFrameInfo::ParseFDE(dw_offset_t dwarf_offset,
766766
break;
767767
}
768768

769-
case DW_CFA_val_offset: // 0x14
770-
case DW_CFA_val_offset_sf: // 0x15
769+
case DW_CFA_val_offset: { // 0x14
770+
// takes two unsigned LEB128 operands representing a register number
771+
// and a factored offset. The required action is to change the rule
772+
// for the register indicated by the register number to be a
773+
// val_offset(N) rule where the value of N is factored_offset*
774+
// data_alignment_factor
775+
uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset);
776+
int32_t op_offset =
777+
(int32_t)m_cfi_data.GetULEB128(&offset) * data_align;
778+
reg_location.SetIsCFAPlusOffset(op_offset);
779+
row.SetRegisterInfo(reg_num, reg_location);
780+
break;
781+
}
782+
case DW_CFA_val_offset_sf: { // 0x15
783+
// takes two operands: an unsigned LEB128 value representing a
784+
// register number and a signed LEB128 factored offset. This
785+
// instruction is identical to DW_CFA_val_offset except that the
786+
// second operand is signed and factored. The resulting offset is
787+
// factored_offset* data_alignment_factor.
788+
uint32_t reg_num = (uint32_t)m_cfi_data.GetULEB128(&offset);
789+
int32_t op_offset =
790+
(int32_t)m_cfi_data.GetSLEB128(&offset) * data_align;
791+
reg_location.SetIsCFAPlusOffset(op_offset);
792+
row.SetRegisterInfo(reg_num, reg_location);
793+
break;
794+
}
771795
default:
772796
break;
773797
}

lldb/source/Target/RegisterContextUnwind.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1118,6 +1118,27 @@ bool RegisterContextUnwind::ReadRegisterValueFromRegisterLocation(
11181118
success = GetNextFrame()->ReadRegister(other_reg_info, value);
11191119
}
11201120
} break;
1121+
case UnwindLLDB::ConcreteRegisterLocation::eRegisterIsRegisterPlusOffset: {
1122+
auto regnum = regloc.location.reg_plus_offset.register_number;
1123+
const RegisterInfo *other_reg_info =
1124+
GetRegisterInfoAtIndex(regloc.location.reg_plus_offset.register_number);
1125+
1126+
if (!other_reg_info)
1127+
return false;
1128+
1129+
if (IsFrameZero()) {
1130+
success =
1131+
m_thread.GetRegisterContext()->ReadRegister(other_reg_info, value);
1132+
} else {
1133+
success = GetNextFrame()->ReadRegister(other_reg_info, value);
1134+
}
1135+
if (success) {
1136+
UnwindLogMsg("read (%d)'s location", regnum);
1137+
value = value.GetAsUInt64(~0ull, &success) +
1138+
regloc.location.reg_plus_offset.offset;
1139+
UnwindLogMsg("success %s", success ? "yes" : "no");
1140+
}
1141+
} break;
11211142
case UnwindLLDB::ConcreteRegisterLocation::eRegisterValueInferred:
11221143
success =
11231144
value.SetUInt(regloc.location.inferred_value, reg_info->byte_size);
@@ -1164,6 +1185,7 @@ bool RegisterContextUnwind::WriteRegisterValueToRegisterLocation(
11641185
success = GetNextFrame()->WriteRegister(other_reg_info, value);
11651186
}
11661187
} break;
1188+
case UnwindLLDB::ConcreteRegisterLocation::eRegisterIsRegisterPlusOffset:
11671189
case UnwindLLDB::ConcreteRegisterLocation::eRegisterValueInferred:
11681190
case UnwindLLDB::ConcreteRegisterLocation::eRegisterNotSaved:
11691191
break;
@@ -1959,6 +1981,7 @@ bool RegisterContextUnwind::ReadFrameAddress(
19591981

19601982
switch (fa.GetValueType()) {
19611983
case UnwindPlan::Row::FAValue::isRegisterDereferenced: {
1984+
UnwindLogMsg("CFA value via dereferencing reg");
19621985
RegisterNumber cfa_reg(m_thread, row_register_kind,
19631986
fa.GetRegisterNumber());
19641987
if (ReadGPRValue(cfa_reg, cfa_reg_contents)) {
@@ -1991,6 +2014,7 @@ bool RegisterContextUnwind::ReadFrameAddress(
19912014
break;
19922015
}
19932016
case UnwindPlan::Row::FAValue::isRegisterPlusOffset: {
2017+
UnwindLogMsg("CFA value via register plus offset");
19942018
RegisterNumber cfa_reg(m_thread, row_register_kind,
19952019
fa.GetRegisterNumber());
19962020
if (ReadGPRValue(cfa_reg, cfa_reg_contents)) {
@@ -2012,10 +2036,13 @@ bool RegisterContextUnwind::ReadFrameAddress(
20122036
address, cfa_reg.GetName(), cfa_reg.GetAsKind(eRegisterKindLLDB),
20132037
cfa_reg_contents, fa.GetOffset());
20142038
return true;
2015-
}
2039+
} else
2040+
UnwindLogMsg("unable to read CFA register %s (%d)", cfa_reg.GetName(),
2041+
cfa_reg.GetAsKind(eRegisterKindLLDB));
20162042
break;
20172043
}
20182044
case UnwindPlan::Row::FAValue::isDWARFExpression: {
2045+
UnwindLogMsg("CFA value via DWARF expression");
20192046
ExecutionContext exe_ctx(m_thread.shared_from_this());
20202047
Process *process = exe_ctx.GetProcessPtr();
20212048
DataExtractor dwarfdata(fa.GetDWARFExpressionBytes(),
@@ -2042,6 +2069,7 @@ bool RegisterContextUnwind::ReadFrameAddress(
20422069
break;
20432070
}
20442071
case UnwindPlan::Row::FAValue::isRaSearch: {
2072+
UnwindLogMsg("CFA value via heuristic search");
20452073
Process &process = *m_thread.GetProcess();
20462074
lldb::addr_t return_address_hint = GetReturnAddressHint(fa.GetOffset());
20472075
if (return_address_hint == LLDB_INVALID_ADDRESS)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
.text
2+
.globl bar
3+
bar:
4+
.cfi_startproc
5+
leal (%edi, %edi), %eax
6+
ret
7+
.cfi_endproc
8+
9+
.globl foo
10+
# This function uses a non-standard calling convention (return address
11+
# needs to be adjusted) to force lldb to use eh_frame/debug_frame
12+
# instead of reading the code directly.
13+
foo:
14+
.cfi_startproc
15+
.cfi_escape 0x16, 0x10, 0x06, 0x38, 0x1c, 0x06, 0x08, 0x47, 0x1c
16+
# Clobber r12 and record that it's reconstructable from CFA
17+
.cfi_val_offset %r12, 32
18+
movq $0x456, %r12
19+
call bar
20+
addl $1, %eax
21+
# Reconstruct %r12
22+
movq %rsp, %r12
23+
addq %r12, 8
24+
popq %rdi
25+
subq $0x47, %rdi
26+
jmp *%rdi # Return
27+
.cfi_endproc
28+
29+
.globl asm_main
30+
asm_main:
31+
.cfi_startproc
32+
pushq %rbp
33+
.cfi_def_cfa_offset 16
34+
.cfi_offset %rbp, -16
35+
movq %rsp, %rbp
36+
movq %rsp, %r12
37+
addq $32, %r12
38+
.cfi_def_cfa_register %rbp
39+
movl $47, %edi
40+
41+
# Non-standard calling convention. The real return address must be
42+
# decremented by 0x47.
43+
leaq 0x47+1f(%rip), %rax
44+
pushq %rax
45+
jmp foo # call
46+
1:
47+
popq %rbp
48+
.cfi_def_cfa %rsp, 8
49+
ret
50+
.cfi_endproc
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Test handing of the dwarf val_offset() rule which can be used to reconstruct
2+
# the value of a register that is neither in a live register or saved on the
3+
# stack but is computable with CFA + offset.
4+
5+
# UNSUPPORTED: system-windows, ld_new-bug
6+
# REQUIRES: target-x86_64, native
7+
8+
# RUN: %clang_host %p/Inputs/call-asm.c %p/Inputs/eh-frame-dwarf-unwind-val-offset.s -o %t
9+
# RUN: %lldb %t -s %s -o exit | FileCheck %s
10+
11+
breakpoint set -n asm_main
12+
# CHECK: Breakpoint 1: where = {{.*}}`asm_main
13+
14+
breakpoint set -n bar
15+
# CHECK: Breakpoint 2: where = {{.*}}`bar
16+
17+
process launch
18+
# CHECK: stop reason = breakpoint 1.1
19+
20+
stepi
21+
stepi
22+
stepi
23+
register read -G x r12
24+
# CHECK: r12 = 0x[[#%.16x,R12:]]{{$}}
25+
26+
continue
27+
# CHECK: stop reason = breakpoint 2.1
28+
29+
thread backtrace
30+
# CHECK: frame #0: {{.*}}`bar
31+
# CHECK: frame #1: {{.*}}`foo + 12
32+
# CHECK: frame #2: {{.*}}`asm_main + 29
33+
34+
target modules show-unwind -n bar
35+
# CHECK: eh_frame UnwindPlan:
36+
# CHECK: row[0]: 0: CFA=rsp +8 => rip=[CFA-8]
37+
38+
target modules show-unwind -n foo
39+
# CHECK: eh_frame UnwindPlan:
40+
# CHECK: row[0]: 0: CFA=rsp +8 => r12=CFA+32 rip=DW_OP_lit8, DW_OP_minus, DW_OP_deref, DW_OP_const1u 0x47, DW_OP_minus
41+
42+
target modules show-unwind -n asm_main
43+
# CHECK: eh_frame UnwindPlan:
44+
# CHECK: row[0]: 0: CFA=rsp +8 => rip=[CFA-8]
45+
# CHECK: row[1]: 1: CFA=rsp+16 => rbp=[CFA-16] rip=[CFA-8]
46+
# CHECK: row[2]: 11: CFA=rbp+16 => rbp=[CFA-16] rip=[CFA-8]
47+
# CHECK: row[3]: 30: CFA=rsp +8 => rbp=[CFA-16] rip=[CFA-8]
48+
49+
register read -G x r12
50+
# CHECK: r12 = 0x0000000000000456
51+
52+
frame select 1
53+
register read -G x r12
54+
# CHECK: r12 = 0x0000000000000456
55+
56+
frame select 2
57+
register read -G x r12
58+
# CHECK: r12 = 0x[[#R12 + 32]]

lldb/unittests/Symbol/TestDWARFCallFrameInfo.cpp

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class DWARFCallFrameInfoTest : public testing::Test {
3939

4040
protected:
4141
void TestBasic(DWARFCallFrameInfo::Type type, llvm::StringRef symbol);
42+
void TestValOffset(DWARFCallFrameInfo::Type type, llvm::StringRef symbol);
4243
};
4344

4445
namespace lldb_private {
@@ -256,3 +257,126 @@ TEST_F(DWARFCallFrameInfoTest, Basic_dwarf4) {
256257
TEST_F(DWARFCallFrameInfoTest, Basic_eh) {
257258
TestBasic(DWARFCallFrameInfo::EH, "eh_frame");
258259
}
260+
261+
static UnwindPlan::Row GetValOffsetExpectedRow0() {
262+
UnwindPlan::Row row;
263+
row.SetOffset(0);
264+
row.GetCFAValue().SetIsRegisterPlusOffset(dwarf_rsp_x86_64, 16);
265+
row.SetRegisterLocationToAtCFAPlusOffset(dwarf_rip_x86_64, -8, false);
266+
row.SetRegisterLocationToIsCFAPlusOffset(dwarf_rbp_x86_64, -16, false);
267+
return row;
268+
}
269+
270+
void DWARFCallFrameInfoTest::TestValOffset(DWARFCallFrameInfo::Type type,
271+
llvm::StringRef symbol) {
272+
// This test is artificial as X86 does not use DW_CFA_val_offset but this
273+
// test verifies that we can successfully interpret them if they do occur.
274+
// Note the distinction between RBP and RIP in this part of the DWARF dump:
275+
// 0x0: CFA=RSP+16: RBP=CFA-16, RIP=[CFA-8]
276+
// Whereas RIP is stored in the memory CFA-8 points at, RBP is reconstructed
277+
// from the CFA without any memory access.
278+
auto ExpectedFile = TestFile::fromYaml(R"(
279+
--- !ELF
280+
FileHeader:
281+
Class: ELFCLASS64
282+
Data: ELFDATA2LSB
283+
Type: ET_REL
284+
Machine: EM_X86_64
285+
SectionHeaderStringTable: .strtab
286+
Sections:
287+
- Name: .text
288+
Type: SHT_PROGBITS
289+
Flags: [ SHF_ALLOC, SHF_EXECINSTR ]
290+
AddressAlign: 0x4
291+
Content: 0F1F00
292+
- Name: .debug_frame
293+
Type: SHT_PROGBITS
294+
AddressAlign: 0x8
295+
#00000000 00000014 ffffffff CIE
296+
# Format: DWARF32
297+
# Version: 4
298+
# Augmentation: ""
299+
# Address size: 8
300+
# Segment desc size: 0
301+
# Code alignment factor: 1
302+
# Data alignment factor: -8
303+
# Return address column: 16
304+
#
305+
# DW_CFA_def_cfa: RSP +8
306+
# DW_CFA_offset: RIP -8
307+
# DW_CFA_nop:
308+
# DW_CFA_nop:
309+
# DW_CFA_nop:
310+
# DW_CFA_nop:
311+
#
312+
# CFA=RSP+8: RIP=[CFA-8]
313+
#
314+
#00000018 0000001c 00000000 FDE cie=00000000 pc=00000000...00000003
315+
# Format: DWARF32
316+
# DW_CFA_def_cfa_offset: +16
317+
# DW_CFA_val_offset: RBP -16
318+
# DW_CFA_nop:
319+
# DW_CFA_nop:
320+
# DW_CFA_nop:
321+
#
322+
# 0x0: CFA=RSP+16: RBP=CFA-16, RIP=[CFA-8]
323+
Content: 14000000FFFFFFFF040008000178100C07089001000000001C00000000000000000000000000000003000000000000000E10140602000000
324+
- Name: .rela.debug_frame
325+
Type: SHT_RELA
326+
Flags: [ SHF_INFO_LINK ]
327+
Link: .symtab
328+
AddressAlign: 0x8
329+
Info: .debug_frame
330+
Relocations:
331+
- Offset: 0x1C
332+
Symbol: .debug_frame
333+
Type: R_X86_64_32
334+
- Offset: 0x20
335+
Symbol: .text
336+
Type: R_X86_64_64
337+
- Type: SectionHeaderTable
338+
Sections:
339+
- Name: .strtab
340+
- Name: .text
341+
- Name: .debug_frame
342+
- Name: .rela.debug_frame
343+
- Name: .symtab
344+
Symbols:
345+
- Name: .text
346+
Type: STT_SECTION
347+
Section: .text
348+
- Name: debug_frame3
349+
Section: .text
350+
- Name: .debug_frame
351+
Type: STT_SECTION
352+
Section: .debug_frame
353+
...
354+
)");
355+
ASSERT_THAT_EXPECTED(ExpectedFile, llvm::Succeeded());
356+
357+
auto module_sp = std::make_shared<Module>(ExpectedFile->moduleSpec());
358+
SectionList *list = module_sp->GetSectionList();
359+
ASSERT_NE(nullptr, list);
360+
361+
auto section_sp = list->FindSectionByType(type == DWARFCallFrameInfo::EH
362+
? eSectionTypeEHFrame
363+
: eSectionTypeDWARFDebugFrame,
364+
false);
365+
ASSERT_NE(nullptr, section_sp);
366+
367+
DWARFCallFrameInfo cfi(*module_sp->GetObjectFile(), section_sp, type);
368+
369+
const Symbol *sym = module_sp->FindFirstSymbolWithNameAndType(
370+
ConstString(symbol), eSymbolTypeAny);
371+
ASSERT_NE(nullptr, sym);
372+
373+
std::unique_ptr<UnwindPlan> plan_up = cfi.GetUnwindPlan(sym->GetAddress());
374+
ASSERT_TRUE(plan_up);
375+
ASSERT_EQ(1, plan_up->GetRowCount());
376+
EXPECT_THAT(plan_up->GetRowAtIndex(0),
377+
testing::Pointee(GetValOffsetExpectedRow0()));
378+
}
379+
380+
TEST_F(DWARFCallFrameInfoTest, ValOffset_dwarf3) {
381+
TestValOffset(DWARFCallFrameInfo::DWARF, "debug_frame3");
382+
}

0 commit comments

Comments
 (0)