| //===- Win64EHDumper.cpp - Win64 EH Printer ---------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "Win64EHDumper.h" |
| #include "llvm-readobj.h" |
| #include "llvm/ADT/Enum.h" |
| #include "llvm/Object/COFF.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/Format.h" |
| #include "llvm/Support/FormatVariadic.h" |
| #include "llvm/Support/WithColor.h" |
| |
| using namespace llvm; |
| using namespace llvm::object; |
| using namespace llvm::Win64EH; |
| |
| // clang-format off |
| constexpr EnumStringDef<unsigned> UnwindFlagDefs[] = { |
| {{"ExceptionHandler"}, UNW_ExceptionHandler}, |
| {{"TerminateHandler"}, UNW_TerminateHandler}, |
| {{"ChainInfo"} , UNW_ChainInfo }, |
| {{"Large"} , UNW_FlagLarge } |
| }; |
| constexpr auto UnwindFlags = BUILD_ENUM_STRINGS(UnwindFlagDefs); |
| |
| constexpr EnumStringDef<unsigned> EpilogFlagDefs[] = { |
| {{"ParentFragmentTransfer"}, EPILOG_PARENT_FRAGMENT_TRANSFER}, |
| {{"Large"} , EPILOG_INFO_LARGE } |
| }; |
| constexpr auto EpilogFlags = BUILD_ENUM_STRINGS(EpilogFlagDefs); |
| |
| constexpr EnumStringDef<unsigned> UnwindOpInfoDefs[] = { |
| {{"RAX"}, 0}, |
| {{"RCX"}, 1}, |
| {{"RDX"}, 2}, |
| {{"RBX"}, 3}, |
| {{"RSP"}, 4}, |
| {{"RBP"}, 5}, |
| {{"RSI"}, 6}, |
| {{"RDI"}, 7}, |
| {{"R8"}, 8}, |
| {{"R9"}, 9}, |
| {{"R10"}, 10}, |
| {{"R11"}, 11}, |
| {{"R12"}, 12}, |
| {{"R13"}, 13}, |
| {{"R14"}, 14}, |
| {{"R15"}, 15} |
| }; |
| constexpr auto UnwindOpInfo = BUILD_ENUM_STRINGS(UnwindOpInfoDefs); |
| // clang-format on |
| |
| static uint64_t getOffsetOfLSDA(const UnwindInfo& UI) { |
| return static_cast<const char*>(UI.getLanguageSpecificData()) |
| - reinterpret_cast<const char*>(&UI); |
| } |
| |
| static uint32_t getLargeSlotValue(ArrayRef<UnwindCode> UC) { |
| if (UC.size() < 3) |
| return 0; |
| return UC[1].FrameOffset + (static_cast<uint32_t>(UC[2].FrameOffset) << 16); |
| } |
| |
| // Returns the name of the unwind code. |
| static StringRef getUnwindCodeTypeName(uint8_t Code) { |
| switch (Code) { |
| default: llvm_unreachable("Invalid unwind code"); |
| case UOP_PushNonVol: return "PUSH_NONVOL"; |
| case UOP_AllocLarge: return "ALLOC_LARGE"; |
| case UOP_AllocSmall: return "ALLOC_SMALL"; |
| case UOP_SetFPReg: return "SET_FPREG"; |
| case UOP_SaveNonVol: return "SAVE_NONVOL"; |
| case UOP_SaveNonVolBig: return "SAVE_NONVOL_FAR"; |
| case UOP_SaveXMM128: return "SAVE_XMM128"; |
| case UOP_SaveXMM128Big: return "SAVE_XMM128_FAR"; |
| case UOP_PushMachFrame: return "PUSH_MACHFRAME"; |
| case UOP_Epilog: |
| return "EPILOG"; |
| } |
| } |
| |
| // Returns the name of a referenced register. |
| static StringRef getUnwindRegisterName(uint8_t Reg) { |
| switch (Reg) { |
| default: llvm_unreachable("Invalid register"); |
| case 0: return "RAX"; |
| case 1: return "RCX"; |
| case 2: return "RDX"; |
| case 3: return "RBX"; |
| case 4: return "RSP"; |
| case 5: return "RBP"; |
| case 6: return "RSI"; |
| case 7: return "RDI"; |
| case 8: return "R8"; |
| case 9: return "R9"; |
| case 10: return "R10"; |
| case 11: return "R11"; |
| case 12: return "R12"; |
| case 13: return "R13"; |
| case 14: return "R14"; |
| case 15: return "R15"; |
| } |
| } |
| |
| // Calculates the number of array slots required for the unwind code. |
| static unsigned getNumUsedSlots(const UnwindCode &UnwindCode) { |
| switch (UnwindCode.getUnwindOp()) { |
| default: llvm_unreachable("Invalid unwind code"); |
| case UOP_PushNonVol: |
| case UOP_AllocSmall: |
| case UOP_SetFPReg: |
| case UOP_PushMachFrame: |
| case UOP_Epilog: |
| return 1; |
| case UOP_SaveNonVol: |
| case UOP_SaveXMM128: |
| return 2; |
| case UOP_SaveNonVolBig: |
| case UOP_SaveXMM128Big: |
| return 3; |
| case UOP_AllocLarge: |
| return (UnwindCode.getOpInfo() == 0) ? 2 : 3; |
| } |
| } |
| |
| static std::error_code getSymbol(const COFFObjectFile &COFF, uint64_t VA, |
| object::SymbolRef &Sym) { |
| for (const auto &Symbol : COFF.symbols()) { |
| Expected<uint64_t> Address = Symbol.getAddress(); |
| if (!Address) |
| return errorToErrorCode(Address.takeError()); |
| if (*Address == VA) { |
| Sym = Symbol; |
| return std::error_code(); |
| } |
| } |
| return inconvertibleErrorCode(); |
| } |
| |
| static object::SymbolRef getPreferredSymbol(const COFFObjectFile &COFF, |
| object::SymbolRef Sym, |
| uint32_t &SymbolOffset, |
| bool IsRangeEnd) { |
| // The symbol resolved by ResolveSymbol can be any internal |
| // nondescriptive symbol; try to resolve a more descriptive one. |
| COFFSymbolRef CoffSym = COFF.getCOFFSymbol(Sym); |
| if (CoffSym.getStorageClass() != COFF::IMAGE_SYM_CLASS_LABEL && |
| CoffSym.getSectionDefinition() == nullptr) |
| return Sym; |
| for (const auto &S : COFF.symbols()) { |
| COFFSymbolRef CS = COFF.getCOFFSymbol(S); |
| if (CS.getSectionNumber() == CoffSym.getSectionNumber() && |
| CS.getValue() <= CoffSym.getValue() + SymbolOffset && |
| CS.getStorageClass() != COFF::IMAGE_SYM_CLASS_LABEL && |
| CS.getSectionDefinition() == nullptr) { |
| uint32_t Offset = CoffSym.getValue() + SymbolOffset - CS.getValue(); |
| // For the end of a range, don't pick a symbol with a zero offset; |
| // prefer a symbol with a small positive offset. |
| if (Offset <= SymbolOffset && (!IsRangeEnd || Offset > 0)) { |
| SymbolOffset = Offset; |
| Sym = S; |
| CoffSym = CS; |
| if (CS.isExternal() && SymbolOffset == 0) |
| return Sym; |
| } |
| } |
| } |
| return Sym; |
| } |
| |
| static std::string formatSymbol(const Dumper::Context &Ctx, |
| const coff_section *Section, uint64_t Offset, |
| uint32_t Displacement, |
| bool IsRangeEnd = false) { |
| std::string Buffer; |
| raw_string_ostream OS(Buffer); |
| |
| SymbolRef Symbol; |
| if (!Ctx.ResolveSymbol(Section, Offset, Symbol, Ctx.UserData)) { |
| // We found a relocation at the given offset in the section, pointing |
| // at a symbol. |
| |
| // Try to resolve label/section symbols into function names. |
| Symbol = getPreferredSymbol(Ctx.COFF, Symbol, Displacement, IsRangeEnd); |
| |
| Expected<StringRef> Name = Symbol.getName(); |
| if (Name) { |
| OS << *Name; |
| if (Displacement > 0) |
| OS << format(" +0x%X (0x%" PRIX64 ")", Displacement, Offset); |
| else |
| OS << format(" (0x%" PRIX64 ")", Offset); |
| return OS.str(); |
| } else { |
| // TODO: Actually report errors helpfully. |
| consumeError(Name.takeError()); |
| } |
| } else if (!getSymbol(Ctx.COFF, Ctx.COFF.getImageBase() + Displacement, |
| Symbol)) { |
| Expected<StringRef> Name = Symbol.getName(); |
| if (Name) { |
| OS << *Name; |
| OS << format(" (0x%" PRIX64 ")", Ctx.COFF.getImageBase() + Displacement); |
| return OS.str(); |
| } else { |
| consumeError(Name.takeError()); |
| } |
| } |
| |
| if (Displacement > 0) |
| OS << format("(0x%" PRIX64 ")", Ctx.COFF.getImageBase() + Displacement); |
| else |
| OS << format("(0x%" PRIX64 ")", Offset); |
| return OS.str(); |
| } |
| |
| static std::error_code resolveRelocation(const Dumper::Context &Ctx, |
| const coff_section *Section, |
| uint64_t Offset, |
| const coff_section *&ResolvedSection, |
| uint64_t &ResolvedAddress) { |
| SymbolRef Symbol; |
| if (std::error_code EC = |
| Ctx.ResolveSymbol(Section, Offset, Symbol, Ctx.UserData)) |
| return EC; |
| |
| Expected<uint64_t> ResolvedAddressOrErr = Symbol.getAddress(); |
| if (!ResolvedAddressOrErr) |
| return errorToErrorCode(ResolvedAddressOrErr.takeError()); |
| ResolvedAddress = *ResolvedAddressOrErr; |
| |
| Expected<section_iterator> SI = Symbol.getSection(); |
| if (!SI) |
| return errorToErrorCode(SI.takeError()); |
| ResolvedSection = Ctx.COFF.getCOFFSection(**SI); |
| return std::error_code(); |
| } |
| |
| static const object::coff_section * |
| getSectionContaining(const COFFObjectFile &COFF, uint64_t VA) { |
| for (const auto &Section : COFF.sections()) { |
| uint64_t Address = Section.getAddress(); |
| uint64_t Size = Section.getSize(); |
| |
| if (VA >= Address && (VA - Address) <= Size) |
| return COFF.getCOFFSection(Section); |
| } |
| return nullptr; |
| } |
| |
| namespace llvm { |
| namespace Win64EH { |
| void Dumper::printRuntimeFunctionEntry(const Context &Ctx, |
| const coff_section *Section, |
| uint64_t Offset, |
| const RuntimeFunction &RF) { |
| SW.printString("StartAddress", |
| formatSymbol(Ctx, Section, Offset + 0, RF.StartAddress)); |
| SW.printString("EndAddress", |
| formatSymbol(Ctx, Section, Offset + 4, RF.EndAddress, |
| /*IsRangeEnd=*/true)); |
| SW.printString("UnwindInfoAddress", |
| formatSymbol(Ctx, Section, Offset + 8, RF.UnwindInfoOffset)); |
| } |
| |
| // Prints one unwind code. Because an unwind code can occupy up to 3 slots in |
| // the unwind codes array, this function requires that the correct number of |
| // slots is provided. |
| void Dumper::printUnwindCode(const UnwindInfo &UI, ArrayRef<UnwindCode> UC, |
| bool &SeenFirstEpilog) { |
| assert(UC.size() >= getNumUsedSlots(UC[0])); |
| |
| SW.startLine() << format("0x%02X: ", unsigned(UC[0].u.CodeOffset)) |
| << getUnwindCodeTypeName(UC[0].getUnwindOp()); |
| |
| switch (UC[0].getUnwindOp()) { |
| case UOP_PushNonVol: |
| OS << " reg=" << getUnwindRegisterName(UC[0].getOpInfo()); |
| break; |
| |
| case UOP_AllocLarge: |
| OS << " size=" |
| << ((UC[0].getOpInfo() == 0) ? UC[1].FrameOffset * 8 |
| : getLargeSlotValue(UC)); |
| break; |
| |
| case UOP_AllocSmall: |
| OS << " size=" << (UC[0].getOpInfo() + 1) * 8; |
| break; |
| |
| case UOP_SetFPReg: |
| if (UI.getFrameRegister() == 0) |
| OS << " reg=<invalid>"; |
| else |
| OS << " reg=" << getUnwindRegisterName(UI.getFrameRegister()) |
| << format(", offset=0x%X", UI.getFrameOffset() * 16); |
| break; |
| |
| case UOP_SaveNonVol: |
| OS << " reg=" << getUnwindRegisterName(UC[0].getOpInfo()) |
| << format(", offset=0x%X", UC[1].FrameOffset * 8); |
| break; |
| |
| case UOP_SaveNonVolBig: |
| OS << " reg=" << getUnwindRegisterName(UC[0].getOpInfo()) |
| << format(", offset=0x%X", getLargeSlotValue(UC)); |
| break; |
| |
| case UOP_SaveXMM128: |
| OS << " reg=XMM" << static_cast<uint32_t>(UC[0].getOpInfo()) |
| << format(", offset=0x%X", UC[1].FrameOffset * 16); |
| break; |
| |
| case UOP_SaveXMM128Big: |
| OS << " reg=XMM" << static_cast<uint32_t>(UC[0].getOpInfo()) |
| << format(", offset=0x%X", getLargeSlotValue(UC)); |
| break; |
| |
| case UOP_PushMachFrame: |
| OS << " errcode=" << (UC[0].getOpInfo() == 0 ? "no" : "yes"); |
| break; |
| |
| case UOP_Epilog: |
| if (SeenFirstEpilog) { |
| uint32_t Offset = UC[0].getEpilogOffset(); |
| if (Offset == 0) { |
| OS << " padding"; |
| } else { |
| OS << " offset=" << format("0x%X", Offset); |
| } |
| } else { |
| SeenFirstEpilog = true; |
| bool AtEnd = (UC[0].getOpInfo() & 0x1) != 0; |
| uint32_t Length = UC[0].u.CodeOffset; |
| OS << " atend=" << (AtEnd ? "yes" : "no") |
| << ", length=" << format("0x%X", Length); |
| } |
| break; |
| } |
| |
| OS << "\n"; |
| } |
| |
| void Dumper::printUnwindInfo(const Context &Ctx, const coff_section *Section, |
| off_t Offset, const UnwindInfo &UI) { |
| DictScope UIS(SW, "UnwindInfo"); |
| SW.printNumber("Version", UI.getVersion()); |
| SW.printFlags("Flags", UI.getFlags(), EnumStrings(UnwindFlags)); |
| SW.printNumber("PrologSize", UI.PrologSize); |
| if (UI.getFrameRegister()) { |
| SW.printEnum("FrameRegister", UI.getFrameRegister(), |
| EnumStrings(UnwindOpInfo)); |
| SW.printHex("FrameOffset", UI.getFrameOffset()); |
| } else { |
| SW.printString("FrameRegister", StringRef("-")); |
| SW.printString("FrameOffset", StringRef("-")); |
| } |
| |
| SW.printNumber("UnwindCodeCount", UI.NumCodes); |
| { |
| ListScope UCS(SW, "UnwindCodes"); |
| ArrayRef<UnwindCode> UC(&UI.UnwindCodes[0], UI.NumCodes); |
| bool SeenFirstEpilog = false; |
| for (const UnwindCode *UCI = UC.begin(), *UCE = UC.end(); UCI < UCE; ++UCI) { |
| unsigned UsedSlots = getNumUsedSlots(*UCI); |
| if (UsedSlots > UC.size()) { |
| errs() << "corrupt unwind data"; |
| return; |
| } |
| |
| printUnwindCode(UI, ArrayRef(UCI, UCE), SeenFirstEpilog); |
| UCI = UCI + UsedSlots - 1; |
| } |
| } |
| |
| uint64_t LSDAOffset = Offset + getOffsetOfLSDA(UI); |
| if (UI.getFlags() & (UNW_ExceptionHandler | UNW_TerminateHandler)) { |
| SW.printString("Handler", |
| formatSymbol(Ctx, Section, LSDAOffset, |
| UI.getLanguageSpecificHandlerOffset())); |
| } else if (UI.getFlags() & UNW_ChainInfo) { |
| if (const RuntimeFunction *Chained = UI.getChainedFunctionEntry()) { |
| DictScope CS(SW, "Chained"); |
| printRuntimeFunctionEntry(Ctx, Section, LSDAOffset, *Chained); |
| } |
| } |
| } |
| |
| void Dumper::printRuntimeFunction(const Context &Ctx, |
| const coff_section *Section, |
| uint64_t SectionOffset, |
| const RuntimeFunction &RF) { |
| DictScope RFS(SW, "RuntimeFunction"); |
| printRuntimeFunctionEntry(Ctx, Section, SectionOffset, RF); |
| |
| const coff_section *XData = nullptr; |
| uint64_t Offset; |
| resolveRelocation(Ctx, Section, SectionOffset + 8, XData, Offset); |
| Offset = Offset + RF.UnwindInfoOffset; |
| |
| if (!XData) { |
| uint64_t Address = Ctx.COFF.getImageBase() + RF.UnwindInfoOffset; |
| XData = getSectionContaining(Ctx.COFF, Address); |
| if (!XData) |
| return; |
| Offset = RF.UnwindInfoOffset - XData->VirtualAddress; |
| } |
| |
| ArrayRef<uint8_t> Contents; |
| if (Error E = Ctx.COFF.getSectionContents(XData, Contents)) |
| reportError(std::move(E), Ctx.COFF.getFileName()); |
| |
| if (Contents.empty()) |
| return; |
| |
| if (Offset > Contents.size()) |
| return; |
| |
| // Check version before casting to UnwindInfo struct. |
| // Only byte 0 (VersionAndFlags) is layout-compatible between V1/V2 and V3. |
| uint8_t VersionByte = Contents[Offset]; |
| uint8_t Version = VersionByte & 0x07; |
| |
| if (Version == 3) { |
| ArrayRef<uint8_t> RawData = Contents.slice(Offset); |
| printUnwindInfoV3(Ctx, XData, Offset, RawData); |
| } else { |
| const auto UI = |
| reinterpret_cast<const UnwindInfo *>(Contents.data() + Offset); |
| printUnwindInfo(Ctx, XData, Offset, *UI); |
| } |
| } |
| |
| static void printDecodedWOD(ScopedPrinter &SW, raw_ostream &OS, |
| const DecodedWOD &W) { |
| switch (W.Opcode) { |
| case WOD_PUSH: |
| OS << "PUSH Reg=" << getRegisterNameV3(W.Register); |
| break; |
| case WOD_PUSH2: |
| OS << "PUSH2 Reg1=" << getRegisterNameV3(W.Register) |
| << ", Reg2=" << getRegisterNameV3(W.Register2); |
| break; |
| case WOD_PUSH_CONSECUTIVE_2: |
| OS << "PUSH_CONSECUTIVE_2 Reg=" << getRegisterNameV3(W.Register) << " (+" |
| << getRegisterNameV3(W.Register + 1) << ")"; |
| break; |
| case WOD_ALLOC_SMALL: |
| OS << format("ALLOC_SMALL Size=0x%X", W.Size); |
| break; |
| case WOD_ALLOC_LARGE: |
| OS << format("ALLOC_LARGE Size=0x%X", W.Size); |
| break; |
| case WOD_ALLOC_HUGE: |
| OS << format("ALLOC_HUGE Size=0x%X", W.Size); |
| break; |
| case WOD_SET_FPREG: |
| OS << "SET_FPREG Reg=" << getRegisterNameV3(W.Register) |
| << format(", Offset=0x%X", W.Displacement); |
| break; |
| case WOD_SAVE_NONVOL: |
| OS << "SAVE_NONVOL Reg=" << getRegisterNameV3(W.Register) |
| << format(", Disp=0x%X", W.Displacement); |
| break; |
| case WOD_SAVE_NONVOL_FAR: |
| OS << "SAVE_NONVOL_FAR Reg=" << getRegisterNameV3(W.Register) |
| << format(", Disp=0x%X", W.Displacement); |
| break; |
| case WOD_SAVE_XMM128: |
| OS << "SAVE_XMM128 Reg=XMM" << static_cast<unsigned>(W.Register) |
| << format(", Disp=0x%X", W.Displacement); |
| break; |
| case WOD_SAVE_XMM128_FAR: |
| OS << "SAVE_XMM128_FAR Reg=XMM" << static_cast<unsigned>(W.Register) |
| << format(", Disp=0x%X", W.Displacement); |
| break; |
| case WOD_PUSH_CANONICAL_FRAME: |
| // TODO: When the Windows x64 Unwind V3 spec is finalized, replace this |
| // raw Type value with a descriptive name. Type values are defined by the |
| // OS (see the Windows SDK headers) but the set is not yet stable. |
| OS << "PUSH_CANONICAL_FRAME Type=" << static_cast<unsigned>(W.Type); |
| break; |
| } |
| } |
| |
| /// Decode and print N WODs from the pool starting at byte offset PoolOffset, |
| /// pairing each with the corresponding IP offset from IpOffsets. |
| static void printWODSequence(ScopedPrinter &SW, raw_ostream &OS, |
| ArrayRef<uint8_t> WODPool, unsigned PoolOffset, |
| ArrayRef<uint16_t> IpOffsets, unsigned Count) { |
| unsigned CurrentOffset = PoolOffset; |
| for (unsigned I = 0; I < Count; ++I) { |
| Expected<DecodedWOD> WOrErr = decodeWOD(WODPool, CurrentOffset); |
| if (!WOrErr) { |
| WithColor::warning(errs()) << toString(WOrErr.takeError()) << "\n"; |
| return; |
| } |
| const DecodedWOD &W = *WOrErr; |
| SW.startLine() << format("[%u] IP +0x%04X: ", I, |
| I < IpOffsets.size() ? IpOffsets[I] : 0); |
| printDecodedWOD(SW, OS, W); |
| OS << "\n"; |
| CurrentOffset += W.ByteSize; |
| } |
| } |
| |
| void Dumper::printUnwindInfoV3(const Context &Ctx, |
| const object::coff_section *Section, |
| off_t Offset, ArrayRef<uint8_t> Data) { |
| DictScope UIS(SW, "UnwindInfo"); |
| |
| Expected<DecodedUnwindInfoV3> InfoOrErr = decodeUnwindInfoV3(Data); |
| if (!InfoOrErr) { |
| WithColor::warning(errs()) << toString(InfoOrErr.takeError()) << "\n"; |
| return; |
| } |
| const DecodedUnwindInfoV3 &Info = *InfoOrErr; |
| |
| SW.printNumber("Version", Info.Version); |
| SW.printFlags("Flags", Info.Flags, EnumStrings(UnwindFlags)); |
| SW.printHex("SizeOfProlog", Info.SizeOfProlog); |
| SW.printNumber("PayloadWords", Info.PayloadWords); |
| SW.printNumber("NumberOfOps", Info.NumberOfOps); |
| SW.printNumber("NumberOfEpilogs", Info.NumberOfEpilogs); |
| |
| // Validation: SizeOfProlog must be >= first (largest) prolog IP offset. |
| // SizeOfProlog is the total prolog size in bytes, while the first IP offset |
| // is the start of the last unwind-affecting instruction within the prolog. |
| if (Info.NumberOfOps > 0 && Info.SizeOfProlog < Info.PrologIpOffsets[0]) { |
| WithColor::warning(errs()) |
| << format("SizeOfProlog (%u) is smaller than first prolog IP offset " |
| "(%u)\n", |
| Info.SizeOfProlog, Info.PrologIpOffsets[0]); |
| } |
| |
| // Per the V3 spec, Flags bit 4 (0x10) is reserved and must be zero. Warn |
| // (rather than error) so we stay forward-compatible if Microsoft later |
| // defines this bit. |
| if (Info.Flags & 0x10) |
| WithColor::warning(errs()) |
| << "V3 unwind info has reserved Flags bit 4 set\n"; |
| |
| // Print prolog ops |
| { |
| SW.startLine() << format("Prolog [%u ops]:\n", Info.NumberOfOps); |
| SW.indent(); |
| printWODSequence(SW, OS, Info.WODPool, 0, ArrayRef(Info.PrologIpOffsets), |
| Info.NumberOfOps); |
| SW.unindent(); |
| } |
| |
| // Print epilog descriptors |
| uint8_t BaseEpilogFlags = 0; |
| bool HaveBaseEpilog = false; |
| for (unsigned I = 0; I < Info.NumberOfEpilogs; ++I) { |
| const DecodedEpilogV3 &Epi = Info.Epilogs[I]; |
| |
| DictScope ES(SW, formatv("Epilog [{0}]", I).str()); |
| SW.printFlags("Flags", Epi.Flags, EnumStrings(EpilogFlags)); |
| // Format the signed EpilogOffset as hex with explicit sign so negative |
| // tail-relative offsets remain readable (e.g. "-0x14" rather than |
| // "0xFFFFFFEC"). |
| { |
| int32_t SignedOff = static_cast<int32_t>(Epi.EpilogOffset); |
| uint32_t AbsOff = |
| SignedOff < 0 |
| ? static_cast<uint32_t>(-static_cast<int64_t>(SignedOff)) |
| : static_cast<uint32_t>(SignedOff); |
| SW.printString( |
| "EpilogOffset", |
| formatv("{0}0x{1:X-}", SignedOff < 0 ? "-" : "+", AbsOff).str()); |
| } |
| SW.printNumber("NumberOfOps", Epi.NumberOfOps); |
| |
| if (Epi.NumberOfOps == 0) { |
| if (I == 0) { |
| WithColor::warning(errs()) |
| << "first epilog cannot inherit (NumberOfOps=0)\n"; |
| } else { |
| // Per the V3 spec, Flags bits 0 and 1 are producer-replicated into an |
| // inherited descriptor, so they must match the base epilog. Warn if a |
| // non-compliant producer left them inconsistent. |
| if (HaveBaseEpilog && (Epi.Flags & 0x03) != (BaseEpilogFlags & 0x03)) |
| WithColor::warning(errs()) |
| << format("inherited epilog flags (0x%X) do not match base " |
| "epilog flags (0x%X)\n", |
| Epi.Flags & 0x03, BaseEpilogFlags & 0x03); |
| // Surface the values inherited from the base epilog so a |
| // reader can see what the unwinder will actually execute. |
| SW.startLine() << format( |
| "(inherits from base epilog: FirstOp=0x%X, " |
| "IpOffsetOfLastInstruction=0x%X, %u ops)\n", |
| Epi.FirstOp, static_cast<unsigned>(Epi.IpOffsetOfLastInstruction), |
| static_cast<unsigned>(Epi.IpOffsets.size())); |
| } |
| } else { |
| SW.printHex("FirstOp", Epi.FirstOp); |
| SW.printHex("IpOffsetOfLastInstruction", Epi.IpOffsetOfLastInstruction); |
| printWODSequence(SW, OS, Info.WODPool, Epi.FirstOp, |
| ArrayRef(Epi.IpOffsets), Epi.NumberOfOps); |
| // This is a full descriptor; it becomes the base that subsequent |
| // inherited descriptors replicate their flags from. |
| BaseEpilogFlags = Epi.Flags; |
| HaveBaseEpilog = true; |
| } |
| } |
| |
| // Optionally dump the WOD pool with byte offsets. This is useful for |
| // understanding how WODs are shared between the prolog and epilogs but is |
| // normally redundant with the per-prolog / per-epilog decoded output, so |
| // it's gated behind --unwind-show-wod-pool. |
| if (opts::UnwindShowWODPool) { |
| ListScope WS(SW, formatv("WODPool [{0} bytes]", Info.WODPool.size()).str()); |
| unsigned PoolOffset = 0; |
| while (PoolOffset < Info.WODPool.size()) { |
| // PayloadWords counts 2-byte words, so the pool may have a single |
| // trailing zero padding byte to round up to a word boundary. A bare |
| // 0x00 byte is never a valid 1-byte WOD (WOD_ALLOC_SMALL requires the |
| // low nibble to be 8), so treat a final zero byte as padding rather |
| // than trying to decode it. |
| if (PoolOffset + 1 == Info.WODPool.size() && |
| Info.WODPool[PoolOffset] == 0) { |
| SW.startLine() << format("+0x%04X: (padding)\n", PoolOffset); |
| break; |
| } |
| Expected<DecodedWOD> WOrErr = decodeWOD(Info.WODPool, PoolOffset); |
| if (!WOrErr) { |
| WithColor::warning(errs()) << toString(WOrErr.takeError()) << "\n"; |
| break; |
| } |
| const DecodedWOD &W = *WOrErr; |
| SW.startLine() << format("+0x%04X: ", PoolOffset); |
| printDecodedWOD(SW, OS, W); |
| OS << "\n"; |
| PoolOffset += W.ByteSize; |
| } |
| } |
| |
| // Handle exception handler / chain info |
| uint64_t LSDAOffset = Offset + Info.PayloadSize; |
| if (Info.Flags & (UNW_ExceptionHandler | UNW_TerminateHandler)) { |
| if (LSDAOffset + 4 <= Data.size() + Offset) { |
| uint32_t HandlerRVA = support::endian::read32le(&Data[Info.PayloadSize]); |
| SW.printString("Handler", |
| formatSymbol(Ctx, Section, LSDAOffset, HandlerRVA)); |
| } |
| } else if (Info.Flags & UNW_ChainInfo) { |
| if (LSDAOffset + sizeof(RuntimeFunction) <= Data.size() + Offset) { |
| const auto *Chained = |
| reinterpret_cast<const RuntimeFunction *>(&Data[Info.PayloadSize]); |
| DictScope CS(SW, "Chained"); |
| printRuntimeFunctionEntry(Ctx, Section, LSDAOffset, *Chained); |
| } |
| } |
| } |
| |
| void Dumper::printData(const Context &Ctx) { |
| for (const auto &Section : Ctx.COFF.sections()) { |
| StringRef Name; |
| if (Expected<StringRef> NameOrErr = Section.getName()) |
| Name = *NameOrErr; |
| else |
| consumeError(NameOrErr.takeError()); |
| |
| if (Name != ".pdata" && !Name.starts_with(".pdata$")) |
| continue; |
| |
| const coff_section *PData = Ctx.COFF.getCOFFSection(Section); |
| ArrayRef<uint8_t> Contents; |
| |
| if (Error E = Ctx.COFF.getSectionContents(PData, Contents)) |
| reportError(std::move(E), Ctx.COFF.getFileName()); |
| if (Contents.empty()) |
| continue; |
| |
| const RuntimeFunction *Entries = |
| reinterpret_cast<const RuntimeFunction *>(Contents.data()); |
| const size_t Count = Contents.size() / sizeof(RuntimeFunction); |
| ArrayRef<RuntimeFunction> RuntimeFunctions(Entries, Count); |
| |
| size_t Index = 0; |
| for (const auto &RF : RuntimeFunctions) { |
| printRuntimeFunction(Ctx, Ctx.COFF.getCOFFSection(Section), |
| Index * sizeof(RuntimeFunction), RF); |
| ++Index; |
| } |
| } |
| } |
| } |
| } |
| |