Skip to content

Commit b36a446

Browse files
Parse CFI instructions to create SFrame FREs (#155496)
This PR parses CFI instructions to generate FREs. Unfortunately, actually emitting the FREs into the object file is somewhat involved and would make this PR quite a bit harder to review. And the dumper itself properly errors if the FRE count is included in the header or FDEs, but they are not actually emitted. So actually testing that the proper FREs are generated will have to wait for a subsequent PR. For now, just check for common issues with CFI that sframe doesn't support, and that proper error handling is done.
1 parent 7f70bdd commit b36a446

File tree

3 files changed

+237
-10
lines changed

3 files changed

+237
-10
lines changed

llvm/lib/MC/MCSFrame.cpp

Lines changed: 191 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,33 @@ using namespace sframe;
2121

2222
namespace {
2323

24+
// High-level structure to track info needed to emit a
25+
// sframe_frame_row_entry_addrX. On disk these have both a fixed portion of type
26+
// sframe_frame_row_entry_addrX and trailing data of X * S bytes, where X is the
27+
// datum size, and S is 1, 2, or 3 depending on which of CFA, SP, and FP are
28+
// being tracked.
29+
struct SFrameFRE {
30+
// An FRE describes how to find the registers when the PC is at this
31+
// Label from function start.
32+
const MCSymbol *Label = nullptr;
33+
size_t CFAOffset = 0;
34+
size_t FPOffset = 0;
35+
size_t RAOffset = 0;
36+
bool FromFP = false;
37+
bool CFARegSet = false;
38+
39+
SFrameFRE(const MCSymbol *Start) : Label(Start) {}
40+
};
41+
2442
// High-level structure to track info needed to emit a sframe_func_desc_entry
2543
// and its associated FREs.
2644
struct SFrameFDE {
2745
// Reference to the original dwarf frame to avoid copying.
2846
const MCDwarfFrameInfo &DFrame;
2947
// Label where this FDE's FREs start.
3048
MCSymbol *FREStart;
49+
// Unwinding fres
50+
SmallVector<SFrameFRE> FREs;
3151

3252
SFrameFDE(const MCDwarfFrameInfo &DF, MCSymbol *FRES)
3353
: DFrame(DF), FREStart(FRES) {}
@@ -53,7 +73,8 @@ struct SFrameFDE {
5373
MCFixup::getDataKindForSize(4)));
5474
S.emitInt32(0);
5575

56-
// sfde_func_start_num_fres
76+
// sfde_func_num_fres
77+
// TODO: When we actually emit fres, replace 0 with FREs.size()
5778
S.emitInt32(0);
5879

5980
// sfde_func_info word
@@ -76,10 +97,90 @@ class SFrameEmitterImpl {
7697
MCObjectStreamer &Streamer;
7798
SmallVector<SFrameFDE> FDEs;
7899
ABI SFrameABI;
100+
// Target-specific convenience variables to detect when a CFI instruction
101+
// references these registers. Unlike in dwarf frame descriptions, they never
102+
// escape into the sframe section itself.
103+
unsigned SPReg;
104+
unsigned FPReg;
105+
unsigned RAReg;
79106
MCSymbol *FDESubSectionStart;
80107
MCSymbol *FRESubSectionStart;
81108
MCSymbol *FRESubSectionEnd;
82109

110+
bool setCFARegister(SFrameFRE &FRE, const MCCFIInstruction &I) {
111+
if (I.getRegister() == SPReg) {
112+
FRE.CFARegSet = true;
113+
FRE.FromFP = false;
114+
return true;
115+
}
116+
if (I.getRegister() == FPReg) {
117+
FRE.CFARegSet = true;
118+
FRE.FromFP = true;
119+
return true;
120+
}
121+
Streamer.getContext().reportWarning(
122+
I.getLoc(), "canonical Frame Address not in stack- or frame-pointer. "
123+
"Omitting SFrame unwind info for this function");
124+
return false;
125+
}
126+
127+
bool setCFAOffset(SFrameFRE &FRE, const SMLoc &Loc, size_t Offset) {
128+
if (!FRE.CFARegSet) {
129+
Streamer.getContext().reportWarning(
130+
Loc, "adjusting CFA offset without a base register. "
131+
"Omitting SFrame unwind info for this function");
132+
return false;
133+
}
134+
FRE.CFAOffset = Offset;
135+
return true;
136+
}
137+
138+
// Add the effects of CFI to the current FDE, creating a new FRE when
139+
// necessary.
140+
bool handleCFI(SFrameFDE &FDE, SFrameFRE &FRE, const MCCFIInstruction &CFI) {
141+
switch (CFI.getOperation()) {
142+
case MCCFIInstruction::OpDefCfaRegister:
143+
return setCFARegister(FRE, CFI);
144+
case MCCFIInstruction::OpDefCfa:
145+
case MCCFIInstruction::OpLLVMDefAspaceCfa:
146+
if (!setCFARegister(FRE, CFI))
147+
return false;
148+
return setCFAOffset(FRE, CFI.getLoc(), CFI.getOffset());
149+
case MCCFIInstruction::OpOffset:
150+
if (CFI.getRegister() == FPReg)
151+
FRE.FPOffset = CFI.getOffset();
152+
else if (CFI.getRegister() == RAReg)
153+
FRE.RAOffset = CFI.getOffset();
154+
return true;
155+
case MCCFIInstruction::OpRelOffset:
156+
if (CFI.getRegister() == FPReg)
157+
FRE.FPOffset += CFI.getOffset();
158+
else if (CFI.getRegister() == RAReg)
159+
FRE.RAOffset += CFI.getOffset();
160+
return true;
161+
case MCCFIInstruction::OpDefCfaOffset:
162+
return setCFAOffset(FRE, CFI.getLoc(), CFI.getOffset());
163+
case MCCFIInstruction::OpAdjustCfaOffset:
164+
return setCFAOffset(FRE, CFI.getLoc(), FRE.CFAOffset + CFI.getOffset());
165+
case MCCFIInstruction::OpRememberState:
166+
// TODO: Implement. Will use FDE.
167+
return true;
168+
case MCCFIInstruction::OpRestore:
169+
// TODO: Implement. Will use FDE.
170+
return true;
171+
case MCCFIInstruction::OpRestoreState:
172+
// TODO: Implement. Will use FDE.
173+
return true;
174+
case MCCFIInstruction::OpEscape:
175+
// TODO: Implement. Will use FDE.
176+
return true;
177+
default:
178+
// Instructions that don't affect the CFA, RA, and SP can be safely
179+
// ignored.
180+
return true;
181+
}
182+
}
183+
83184
public:
84185
SFrameEmitterImpl(MCObjectStreamer &Streamer) : Streamer(Streamer) {
85186
assert(Streamer.getContext()
@@ -88,13 +189,96 @@ class SFrameEmitterImpl {
88189
.has_value());
89190
FDEs.reserve(Streamer.getDwarfFrameInfos().size());
90191
SFrameABI = *Streamer.getContext().getObjectFileInfo()->getSFrameABIArch();
192+
switch (SFrameABI) {
193+
case ABI::AArch64EndianBig:
194+
case ABI::AArch64EndianLittle:
195+
SPReg = 31;
196+
RAReg = 29;
197+
FPReg = 30;
198+
break;
199+
case ABI::AMD64EndianLittle:
200+
SPReg = 7;
201+
// RARegister untracked in this abi. Value chosen to match
202+
// MCDwarfFrameInfo constructor.
203+
RAReg = static_cast<unsigned>(INT_MAX);
204+
FPReg = 6;
205+
break;
206+
}
207+
91208
FDESubSectionStart = Streamer.getContext().createTempSymbol();
92209
FRESubSectionStart = Streamer.getContext().createTempSymbol();
93210
FRESubSectionEnd = Streamer.getContext().createTempSymbol();
94211
}
95212

96-
void BuildSFDE(const MCDwarfFrameInfo &DF) {
97-
FDEs.emplace_back(DF, Streamer.getContext().createTempSymbol());
213+
bool atSameLocation(const MCSymbol *Left, const MCSymbol *Right) {
214+
return Left != nullptr && Right != nullptr &&
215+
Left->getFragment() == Right->getFragment() &&
216+
Left->getOffset() == Right->getOffset();
217+
}
218+
219+
bool equalIgnoringLocation(const SFrameFRE &Left, const SFrameFRE &Right) {
220+
return Left.CFAOffset == Right.CFAOffset &&
221+
Left.FPOffset == Right.FPOffset && Left.RAOffset == Right.RAOffset &&
222+
Left.FromFP == Right.FromFP && Left.CFARegSet == Right.CFARegSet;
223+
}
224+
225+
void buildSFDE(const MCDwarfFrameInfo &DF) {
226+
bool Valid = true;
227+
SFrameFDE FDE(DF, Streamer.getContext().createTempSymbol());
228+
// This would have been set via ".cfi_return_column", but
229+
// MCObjectStreamer doesn't emit an MCCFIInstruction for that. It just
230+
// sets the DF.RAReg.
231+
// FIXME: This also prevents providing a proper location for the error.
232+
// LLVM doesn't change the return column itself, so this was
233+
// hand-written assembly.
234+
if (DF.RAReg != RAReg) {
235+
Streamer.getContext().reportWarning(
236+
SMLoc(), "non-default RA register in .cfi_return_column " +
237+
Twine(DF.RAReg) +
238+
". Omitting SFrame unwind info for this function");
239+
Valid = false;
240+
}
241+
MCSymbol *LastLabel = DF.Begin;
242+
SFrameFRE BaseFRE(LastLabel);
243+
if (!DF.IsSimple) {
244+
for (const auto &CFI :
245+
Streamer.getContext().getAsmInfo()->getInitialFrameState())
246+
if (!handleCFI(FDE, BaseFRE, CFI))
247+
Valid = false;
248+
}
249+
FDE.FREs.push_back(BaseFRE);
250+
251+
for (const auto &CFI : DF.Instructions) {
252+
// Instructions from InitialFrameState may not have a label, but if these
253+
// instructions don't, then they are in dead code or otherwise unused.
254+
// TODO: This check follows MCDwarf.cpp
255+
// FrameEmitterImplementation::emitCFIInstructions, but nothing in the
256+
// testsuite triggers it. We should see if it can be removed in both
257+
// places, or alternately, add a test to exercise it.
258+
auto *L = CFI.getLabel();
259+
if (L && !L->isDefined())
260+
continue;
261+
262+
SFrameFRE FRE = FDE.FREs.back();
263+
if (!handleCFI(FDE, FRE, CFI))
264+
Valid = false;
265+
266+
// If nothing relevant but the location changed, don't add the FRE.
267+
if (equalIgnoringLocation(FRE, FDE.FREs.back()))
268+
continue;
269+
270+
// If the location stayed the same, then update the current
271+
// row. Otherwise, add a new one.
272+
if (atSameLocation(LastLabel, L))
273+
FDE.FREs.back() = FRE;
274+
else {
275+
FDE.FREs.push_back(FRE);
276+
FDE.FREs.back().Label = L;
277+
LastLabel = L;
278+
}
279+
}
280+
if (Valid)
281+
FDEs.push_back(FDE);
98282
}
99283

100284
void emitPreamble() {
@@ -116,7 +300,9 @@ class SFrameEmitterImpl {
116300
// shf_num_fdes
117301
Streamer.emitInt32(FDEs.size());
118302
// shf_num_fres
119-
Streamer.emitInt32(0);
303+
uint32_t TotalFREs = 0;
304+
Streamer.emitInt32(TotalFREs);
305+
120306
// shf_fre_len
121307
Streamer.emitAbsoluteSymbolDiff(FRESubSectionEnd, FRESubSectionStart,
122308
sizeof(int32_t));
@@ -161,7 +347,7 @@ void MCSFrameEmitter::emit(MCObjectStreamer &Streamer) {
161347
// Both the header itself and the FDEs include various offsets and counts.
162348
// Therefore, all of this must be precomputed.
163349
for (const auto &DFrame : FrameArray)
164-
Emitter.BuildSFDE(DFrame);
350+
Emitter.buildSFDE(DFrame);
165351

166352
MCSection *Section = Context.getObjectFileInfo()->getSFrameSection();
167353
// Not strictly necessary, but gas always aligns to 8, so match that.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// TODO: Add other architectures as they gain sframe support
2+
// REQUIRES: x86-registered-target
3+
// RUN: llvm-mc --assemble --filetype=obj -triple x86_64 %s -o %t.o 2>&1 | FileCheck %s
4+
// RUN: llvm-readelf --sframe %t.o | FileCheck --check-prefix=CHECK-NOFDES %s
5+
6+
7+
.cfi_sections .sframe
8+
f1:
9+
.cfi_startproc simple
10+
// CHECK: non-default RA register {{.*}}
11+
.cfi_return_column 0
12+
nop
13+
// CHECK: {{.*}} adjusting CFA offset without a base register.{{.*}}
14+
.cfi_def_cfa_offset 16 // no line number reported here.
15+
nop
16+
// CHECK: [[@LINE+1]]:{{.*}} adjusting CFA offset without a base register.{{.*}}
17+
.cfi_adjust_cfa_offset 16
18+
nop
19+
.cfi_endproc
20+
21+
f2:
22+
.cfi_startproc
23+
nop
24+
// CHECK: canonical Frame Address not in stack- or frame-pointer. {{.*}}
25+
.cfi_def_cfa 0, 4
26+
nop
27+
28+
.cfi_endproc
29+
30+
// CHECK-NOFDES: Num FDEs: 0
31+
// CHECK-NOFDES: Num FREs: 0

llvm/test/MC/ELF/cfi-sframe.s

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@
55

66
.cfi_sections .sframe
77
f1:
8-
.cfi_startproc
8+
.cfi_startproc // FRE 0
9+
nop
10+
.cfi_def_cfa_offset 16 // FRE 1
11+
.cfi_def_cfa_offset 8 // location didn't change. No new FRE, but new offset.
12+
nop
13+
.cfi_def_cfa_offset 8 // offset didn't change. No new FRE.
14+
nop
15+
.cfi_def_cfa_offset 16 // FRE 2. new location, new offset.
916
nop
17+
.cfi_register 0, 1 // Uninteresting register. No new FRE.
18+
nop
19+
1020
.cfi_endproc
1121

1222
f2:
@@ -32,11 +42,11 @@ f2:
3242
// CHECK: Function Index [
3343
// CHECK-NEXT: FuncDescEntry [0] {
3444
// CHECK-NEXT: PC {
35-
// CHECK-NEXT: Relocation: {{.*}}32{{.*}}
45+
// CHECK-NEXT: Relocation: {{.*}}PC32{{.*}}
3646
// CHECK-NEXT: Symbol Name: .text
37-
// CHECK-NEXT: Start Address: 0x0
47+
// CHECK-NEXT: Start Address: {{.*}}
3848
// CHECK-NEXT: }
39-
// CHECK-NEXT: Size: 0x1
49+
// CHECK-NEXT: Size: 0x5
4050
// CHECK-NEXT: Start FRE Offset: 0x0
4151
// CHECK-NEXT: Num FREs: 0
4252
// CHECK-NEXT: Info {
@@ -51,7 +61,7 @@ f2:
5161
// CHECK-NEXT: }
5262
// CHECK-NEXT: FuncDescEntry [1] {
5363
// CHECK-NEXT: PC {
54-
// CHECK-NEXT: Relocation: R_X86_64_PC32
64+
// CHECK-NEXT: Relocation: {{.*}}PC32{{.*}}
5565
// CHECK-NEXT: Symbol Name: .text
5666
// CHECK-NEXT: Start Address: {{.*}}
5767
// CHECK-NEXT: }

0 commit comments

Comments
 (0)