1//===- Win64EHDumper.cpp - Win64 EH Printer ---------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "Win64EHDumper.h"
10#include "llvm-readobj.h"
11#include "llvm/ADT/Enum.h"
12#include "llvm/Object/COFF.h"
13#include "llvm/Support/ErrorHandling.h"
14#include "llvm/Support/Format.h"
15#include "llvm/Support/FormatVariadic.h"
16#include "llvm/Support/WithColor.h"
17
18using namespace llvm;
19using namespace llvm::object;
20using namespace llvm::Win64EH;
21
22// clang-format off
23constexpr EnumStringDef<unsigned> UnwindFlagDefs[] = {
24 {.Names: {"ExceptionHandler"}, .Value: UNW_ExceptionHandler},
25 {.Names: {"TerminateHandler"}, .Value: UNW_TerminateHandler},
26 {.Names: {"ChainInfo"} , .Value: UNW_ChainInfo },
27 {.Names: {"Large"} , .Value: UNW_FlagLarge }
28};
29constexpr auto UnwindFlags = BUILD_ENUM_STRINGS(UnwindFlagDefs);
30
31constexpr EnumStringDef<unsigned> EpilogFlagDefs[] = {
32 {.Names: {"ParentFragmentTransfer"}, .Value: EPILOG_PARENT_FRAGMENT_TRANSFER},
33 {.Names: {"Large"} , .Value: EPILOG_INFO_LARGE }
34};
35constexpr auto EpilogFlags = BUILD_ENUM_STRINGS(EpilogFlagDefs);
36
37constexpr EnumStringDef<unsigned> UnwindOpInfoDefs[] = {
38 {.Names: {"RAX"}, .Value: 0},
39 {.Names: {"RCX"}, .Value: 1},
40 {.Names: {"RDX"}, .Value: 2},
41 {.Names: {"RBX"}, .Value: 3},
42 {.Names: {"RSP"}, .Value: 4},
43 {.Names: {"RBP"}, .Value: 5},
44 {.Names: {"RSI"}, .Value: 6},
45 {.Names: {"RDI"}, .Value: 7},
46 {.Names: {"R8"}, .Value: 8},
47 {.Names: {"R9"}, .Value: 9},
48 {.Names: {"R10"}, .Value: 10},
49 {.Names: {"R11"}, .Value: 11},
50 {.Names: {"R12"}, .Value: 12},
51 {.Names: {"R13"}, .Value: 13},
52 {.Names: {"R14"}, .Value: 14},
53 {.Names: {"R15"}, .Value: 15}
54};
55constexpr auto UnwindOpInfo = BUILD_ENUM_STRINGS(UnwindOpInfoDefs);
56// clang-format on
57
58static uint64_t getOffsetOfLSDA(const UnwindInfo& UI) {
59 return static_cast<const char*>(UI.getLanguageSpecificData())
60 - reinterpret_cast<const char*>(&UI);
61}
62
63static uint32_t getLargeSlotValue(ArrayRef<UnwindCode> UC) {
64 if (UC.size() < 3)
65 return 0;
66 return UC[1].FrameOffset + (static_cast<uint32_t>(UC[2].FrameOffset) << 16);
67}
68
69// Returns the name of the unwind code.
70static StringRef getUnwindCodeTypeName(uint8_t Code) {
71 switch (Code) {
72 default: llvm_unreachable("Invalid unwind code");
73 case UOP_PushNonVol: return "PUSH_NONVOL";
74 case UOP_AllocLarge: return "ALLOC_LARGE";
75 case UOP_AllocSmall: return "ALLOC_SMALL";
76 case UOP_SetFPReg: return "SET_FPREG";
77 case UOP_SaveNonVol: return "SAVE_NONVOL";
78 case UOP_SaveNonVolBig: return "SAVE_NONVOL_FAR";
79 case UOP_SaveXMM128: return "SAVE_XMM128";
80 case UOP_SaveXMM128Big: return "SAVE_XMM128_FAR";
81 case UOP_PushMachFrame: return "PUSH_MACHFRAME";
82 case UOP_Epilog:
83 return "EPILOG";
84 }
85}
86
87// Returns the name of a referenced register.
88static StringRef getUnwindRegisterName(uint8_t Reg) {
89 switch (Reg) {
90 default: llvm_unreachable("Invalid register");
91 case 0: return "RAX";
92 case 1: return "RCX";
93 case 2: return "RDX";
94 case 3: return "RBX";
95 case 4: return "RSP";
96 case 5: return "RBP";
97 case 6: return "RSI";
98 case 7: return "RDI";
99 case 8: return "R8";
100 case 9: return "R9";
101 case 10: return "R10";
102 case 11: return "R11";
103 case 12: return "R12";
104 case 13: return "R13";
105 case 14: return "R14";
106 case 15: return "R15";
107 }
108}
109
110// Calculates the number of array slots required for the unwind code.
111static unsigned getNumUsedSlots(const UnwindCode &UnwindCode) {
112 switch (UnwindCode.getUnwindOp()) {
113 default: llvm_unreachable("Invalid unwind code");
114 case UOP_PushNonVol:
115 case UOP_AllocSmall:
116 case UOP_SetFPReg:
117 case UOP_PushMachFrame:
118 case UOP_Epilog:
119 return 1;
120 case UOP_SaveNonVol:
121 case UOP_SaveXMM128:
122 return 2;
123 case UOP_SaveNonVolBig:
124 case UOP_SaveXMM128Big:
125 return 3;
126 case UOP_AllocLarge:
127 return (UnwindCode.getOpInfo() == 0) ? 2 : 3;
128 }
129}
130
131static std::error_code getSymbol(const COFFObjectFile &COFF, uint64_t VA,
132 object::SymbolRef &Sym) {
133 for (const auto &Symbol : COFF.symbols()) {
134 Expected<uint64_t> Address = Symbol.getAddress();
135 if (!Address)
136 return errorToErrorCode(Err: Address.takeError());
137 if (*Address == VA) {
138 Sym = Symbol;
139 return std::error_code();
140 }
141 }
142 return inconvertibleErrorCode();
143}
144
145static object::SymbolRef getPreferredSymbol(const COFFObjectFile &COFF,
146 object::SymbolRef Sym,
147 uint32_t &SymbolOffset,
148 bool IsRangeEnd) {
149 // The symbol resolved by ResolveSymbol can be any internal
150 // nondescriptive symbol; try to resolve a more descriptive one.
151 COFFSymbolRef CoffSym = COFF.getCOFFSymbol(Symbol: Sym);
152 if (CoffSym.getStorageClass() != COFF::IMAGE_SYM_CLASS_LABEL &&
153 CoffSym.getSectionDefinition() == nullptr)
154 return Sym;
155 for (const auto &S : COFF.symbols()) {
156 COFFSymbolRef CS = COFF.getCOFFSymbol(Symbol: S);
157 if (CS.getSectionNumber() == CoffSym.getSectionNumber() &&
158 CS.getValue() <= CoffSym.getValue() + SymbolOffset &&
159 CS.getStorageClass() != COFF::IMAGE_SYM_CLASS_LABEL &&
160 CS.getSectionDefinition() == nullptr) {
161 uint32_t Offset = CoffSym.getValue() + SymbolOffset - CS.getValue();
162 // For the end of a range, don't pick a symbol with a zero offset;
163 // prefer a symbol with a small positive offset.
164 if (Offset <= SymbolOffset && (!IsRangeEnd || Offset > 0)) {
165 SymbolOffset = Offset;
166 Sym = S;
167 CoffSym = CS;
168 if (CS.isExternal() && SymbolOffset == 0)
169 return Sym;
170 }
171 }
172 }
173 return Sym;
174}
175
176static std::string formatSymbol(const Dumper::Context &Ctx,
177 const coff_section *Section, uint64_t Offset,
178 uint32_t Displacement,
179 bool IsRangeEnd = false) {
180 std::string Buffer;
181 raw_string_ostream OS(Buffer);
182
183 SymbolRef Symbol;
184 if (!Ctx.ResolveSymbol(Section, Offset, Symbol, Ctx.UserData)) {
185 // We found a relocation at the given offset in the section, pointing
186 // at a symbol.
187
188 // Try to resolve label/section symbols into function names.
189 Symbol = getPreferredSymbol(COFF: Ctx.COFF, Sym: Symbol, SymbolOffset&: Displacement, IsRangeEnd);
190
191 Expected<StringRef> Name = Symbol.getName();
192 if (Name) {
193 OS << *Name;
194 if (Displacement > 0)
195 OS << format(Fmt: " +0x%X (0x%" PRIX64 ")", Vals: Displacement, Vals: Offset);
196 else
197 OS << format(Fmt: " (0x%" PRIX64 ")", Vals: Offset);
198 return OS.str();
199 } else {
200 // TODO: Actually report errors helpfully.
201 consumeError(Err: Name.takeError());
202 }
203 } else if (!getSymbol(COFF: Ctx.COFF, VA: Ctx.COFF.getImageBase() + Displacement,
204 Sym&: Symbol)) {
205 Expected<StringRef> Name = Symbol.getName();
206 if (Name) {
207 OS << *Name;
208 OS << format(Fmt: " (0x%" PRIX64 ")", Vals: Ctx.COFF.getImageBase() + Displacement);
209 return OS.str();
210 } else {
211 consumeError(Err: Name.takeError());
212 }
213 }
214
215 if (Displacement > 0)
216 OS << format(Fmt: "(0x%" PRIX64 ")", Vals: Ctx.COFF.getImageBase() + Displacement);
217 else
218 OS << format(Fmt: "(0x%" PRIX64 ")", Vals: Offset);
219 return OS.str();
220}
221
222static std::error_code resolveRelocation(const Dumper::Context &Ctx,
223 const coff_section *Section,
224 uint64_t Offset,
225 const coff_section *&ResolvedSection,
226 uint64_t &ResolvedAddress) {
227 SymbolRef Symbol;
228 if (std::error_code EC =
229 Ctx.ResolveSymbol(Section, Offset, Symbol, Ctx.UserData))
230 return EC;
231
232 Expected<uint64_t> ResolvedAddressOrErr = Symbol.getAddress();
233 if (!ResolvedAddressOrErr)
234 return errorToErrorCode(Err: ResolvedAddressOrErr.takeError());
235 ResolvedAddress = *ResolvedAddressOrErr;
236
237 Expected<section_iterator> SI = Symbol.getSection();
238 if (!SI)
239 return errorToErrorCode(Err: SI.takeError());
240 ResolvedSection = Ctx.COFF.getCOFFSection(Section: **SI);
241 return std::error_code();
242}
243
244static const object::coff_section *
245getSectionContaining(const COFFObjectFile &COFF, uint64_t VA) {
246 for (const auto &Section : COFF.sections()) {
247 uint64_t Address = Section.getAddress();
248 uint64_t Size = Section.getSize();
249
250 if (VA >= Address && (VA - Address) <= Size)
251 return COFF.getCOFFSection(Section);
252 }
253 return nullptr;
254}
255
256namespace llvm {
257namespace Win64EH {
258void Dumper::printRuntimeFunctionEntry(const Context &Ctx,
259 const coff_section *Section,
260 uint64_t Offset,
261 const RuntimeFunction &RF) {
262 SW.printString(Label: "StartAddress",
263 Value: formatSymbol(Ctx, Section, Offset: Offset + 0, Displacement: RF.StartAddress));
264 SW.printString(Label: "EndAddress",
265 Value: formatSymbol(Ctx, Section, Offset: Offset + 4, Displacement: RF.EndAddress,
266 /*IsRangeEnd=*/true));
267 SW.printString(Label: "UnwindInfoAddress",
268 Value: formatSymbol(Ctx, Section, Offset: Offset + 8, Displacement: RF.UnwindInfoOffset));
269}
270
271// Prints one unwind code. Because an unwind code can occupy up to 3 slots in
272// the unwind codes array, this function requires that the correct number of
273// slots is provided.
274void Dumper::printUnwindCode(const UnwindInfo &UI, ArrayRef<UnwindCode> UC,
275 bool &SeenFirstEpilog) {
276 assert(UC.size() >= getNumUsedSlots(UC[0]));
277
278 SW.startLine() << format(Fmt: "0x%02X: ", Vals: unsigned(UC[0].u.CodeOffset))
279 << getUnwindCodeTypeName(Code: UC[0].getUnwindOp());
280
281 switch (UC[0].getUnwindOp()) {
282 case UOP_PushNonVol:
283 OS << " reg=" << getUnwindRegisterName(Reg: UC[0].getOpInfo());
284 break;
285
286 case UOP_AllocLarge:
287 OS << " size="
288 << ((UC[0].getOpInfo() == 0) ? UC[1].FrameOffset * 8
289 : getLargeSlotValue(UC));
290 break;
291
292 case UOP_AllocSmall:
293 OS << " size=" << (UC[0].getOpInfo() + 1) * 8;
294 break;
295
296 case UOP_SetFPReg:
297 if (UI.getFrameRegister() == 0)
298 OS << " reg=<invalid>";
299 else
300 OS << " reg=" << getUnwindRegisterName(Reg: UI.getFrameRegister())
301 << format(Fmt: ", offset=0x%X", Vals: UI.getFrameOffset() * 16);
302 break;
303
304 case UOP_SaveNonVol:
305 OS << " reg=" << getUnwindRegisterName(Reg: UC[0].getOpInfo())
306 << format(Fmt: ", offset=0x%X", Vals: UC[1].FrameOffset * 8);
307 break;
308
309 case UOP_SaveNonVolBig:
310 OS << " reg=" << getUnwindRegisterName(Reg: UC[0].getOpInfo())
311 << format(Fmt: ", offset=0x%X", Vals: getLargeSlotValue(UC));
312 break;
313
314 case UOP_SaveXMM128:
315 OS << " reg=XMM" << static_cast<uint32_t>(UC[0].getOpInfo())
316 << format(Fmt: ", offset=0x%X", Vals: UC[1].FrameOffset * 16);
317 break;
318
319 case UOP_SaveXMM128Big:
320 OS << " reg=XMM" << static_cast<uint32_t>(UC[0].getOpInfo())
321 << format(Fmt: ", offset=0x%X", Vals: getLargeSlotValue(UC));
322 break;
323
324 case UOP_PushMachFrame:
325 OS << " errcode=" << (UC[0].getOpInfo() == 0 ? "no" : "yes");
326 break;
327
328 case UOP_Epilog:
329 if (SeenFirstEpilog) {
330 uint32_t Offset = UC[0].getEpilogOffset();
331 if (Offset == 0) {
332 OS << " padding";
333 } else {
334 OS << " offset=" << format(Fmt: "0x%X", Vals: Offset);
335 }
336 } else {
337 SeenFirstEpilog = true;
338 bool AtEnd = (UC[0].getOpInfo() & 0x1) != 0;
339 uint32_t Length = UC[0].u.CodeOffset;
340 OS << " atend=" << (AtEnd ? "yes" : "no")
341 << ", length=" << format(Fmt: "0x%X", Vals: Length);
342 }
343 break;
344 }
345
346 OS << "\n";
347}
348
349void Dumper::printUnwindInfo(const Context &Ctx, const coff_section *Section,
350 off_t Offset, const UnwindInfo &UI) {
351 DictScope UIS(SW, "UnwindInfo");
352 SW.printNumber(Label: "Version", Value: UI.getVersion());
353 SW.printFlags(Label: "Flags", Value: UI.getFlags(), Flags: EnumStrings(UnwindFlags));
354 SW.printNumber(Label: "PrologSize", Value: UI.PrologSize);
355 if (UI.getFrameRegister()) {
356 SW.printEnum(Label: "FrameRegister", Value: UI.getFrameRegister(),
357 EnumValues: EnumStrings(UnwindOpInfo));
358 SW.printHex(Label: "FrameOffset", Value: UI.getFrameOffset());
359 } else {
360 SW.printString(Label: "FrameRegister", Value: StringRef("-"));
361 SW.printString(Label: "FrameOffset", Value: StringRef("-"));
362 }
363
364 SW.printNumber(Label: "UnwindCodeCount", Value: UI.NumCodes);
365 {
366 ListScope UCS(SW, "UnwindCodes");
367 ArrayRef<UnwindCode> UC(&UI.UnwindCodes[0], UI.NumCodes);
368 bool SeenFirstEpilog = false;
369 for (const UnwindCode *UCI = UC.begin(), *UCE = UC.end(); UCI < UCE; ++UCI) {
370 unsigned UsedSlots = getNumUsedSlots(UnwindCode: *UCI);
371 if (UsedSlots > UC.size()) {
372 errs() << "corrupt unwind data";
373 return;
374 }
375
376 printUnwindCode(UI, UC: ArrayRef(UCI, UCE), SeenFirstEpilog);
377 UCI = UCI + UsedSlots - 1;
378 }
379 }
380
381 uint64_t LSDAOffset = Offset + getOffsetOfLSDA(UI);
382 if (UI.getFlags() & (UNW_ExceptionHandler | UNW_TerminateHandler)) {
383 SW.printString(Label: "Handler",
384 Value: formatSymbol(Ctx, Section, Offset: LSDAOffset,
385 Displacement: UI.getLanguageSpecificHandlerOffset()));
386 } else if (UI.getFlags() & UNW_ChainInfo) {
387 if (const RuntimeFunction *Chained = UI.getChainedFunctionEntry()) {
388 DictScope CS(SW, "Chained");
389 printRuntimeFunctionEntry(Ctx, Section, Offset: LSDAOffset, RF: *Chained);
390 }
391 }
392}
393
394void Dumper::printRuntimeFunction(const Context &Ctx,
395 const coff_section *Section,
396 uint64_t SectionOffset,
397 const RuntimeFunction &RF) {
398 DictScope RFS(SW, "RuntimeFunction");
399 printRuntimeFunctionEntry(Ctx, Section, Offset: SectionOffset, RF);
400
401 const coff_section *XData = nullptr;
402 uint64_t Offset;
403 resolveRelocation(Ctx, Section, Offset: SectionOffset + 8, ResolvedSection&: XData, ResolvedAddress&: Offset);
404 Offset = Offset + RF.UnwindInfoOffset;
405
406 if (!XData) {
407 uint64_t Address = Ctx.COFF.getImageBase() + RF.UnwindInfoOffset;
408 XData = getSectionContaining(COFF: Ctx.COFF, VA: Address);
409 if (!XData)
410 return;
411 Offset = RF.UnwindInfoOffset - XData->VirtualAddress;
412 }
413
414 ArrayRef<uint8_t> Contents;
415 if (Error E = Ctx.COFF.getSectionContents(Sec: XData, Res&: Contents))
416 reportError(Err: std::move(E), Input: Ctx.COFF.getFileName());
417
418 if (Contents.empty())
419 return;
420
421 if (Offset > Contents.size())
422 return;
423
424 // Check version before casting to UnwindInfo struct.
425 // Only byte 0 (VersionAndFlags) is layout-compatible between V1/V2 and V3.
426 uint8_t VersionByte = Contents[Offset];
427 uint8_t Version = VersionByte & 0x07;
428
429 if (Version == 3) {
430 ArrayRef<uint8_t> RawData = Contents.slice(N: Offset);
431 printUnwindInfoV3(Ctx, Section: XData, Offset, Data: RawData);
432 } else {
433 const auto UI =
434 reinterpret_cast<const UnwindInfo *>(Contents.data() + Offset);
435 printUnwindInfo(Ctx, Section: XData, Offset, UI: *UI);
436 }
437}
438
439static void printDecodedWOD(ScopedPrinter &SW, raw_ostream &OS,
440 const DecodedWOD &W) {
441 switch (W.Opcode) {
442 case WOD_PUSH:
443 OS << "PUSH Reg=" << getRegisterNameV3(Reg: W.Register);
444 break;
445 case WOD_PUSH2:
446 OS << "PUSH2 Reg1=" << getRegisterNameV3(Reg: W.Register)
447 << ", Reg2=" << getRegisterNameV3(Reg: W.Register2);
448 break;
449 case WOD_PUSH_CONSECUTIVE_2:
450 OS << "PUSH_CONSECUTIVE_2 Reg=" << getRegisterNameV3(Reg: W.Register) << " (+"
451 << getRegisterNameV3(Reg: W.Register + 1) << ")";
452 break;
453 case WOD_ALLOC_SMALL:
454 OS << format(Fmt: "ALLOC_SMALL Size=0x%X", Vals: W.Size);
455 break;
456 case WOD_ALLOC_LARGE:
457 OS << format(Fmt: "ALLOC_LARGE Size=0x%X", Vals: W.Size);
458 break;
459 case WOD_ALLOC_HUGE:
460 OS << format(Fmt: "ALLOC_HUGE Size=0x%X", Vals: W.Size);
461 break;
462 case WOD_SET_FPREG:
463 OS << "SET_FPREG Reg=" << getRegisterNameV3(Reg: W.Register)
464 << format(Fmt: ", Offset=0x%X", Vals: W.Displacement);
465 break;
466 case WOD_SAVE_NONVOL:
467 OS << "SAVE_NONVOL Reg=" << getRegisterNameV3(Reg: W.Register)
468 << format(Fmt: ", Disp=0x%X", Vals: W.Displacement);
469 break;
470 case WOD_SAVE_NONVOL_FAR:
471 OS << "SAVE_NONVOL_FAR Reg=" << getRegisterNameV3(Reg: W.Register)
472 << format(Fmt: ", Disp=0x%X", Vals: W.Displacement);
473 break;
474 case WOD_SAVE_XMM128:
475 OS << "SAVE_XMM128 Reg=XMM" << static_cast<unsigned>(W.Register)
476 << format(Fmt: ", Disp=0x%X", Vals: W.Displacement);
477 break;
478 case WOD_SAVE_XMM128_FAR:
479 OS << "SAVE_XMM128_FAR Reg=XMM" << static_cast<unsigned>(W.Register)
480 << format(Fmt: ", Disp=0x%X", Vals: W.Displacement);
481 break;
482 case WOD_PUSH_CANONICAL_FRAME:
483 // TODO: When the Windows x64 Unwind V3 spec is finalized, replace this
484 // raw Type value with a descriptive name. Type values are defined by the
485 // OS (see the Windows SDK headers) but the set is not yet stable.
486 OS << "PUSH_CANONICAL_FRAME Type=" << static_cast<unsigned>(W.Type);
487 break;
488 }
489}
490
491/// Decode and print N WODs from the pool starting at byte offset PoolOffset,
492/// pairing each with the corresponding IP offset from IpOffsets.
493static void printWODSequence(ScopedPrinter &SW, raw_ostream &OS,
494 ArrayRef<uint8_t> WODPool, unsigned PoolOffset,
495 ArrayRef<uint16_t> IpOffsets, unsigned Count) {
496 unsigned CurrentOffset = PoolOffset;
497 for (unsigned I = 0; I < Count; ++I) {
498 Expected<DecodedWOD> WOrErr = decodeWOD(Pool: WODPool, Offset: CurrentOffset);
499 if (!WOrErr) {
500 WithColor::warning(OS&: errs()) << toString(E: WOrErr.takeError()) << "\n";
501 return;
502 }
503 const DecodedWOD &W = *WOrErr;
504 SW.startLine() << format(Fmt: "[%u] IP +0x%04X: ", Vals: I,
505 Vals: I < IpOffsets.size() ? IpOffsets[I] : 0);
506 printDecodedWOD(SW, OS, W);
507 OS << "\n";
508 CurrentOffset += W.ByteSize;
509 }
510}
511
512void Dumper::printUnwindInfoV3(const Context &Ctx,
513 const object::coff_section *Section,
514 off_t Offset, ArrayRef<uint8_t> Data) {
515 DictScope UIS(SW, "UnwindInfo");
516
517 Expected<DecodedUnwindInfoV3> InfoOrErr = decodeUnwindInfoV3(Data);
518 if (!InfoOrErr) {
519 WithColor::warning(OS&: errs()) << toString(E: InfoOrErr.takeError()) << "\n";
520 return;
521 }
522 const DecodedUnwindInfoV3 &Info = *InfoOrErr;
523
524 SW.printNumber(Label: "Version", Value: Info.Version);
525 SW.printFlags(Label: "Flags", Value: Info.Flags, Flags: EnumStrings(UnwindFlags));
526 SW.printHex(Label: "SizeOfProlog", Value: Info.SizeOfProlog);
527 SW.printNumber(Label: "PayloadWords", Value: Info.PayloadWords);
528 SW.printNumber(Label: "NumberOfOps", Value: Info.NumberOfOps);
529 SW.printNumber(Label: "NumberOfEpilogs", Value: Info.NumberOfEpilogs);
530
531 // Validation: SizeOfProlog must be >= first (largest) prolog IP offset.
532 // SizeOfProlog is the total prolog size in bytes, while the first IP offset
533 // is the start of the last unwind-affecting instruction within the prolog.
534 if (Info.NumberOfOps > 0 && Info.SizeOfProlog < Info.PrologIpOffsets[0]) {
535 WithColor::warning(OS&: errs())
536 << format(Fmt: "SizeOfProlog (%u) is smaller than first prolog IP offset "
537 "(%u)\n",
538 Vals: Info.SizeOfProlog, Vals: Info.PrologIpOffsets[0]);
539 }
540
541 // Per the V3 spec, Flags bit 4 (0x10) is reserved and must be zero. Warn
542 // (rather than error) so we stay forward-compatible if Microsoft later
543 // defines this bit.
544 if (Info.Flags & 0x10)
545 WithColor::warning(OS&: errs())
546 << "V3 unwind info has reserved Flags bit 4 set\n";
547
548 // Print prolog ops
549 {
550 SW.startLine() << format(Fmt: "Prolog [%u ops]:\n", Vals: Info.NumberOfOps);
551 SW.indent();
552 printWODSequence(SW, OS, WODPool: Info.WODPool, PoolOffset: 0, IpOffsets: ArrayRef(Info.PrologIpOffsets),
553 Count: Info.NumberOfOps);
554 SW.unindent();
555 }
556
557 // Print epilog descriptors
558 uint8_t BaseEpilogFlags = 0;
559 bool HaveBaseEpilog = false;
560 for (unsigned I = 0; I < Info.NumberOfEpilogs; ++I) {
561 const DecodedEpilogV3 &Epi = Info.Epilogs[I];
562
563 DictScope ES(SW, formatv(Fmt: "Epilog [{0}]", Vals&: I).str());
564 SW.printFlags(Label: "Flags", Value: Epi.Flags, Flags: EnumStrings(EpilogFlags));
565 // Format the signed EpilogOffset as hex with explicit sign so negative
566 // tail-relative offsets remain readable (e.g. "-0x14" rather than
567 // "0xFFFFFFEC").
568 {
569 int32_t SignedOff = static_cast<int32_t>(Epi.EpilogOffset);
570 uint32_t AbsOff =
571 SignedOff < 0
572 ? static_cast<uint32_t>(-static_cast<int64_t>(SignedOff))
573 : static_cast<uint32_t>(SignedOff);
574 SW.printString(
575 Label: "EpilogOffset",
576 Value: formatv(Fmt: "{0}0x{1:X-}", Vals: SignedOff < 0 ? "-" : "+", Vals&: AbsOff).str());
577 }
578 SW.printNumber(Label: "NumberOfOps", Value: Epi.NumberOfOps);
579
580 if (Epi.NumberOfOps == 0) {
581 if (I == 0) {
582 WithColor::warning(OS&: errs())
583 << "first epilog cannot inherit (NumberOfOps=0)\n";
584 } else {
585 // Per the V3 spec, Flags bits 0 and 1 are producer-replicated into an
586 // inherited descriptor, so they must match the base epilog. Warn if a
587 // non-compliant producer left them inconsistent.
588 if (HaveBaseEpilog && (Epi.Flags & 0x03) != (BaseEpilogFlags & 0x03))
589 WithColor::warning(OS&: errs())
590 << format(Fmt: "inherited epilog flags (0x%X) do not match base "
591 "epilog flags (0x%X)\n",
592 Vals: Epi.Flags & 0x03, Vals: BaseEpilogFlags & 0x03);
593 // Surface the values inherited from the base epilog so a
594 // reader can see what the unwinder will actually execute.
595 SW.startLine() << format(
596 Fmt: "(inherits from base epilog: FirstOp=0x%X, "
597 "IpOffsetOfLastInstruction=0x%X, %u ops)\n",
598 Vals: Epi.FirstOp, Vals: static_cast<unsigned>(Epi.IpOffsetOfLastInstruction),
599 Vals: static_cast<unsigned>(Epi.IpOffsets.size()));
600 }
601 } else {
602 SW.printHex(Label: "FirstOp", Value: Epi.FirstOp);
603 SW.printHex(Label: "IpOffsetOfLastInstruction", Value: Epi.IpOffsetOfLastInstruction);
604 printWODSequence(SW, OS, WODPool: Info.WODPool, PoolOffset: Epi.FirstOp,
605 IpOffsets: ArrayRef(Epi.IpOffsets), Count: Epi.NumberOfOps);
606 // This is a full descriptor; it becomes the base that subsequent
607 // inherited descriptors replicate their flags from.
608 BaseEpilogFlags = Epi.Flags;
609 HaveBaseEpilog = true;
610 }
611 }
612
613 // Optionally dump the WOD pool with byte offsets. This is useful for
614 // understanding how WODs are shared between the prolog and epilogs but is
615 // normally redundant with the per-prolog / per-epilog decoded output, so
616 // it's gated behind --unwind-show-wod-pool.
617 if (opts::UnwindShowWODPool) {
618 ListScope WS(SW, formatv(Fmt: "WODPool [{0} bytes]", Vals: Info.WODPool.size()).str());
619 unsigned PoolOffset = 0;
620 while (PoolOffset < Info.WODPool.size()) {
621 // PayloadWords counts 2-byte words, so the pool may have a single
622 // trailing zero padding byte to round up to a word boundary. A bare
623 // 0x00 byte is never a valid 1-byte WOD (WOD_ALLOC_SMALL requires the
624 // low nibble to be 8), so treat a final zero byte as padding rather
625 // than trying to decode it.
626 if (PoolOffset + 1 == Info.WODPool.size() &&
627 Info.WODPool[PoolOffset] == 0) {
628 SW.startLine() << format(Fmt: "+0x%04X: (padding)\n", Vals: PoolOffset);
629 break;
630 }
631 Expected<DecodedWOD> WOrErr = decodeWOD(Pool: Info.WODPool, Offset: PoolOffset);
632 if (!WOrErr) {
633 WithColor::warning(OS&: errs()) << toString(E: WOrErr.takeError()) << "\n";
634 break;
635 }
636 const DecodedWOD &W = *WOrErr;
637 SW.startLine() << format(Fmt: "+0x%04X: ", Vals: PoolOffset);
638 printDecodedWOD(SW, OS, W);
639 OS << "\n";
640 PoolOffset += W.ByteSize;
641 }
642 }
643
644 // Handle exception handler / chain info
645 uint64_t LSDAOffset = Offset + Info.PayloadSize;
646 if (Info.Flags & (UNW_ExceptionHandler | UNW_TerminateHandler)) {
647 if (LSDAOffset + 4 <= Data.size() + Offset) {
648 uint32_t HandlerRVA = support::endian::read32le(P: &Data[Info.PayloadSize]);
649 SW.printString(Label: "Handler",
650 Value: formatSymbol(Ctx, Section, Offset: LSDAOffset, Displacement: HandlerRVA));
651 }
652 } else if (Info.Flags & UNW_ChainInfo) {
653 if (LSDAOffset + sizeof(RuntimeFunction) <= Data.size() + Offset) {
654 const auto *Chained =
655 reinterpret_cast<const RuntimeFunction *>(&Data[Info.PayloadSize]);
656 DictScope CS(SW, "Chained");
657 printRuntimeFunctionEntry(Ctx, Section, Offset: LSDAOffset, RF: *Chained);
658 }
659 }
660}
661
662void Dumper::printData(const Context &Ctx) {
663 for (const auto &Section : Ctx.COFF.sections()) {
664 StringRef Name;
665 if (Expected<StringRef> NameOrErr = Section.getName())
666 Name = *NameOrErr;
667 else
668 consumeError(Err: NameOrErr.takeError());
669
670 if (Name != ".pdata" && !Name.starts_with(Prefix: ".pdata$"))
671 continue;
672
673 const coff_section *PData = Ctx.COFF.getCOFFSection(Section);
674 ArrayRef<uint8_t> Contents;
675
676 if (Error E = Ctx.COFF.getSectionContents(Sec: PData, Res&: Contents))
677 reportError(Err: std::move(E), Input: Ctx.COFF.getFileName());
678 if (Contents.empty())
679 continue;
680
681 const RuntimeFunction *Entries =
682 reinterpret_cast<const RuntimeFunction *>(Contents.data());
683 const size_t Count = Contents.size() / sizeof(RuntimeFunction);
684 ArrayRef<RuntimeFunction> RuntimeFunctions(Entries, Count);
685
686 size_t Index = 0;
687 for (const auto &RF : RuntimeFunctions) {
688 printRuntimeFunction(Ctx, Section: Ctx.COFF.getCOFFSection(Section),
689 SectionOffset: Index * sizeof(RuntimeFunction), RF);
690 ++Index;
691 }
692 }
693}
694}
695}
696
697