[win][x64] Updated llvm-objdump and llvm-readobj to be able to dump Windows x64 Unwind v3 information.#199120
[win][x64] Updated llvm-objdump and llvm-readobj to be able to dump Windows x64 Unwind v3 information.#199120dpaoliello wants to merge 1 commit into
llvm-objdump and llvm-readobj to be able to dump Windows x64 Unwind v3 information.#199120Conversation
…mp Windows x64 Unwind v3 information. Public docs: <https://learn.microsoft.com/en-us/cpp/build/x64-unwind-information-v3?view=msvc-170> The change adds complete WIndows x64 V3 unwind info decoding and printing support in LLVM, including new data structures, enums, and decoding functions to handle the different WOD opcodes and epilog descriptors. It also updates the dumping utilities (llvm-readobj and llvm-objdump) to correctly interpret V3 unwind info.
|
@llvm/pr-subscribers-platform-windows @llvm/pr-subscribers-llvm-support Author: Daniel Paoliello (dpaoliello) ChangesPublic docs: <https://learn.microsoft.com/en-us/cpp/build/x64-unwind-information-v3?view=msvc-170> The change adds Windows x64 unwind v3 info decoding and printing support in LLVM, including new data structures, enums, and decoding functions to handle the different WOD opcodes and epilog descriptors. It also updates the dumping utilities (llvm-readobj and llvm-objdump) to correctly interpret v3 unwind info. NOTE: This does not yet support large unwind or epilog info variants. Patch is 167.75 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/199120.diff 34 Files Affected:
diff --git a/llvm/include/llvm/Support/Win64EH.h b/llvm/include/llvm/Support/Win64EH.h
index ec3413b31ee4a..cfe30c2fd06d2 100644
--- a/llvm/include/llvm/Support/Win64EH.h
+++ b/llvm/include/llvm/Support/Win64EH.h
@@ -15,8 +15,12 @@
#ifndef LLVM_SUPPORT_WIN64EH_H
#define LLVM_SUPPORT_WIN64EH_H
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Support/DataTypes.h"
#include "llvm/Support/Endian.h"
+#include "llvm/Support/Error.h"
namespace llvm {
namespace Win64EH {
@@ -221,6 +225,78 @@ struct UnwindInfo {
}
};
+//===----------------------------------------------------------------------===//
+// V3 Unwind Information
+//===----------------------------------------------------------------------===//
+
+/// V3 Winding Operation Descriptor opcodes.
+enum WODOpcode : uint8_t {
+ WOD_SET_FPREG = 0, // 8-bit opcode, 2 bytes
+ WOD_ALLOC_HUGE = 1, // 8-bit opcode, 5 bytes
+ WOD_ALLOC_LARGE = 2, // 8-bit opcode, 3 bytes
+ WOD_PUSH_CANONICAL_FRAME = 3, // 8-bit opcode, 2 bytes
+ WOD_PUSH = 4, // 3-bit opcode, 1 byte
+ WOD_SAVE_NONVOL_FAR = 5, // 3-bit opcode, 5 bytes
+ WOD_SAVE_NONVOL = 6, // 3-bit opcode, 3 bytes
+ WOD_PUSH_CONSECUTIVE_2 = 7, // 3-bit opcode, 1 byte
+ WOD_ALLOC_SMALL = 8, // 4-bit opcode, 1 byte
+ WOD_SAVE_XMM128_FAR = 9, // 4-bit opcode, 5 bytes
+ WOD_SAVE_XMM128 = 10, // 4-bit opcode, 3 bytes
+ WOD_PUSH2 = 32, // 6-bit opcode, 2 bytes
+};
+
+/// V3 EPILOG_INFO flags.
+enum EpilogInfoFlagsV3 : uint8_t {
+ EPILOG_PARENT_FRAGMENT_TRANSFER = 0x01,
+};
+
+/// Decoded V3 Winding Operation Descriptor.
+struct DecodedWOD {
+ WODOpcode Opcode;
+ uint8_t Register; // For applicable ops (5-bit for int, 4-bit for XMM)
+ uint8_t Register2; // For WOD_PUSH2
+ uint8_t Type; // For WOD_PUSH_CANONICAL_FRAME
+ uint8_t ByteSize; // How many bytes this WOD consumed (max 5)
+ uint32_t Size; // For alloc ops: final computed size
+ uint32_t Displacement; // For save ops: final computed displacement
+};
+
+/// Decoded V3 epilog descriptor.
+struct DecodedEpilogV3 {
+ uint8_t Flags;
+ uint8_t NumberOfOps;
+ int16_t EpilogOffset;
+ uint16_t FirstOp;
+ uint8_t IpOffsetOfLastInstruction;
+ SmallVector<uint8_t, 8> IpOffsets;
+};
+
+/// Decoded V3 UNWIND_INFO.
+struct DecodedUnwindInfoV3 {
+ uint8_t Version;
+ uint8_t Flags;
+ uint8_t SizeOfProlog;
+ uint8_t CountOfCodes;
+ uint8_t NumberOfOps;
+ uint8_t NumberOfEpilogs;
+ /// Total bytes consumed by the payload (used to locate handler/chain).
+ uint16_t PayloadSize;
+ SmallVector<uint8_t, 8> PrologIpOffsets;
+ SmallVector<DecodedEpilogV3, 4> Epilogs;
+ ArrayRef<uint8_t> WODPool;
+};
+
+/// Return the register name for a 5-bit AMD64 integer register number.
+/// Covers 0-15 (RAX-R15) and 16-31 (R16-R31 for APX).
+StringRef getRegisterNameV3(unsigned Reg);
+
+/// Decode one WOD from the pool at the given byte offset.
+/// Returns an error on malformed data.
+Expected<DecodedWOD> decodeWOD(ArrayRef<uint8_t> Pool, unsigned Offset);
+
+/// Parse a V3 UNWIND_INFO from raw bytes.
+/// Returns an error on malformed data.
+Expected<DecodedUnwindInfoV3> decodeUnwindInfoV3(ArrayRef<uint8_t> Data);
} // End of namespace Win64EH
} // End of namespace llvm
diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index 100cfb567c348..58bf514265ca1 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -282,6 +282,7 @@ add_llvm_component_library(LLVMSupport
VirtualOutputConfig.cpp
VirtualOutputError.cpp
VirtualOutputFile.cpp
+ Win64EH.cpp
WithColor.cpp
YAMLParser.cpp
YAMLTraits.cpp
diff --git a/llvm/lib/Support/Win64EH.cpp b/llvm/lib/Support/Win64EH.cpp
new file mode 100644
index 0000000000000..28b4df64acf2a
--- /dev/null
+++ b/llvm/lib/Support/Win64EH.cpp
@@ -0,0 +1,276 @@
+//===-- Win64EH.cpp - Win64 EH V3 Support -----------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements decoding helpers for V3 unwind information on Win64.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/Win64EH.h"
+#include "llvm/Support/Endian.h"
+#include "llvm/Support/Error.h"
+
+using namespace llvm;
+using namespace llvm::Win64EH;
+
+StringRef Win64EH::getRegisterNameV3(unsigned Reg) {
+ static const char *const Names[] = {
+ "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI",
+ "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15",
+ "R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23",
+ "R24", "R25", "R26", "R27", "R28", "R29", "R30", "R31",
+ };
+ if (Reg >= std::size(Names))
+ return "<invalid>";
+ return Names[Reg];
+}
+
+Expected<DecodedWOD> Win64EH::decodeWOD(ArrayRef<uint8_t> Pool,
+ unsigned Offset) {
+ if (Offset >= Pool.size())
+ return createStringError("WOD pool overflow at offset %u", Offset);
+
+ uint8_t FirstByte = Pool[Offset];
+ DecodedWOD W = {};
+
+ // Determine opcode from variable-width prefix encoding.
+ // The dispatch order matters: check shorter prefixes first since they
+ // occupy the lowest bits, then fall through to longer prefixes.
+ // 3-bit prefix (bits [2:0] >= 4): opcodes 4-7
+ // 4-bit prefix (bits [3:0] >= 8): opcodes 8-10
+ // 6-bit prefix (bits [5:0] == 0x20): opcode 32 (PUSH2)
+ // 8-bit prefix (full byte 0-3): opcodes 0-3
+ uint8_t Low3 = FirstByte & 0x07;
+
+ // 3-bit opcode: bits [2:0] in {4, 5, 6, 7}
+ if (Low3 >= 4) {
+ switch (Low3) {
+ case WOD_PUSH: {
+ W.Opcode = WOD_PUSH;
+ W.ByteSize = 1;
+ W.Register = (FirstByte >> 3) & 0x1F; // 5-bit register
+ return W;
+ }
+ case WOD_SAVE_NONVOL_FAR: {
+ W.Opcode = WOD_SAVE_NONVOL_FAR;
+ W.ByteSize = 5;
+ if (Offset + 5 > Pool.size())
+ return createStringError("WOD_SAVE_NONVOL_FAR truncated at offset %u",
+ Offset);
+ W.Register = (FirstByte >> 3) & 0x1F;
+ W.Displacement = support::endian::read32le(&Pool[Offset + 1]);
+ return W;
+ }
+ case WOD_SAVE_NONVOL: {
+ W.Opcode = WOD_SAVE_NONVOL;
+ W.ByteSize = 3;
+ if (Offset + 3 > Pool.size())
+ return createStringError("WOD_SAVE_NONVOL truncated at offset %u",
+ Offset);
+ W.Register = (FirstByte >> 3) & 0x1F;
+ W.Displacement =
+ (uint32_t)support::endian::read16le(&Pool[Offset + 1]) * 8;
+ return W;
+ }
+ case WOD_PUSH_CONSECUTIVE_2: {
+ W.Opcode = WOD_PUSH_CONSECUTIVE_2;
+ W.ByteSize = 1;
+ W.Register = (FirstByte >> 3) & 0x1F;
+ return W;
+ }
+ default:
+ return createStringError("unknown WOD opcode 0x%02X at pool offset %u",
+ FirstByte, Offset);
+ }
+ }
+
+ // 4-bit opcode: bits [3:0] in {8, 9, 10, ...}
+ uint8_t Low4 = FirstByte & 0x0F;
+ if (Low4 >= 8) {
+ switch (Low4) {
+ case WOD_ALLOC_SMALL: {
+ W.Opcode = WOD_ALLOC_SMALL;
+ W.ByteSize = 1;
+ W.Size = (unsigned)(((FirstByte >> 4) & 0x0F) + 1) * 8;
+ return W;
+ }
+ case WOD_SAVE_XMM128_FAR: {
+ W.Opcode = WOD_SAVE_XMM128_FAR;
+ W.ByteSize = 5;
+ if (Offset + 5 > Pool.size())
+ return createStringError("WOD_SAVE_XMM128_FAR truncated at offset %u",
+ Offset);
+ W.Register = (FirstByte >> 4) & 0x0F;
+ W.Displacement = support::endian::read32le(&Pool[Offset + 1]);
+ return W;
+ }
+ case WOD_SAVE_XMM128: {
+ W.Opcode = WOD_SAVE_XMM128;
+ W.ByteSize = 3;
+ if (Offset + 3 > Pool.size())
+ return createStringError("WOD_SAVE_XMM128 truncated at offset %u",
+ Offset);
+ W.Register = (FirstByte >> 4) & 0x0F;
+ W.Displacement =
+ (uint32_t)support::endian::read16le(&Pool[Offset + 1]) * 16;
+ return W;
+ }
+ default:
+ return createStringError("unknown WOD opcode 0x%02X at pool offset %u",
+ FirstByte, Offset);
+ }
+ }
+
+ // 6-bit opcode: bits [5:0] == 0x20 (WOD_PUSH2)
+ uint8_t Low6 = FirstByte & 0x3F;
+ if (Low6 == WOD_PUSH2) {
+ W.Opcode = WOD_PUSH2;
+ W.ByteSize = 2;
+ if (Offset + 2 > Pool.size())
+ return createStringError("WOD_PUSH2 truncated at offset %u", Offset);
+ uint8_t SecondByte = Pool[Offset + 1];
+ // First reg from bits [7:6] of first byte (2 bits) and bits [2:0] of second
+ // (3 bits)
+ W.Register = ((FirstByte >> 6) & 0x03) | ((SecondByte & 0x07) << 2);
+ W.Register2 = (SecondByte >> 3) & 0x1F;
+ return W;
+ }
+
+ // 8-bit opcode: full byte is opcode (values 0-3)
+ switch (FirstByte) {
+ case WOD_SET_FPREG: {
+ W.Opcode = WOD_SET_FPREG;
+ W.ByteSize = 2;
+ if (Offset + 2 > Pool.size())
+ return createStringError("WOD_SET_FPREG truncated at offset %u", Offset);
+ uint8_t SecondByte = Pool[Offset + 1];
+ W.Register = SecondByte & 0x0F; // 4-bit register
+ W.Displacement = (unsigned)((SecondByte >> 4) & 0x0F) * 16;
+ return W;
+ }
+ case WOD_ALLOC_HUGE: {
+ W.Opcode = WOD_ALLOC_HUGE;
+ W.ByteSize = 5;
+ if (Offset + 5 > Pool.size())
+ return createStringError("WOD_ALLOC_HUGE truncated at offset %u", Offset);
+ W.Size = support::endian::read32le(&Pool[Offset + 1]);
+ return W;
+ }
+ case WOD_ALLOC_LARGE: {
+ W.Opcode = WOD_ALLOC_LARGE;
+ W.ByteSize = 3;
+ if (Offset + 3 > Pool.size())
+ return createStringError("WOD_ALLOC_LARGE truncated at offset %u",
+ Offset);
+ W.Size = (uint32_t)support::endian::read16le(&Pool[Offset + 1]) * 8;
+ return W;
+ }
+ case WOD_PUSH_CANONICAL_FRAME: {
+ W.Opcode = WOD_PUSH_CANONICAL_FRAME;
+ W.ByteSize = 2;
+ if (Offset + 2 > Pool.size())
+ return createStringError(
+ "WOD_PUSH_CANONICAL_FRAME truncated at offset %u", Offset);
+ W.Type = Pool[Offset + 1];
+ return W;
+ }
+ default:
+ return createStringError("unknown WOD opcode 0x%02X at pool offset %u",
+ FirstByte, Offset);
+ }
+}
+
+Expected<DecodedUnwindInfoV3>
+Win64EH::decodeUnwindInfoV3(ArrayRef<uint8_t> Data) {
+ if (Data.size() < 4)
+ return createStringError("V3 unwind info too short: %zu bytes",
+ Data.size());
+
+ DecodedUnwindInfoV3 Info;
+ Info.Version = Data[0] & 0x07;
+ Info.Flags = (Data[0] >> 3) & 0x1F;
+ Info.SizeOfProlog = Data[1];
+ Info.CountOfCodes = Data[2];
+ Info.NumberOfOps = Data[3] & 0x1F;
+ Info.NumberOfEpilogs = (Data[3] >> 5) & 0x07;
+
+ unsigned Offset = 4; // Start of payload
+
+ // Read prolog IP offsets (one byte each)
+ for (unsigned I = 0; I < Info.NumberOfOps; ++I) {
+ if (Offset >= Data.size())
+ return createStringError(
+ "V3 payload truncated reading prolog IP offset %u", I);
+ Info.PrologIpOffsets.push_back(Data[Offset++]);
+ }
+
+ // Read epilog descriptors
+ for (unsigned I = 0; I < Info.NumberOfEpilogs; ++I) {
+ DecodedEpilogV3 Epi;
+ if (Offset >= Data.size())
+ return createStringError(
+ "V3 payload truncated reading epilog %u FlagsAndNumOps", I);
+ uint8_t FlagsAndNumOps = Data[Offset++];
+ Epi.Flags = FlagsAndNumOps & 0x07;
+ Epi.NumberOfOps = (FlagsAndNumOps >> 3) & 0x1F;
+
+ if (Offset + 2 > Data.size())
+ return createStringError(
+ "V3 payload truncated reading epilog %u EpilogOffset", I);
+ Epi.EpilogOffset =
+ static_cast<int16_t>(support::endian::read16le(&Data[Offset]));
+ Offset += 2;
+
+ // Inherited descriptors (NumberOfOps == 0) are only 3 bytes:
+ // FlagsAndNumOps(1) + EpilogOffset(2). They have no FirstOp,
+ // IpOffsetOfLastInstruction, or IP offset fields.
+ if (Epi.NumberOfOps == 0) {
+ Epi.FirstOp = 0;
+ Epi.IpOffsetOfLastInstruction = 0;
+ Info.Epilogs.push_back(std::move(Epi));
+ continue;
+ }
+
+ if (Offset + 2 > Data.size())
+ return createStringError("V3 payload truncated reading epilog %u FirstOp",
+ I);
+ Epi.FirstOp = support::endian::read16le(&Data[Offset]);
+ Offset += 2;
+
+ if (Offset >= Data.size())
+ return createStringError(
+ "V3 payload truncated reading epilog %u IpOffsetOfLastInstruction",
+ I);
+ Epi.IpOffsetOfLastInstruction = Data[Offset++];
+
+ // Read epilog IP offsets (one byte each)
+ for (unsigned J = 0; J < Epi.NumberOfOps; ++J) {
+ if (Offset >= Data.size())
+ return createStringError(
+ "V3 payload truncated reading epilog %u IP offset %u", I, J);
+ Epi.IpOffsets.push_back(Data[Offset++]);
+ }
+
+ Info.Epilogs.push_back(std::move(Epi));
+ }
+
+ // Identify WOD pool: everything from current offset until end of
+ // CountOfCodes * 2 bytes (payload area)
+ unsigned PayloadEnd = 4 + Info.CountOfCodes * 2;
+ if (PayloadEnd > Data.size())
+ PayloadEnd = Data.size();
+ unsigned WODPoolStart = Offset;
+ unsigned WODPoolEnd = PayloadEnd;
+ if (WODPoolStart < WODPoolEnd)
+ Info.WODPool = Data.slice(WODPoolStart, WODPoolEnd - WODPoolStart);
+ else
+ Info.WODPool = ArrayRef<uint8_t>();
+
+ Info.PayloadSize = PayloadEnd;
+
+ return Info;
+}
diff --git a/llvm/test/tools/llvm-objdump/COFF/win64-unwindv3-all-wods.yaml b/llvm/test/tools/llvm-objdump/COFF/win64-unwindv3-all-wods.yaml
new file mode 100644
index 0000000000000..9c2636de06d16
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/COFF/win64-unwindv3-all-wods.yaml
@@ -0,0 +1,140 @@
+# RUN: yaml2obj %s -o %t.exe
+# RUN: llvm-objdump --unwind-info %t.exe | FileCheck %s
+
+## T4: V3 with all WOD types — comprehensive WOD decode coverage.
+##
+## .xdata hex breakdown:
+## Byte 0: 0x03 (Version=3, Flags=0)
+## Byte 1: 0x30 (SizeOfProlog=48)
+## Byte 2: 0x17 (CountOfCodes=23 words -> 46 bytes payload)
+## Byte 3: 0x0C (NumberOfOps=12, NumberOfEpilogs=0)
+## See readobj test for full hex breakdown of WOD pool.
+
+# CHECK-LABEL: Unwind info:
+# CHECK-EMPTY:
+# CHECK-NEXT: Function Table:
+# CHECK-NEXT: Start Address: 0x1000
+# CHECK-NEXT: End Address: 0x1050
+# CHECK-NEXT: Unwind Info Address: 0x2000
+# CHECK-NEXT: Version: 3
+# CHECK-NEXT: Flags: 0
+# CHECK-NEXT: Size of prolog: 0x30
+# CHECK-NEXT: CountOfCodes: 23
+# CHECK-NEXT: NumberOfOps: 12
+# CHECK-NEXT: NumberOfEpilogs: 0
+# CHECK-NEXT: Prolog [12 ops]:
+# CHECK-NEXT: [0] IP +0x30: WOD_PUSH Reg=RDI
+# CHECK-NEXT: [1] IP +0x2C: WOD_PUSH2 Reg1=RAX, Reg2=RCX
+# CHECK-NEXT: [2] IP +0x2B: WOD_PUSH_CONSECUTIVE_2 Reg=RBX (+RSP)
+# CHECK-NEXT: [3] IP +0x2A: WOD_ALLOC_SMALL Size=0x20
+# CHECK-NEXT: [4] IP +0x28: WOD_ALLOC_LARGE Size=0x1000
+# CHECK-NEXT: [5] IP +0x24: WOD_ALLOC_HUGE Size=0x12345
+# CHECK-NEXT: [6] IP +0x20: WOD_SET_FPREG Reg=RBP, Offset=0x30
+# CHECK-NEXT: [7] IP +0x1C: WOD_SAVE_NONVOL Reg=R12, Disp=0x40
+# CHECK-NEXT: [8] IP +0x18: WOD_SAVE_NONVOL_FAR Reg=R13, Disp=0x100000
+# CHECK-NEXT: [9] IP +0x14: WOD_SAVE_XMM128 Reg=XMM4, Disp=0x100
+# CHECK-NEXT: [10] IP +0x10: WOD_SAVE_XMM128_FAR Reg=XMM7, Disp=0x20000
+# CHECK-NEXT: [11] IP +0x00: WOD_PUSH_CANONICAL_FRAME Type=1
+
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 0
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA, IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE, IMAGE_DLL_CHARACTERISTICS_NX_COMPAT ]
+ SizeOfStackReserve: 1048576
+ SizeOfStackCommit: 4096
+ SizeOfHeapReserve: 1048576
+ SizeOfHeapCommit: 4096
+ ExportTable:
+ RelativeVirtualAddress: 0
+ Size: 0
+ ImportTable:
+ RelativeVirtualAddress: 0
+ Size: 0
+ ResourceTable:
+ RelativeVirtualAddress: 0
+ Size: 0
+ ExceptionTable:
+ RelativeVirtualAddress: 12288
+ Size: 12
+ CertificateTable:
+ RelativeVirtualAddress: 0
+ Size: 0
+ BaseRelocationTable:
+ RelativeVirtualAddress: 0
+ Size: 0
+ Debug:
+ RelativeVirtualAddress: 0
+ Size: 0
+ Architecture:
+ RelativeVirtualAddress: 0
+ Size: 0
+ GlobalPtr:
+ RelativeVirtualAddress: 0
+ Size: 0
+ TlsTable:
+ RelativeVirtualAddress: 0
+ Size: 0
+ LoadConfigTable:
+ RelativeVirtualAddress: 0
+ Size: 0
+ BoundImport:
+ RelativeVirtualAddress: 0
+ Size: 0
+ IAT:
+ RelativeVirtualAddress: 0
+ Size: 0
+ DelayImportDescriptor:
+ RelativeVirtualAddress: 0
+ Size: 0
+ ClrRuntimeHeader:
+ RelativeVirtualAddress: 0
+ Size: 0
+header:
+ Machine: IMAGE_FILE_MACHINE_AMD64
+ Characteristics: [ IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE ]
+sections:
+ - Name: .text
+ Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 4096
+ VirtualSize: 80
+ SectionData: '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
+ - Name: .xdata
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 8192
+ VirtualSize: 50
+ SectionData: '0330170C302C2B2A2824201C181410003C20081F38020002014523010000356608006D000010004A10007900000200030100'
+ - Name: .pdata
+ Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+ VirtualAddress: 12288
+ VirtualSize: 12
+ SectionData: '001000005010000000200000'
+symbols:
+ - Name: .text
+ Value: 0
+ SectionNumber: 1
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_NULL
+ StorageClass: IMAGE_SYM_CLASS_STATIC
+ - Name: .xdata
+ Value: 0
+ SectionNumber: 2
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_NULL
+ StorageClass: IMAGE_SYM_CLASS_STATIC
+ - Name: .pdata
+ Value: 0
+ SectionNumber: 3
+ SimpleType: IMAGE_SYM_TYPE_NULL
+ ComplexType: IMAGE_SYM_DTYPE_NULL
+ StorageClass: IMAGE_SYM_CLASS_STATIC
+...
diff --git a/llvm/test/tools/llvm-objdump/COFF/win64-unwindv3-apx.yaml b/llvm/test/tools/llvm-objdump/COFF/win64-unwindv3-apx.yaml
new file mode 100644
index 0000000000000..3b558fb75c920
--- /dev/null
+++ b/llvm/test/tools/llvm-objdump/COFF/win64-unwindv3-apx.yaml
@@ -0,0 +1,123 @@
+# RUN: yaml2obj %s -o %t.exe
+# RUN: llvm-objdump --unwind-info %t.exe | FileCheck %s
+
+## T7: V3 with APX registers (R16-R31).
+
+# CHECK-LABEL: Unwind info:
+# CHECK-EMPTY:
+# CHECK-NEXT: Function Table:
+# CHECK-NEXT: Start Address: 0x1000
+# CHECK-NEXT: End Address: 0x1010
+# CHECK-NEXT: Unwind Info Address: 0x2000
+# CHECK-NEXT: Version: 3
+# CHECK-NEXT: Flags: 0
+# CHECK-NEXT: Size of prolog: 0x4
+# CHECK-NEXT: CountOfCodes: 2
+# CHECK-NEXT: NumberOfOps: 2
+# CHECK-NEXT: NumberOfEpilogs: 0
+# CHECK-NEXT: Prolog [2 ops]:
+# CHECK-NEXT: [0] IP +0x04: WOD_PUSH Reg=R16
+# CHECK-NEXT: [1] IP +0x00: WOD_PUSH Reg=R31
+
+--- !COFF
+OptionalHeader:
+ AddressOfEntryPoint: 4096
+ ImageBase: 0
+ SectionAlignment: 4096
+ FileAlignment: 512
+ MajorOperatingSystemVersion: 6
+ MinorOperatingSystemVersion: 0
+ MajorImageVersion: 0
+ MinorImageVersion: 0
+ MajorSubsystemVersion: 6
+ MinorSubsystemVersion: 0
+ Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI
+ DLLCharacteristics: [ IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA, IMAGE_DLL_CHARACTE...
[truncated]
|
🐧 Linux x64 Test Results
All executed tests passed, but another part of the build failed. Click on a failure below to see the details. tools/llvm-objdump/CMakeFiles/llvm-objdump.dir/COFFDump.cpp.otools/llvm-readobj/CMakeFiles/llvm-readobj.dir/Win64EHDumper.cpp.oIf these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the |
🪟 Windows x64 Test Results
Failed Tests(click on a test name to see its output) LLVMLLVM.CodeGen/PowerPC/fast-isel-cmp-imm.llIf these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the |
mikolaj-pirog
left a comment
There was a problem hiding this comment.
| NOTE: This does not yet support large unwind or epilog info variants.
Could you put some todo please?
| }; | ||
|
|
||
| /// Decoded V3 Winding Operation Descriptor. | ||
| struct DecodedWOD { |
There was a problem hiding this comment.
Nit: could we drop the Decoded from this and subsequent structures?
| # CHECK-NEXT: [0] IP +0x07: WOD_ALLOC_SMALL Size=0x20 | ||
| # CHECK-NEXT: [1] IP +0x02: WOD_PUSH Reg=RSI | ||
| # CHECK-NEXT: [2] IP +0x00: WOD_PUSH Reg=RDI | ||
| # CHECK-NEXT: Epilog [0] (Flags=0x01, Offset=+0x14, IpOfLast=+0x6) [3 ops, FirstOp=0x0]: |
There was a problem hiding this comment.
How about instead of displaying WODs for each prolog and epilog separately, displaying a WOD pool and displaying offsets to it for prolog/epilogs? That way it would be easy to see how the WOD pool is being shared. On the other hand it would be harder to see which WODs are used by each epilog and prolog..
| outs().flush(); | ||
| } | ||
|
|
||
| /// Helper to format a decoded WOD for llvm-objdump plain-text output. |
There was a problem hiding this comment.
| /// Helper to format a decoded WOD for llvm-objdump plain-text output. |
| } | ||
|
|
||
| /// Helper to format a decoded WOD for llvm-objdump plain-text output. | ||
| static void printDecodedWODObjdump(const DecodedWOD &W) { |
There was a problem hiding this comment.
Nit: can we drop the "Objdump" from the name?
| } | ||
| } | ||
|
|
||
| /// Decode and print N WODs from the pool for llvm-objdump. |
There was a problem hiding this comment.
| /// Decode and print N WODs from the pool for llvm-objdump. |
| } | ||
|
|
||
| /// Decode and print N WODs from the pool for llvm-objdump. | ||
| static void printWODSequenceObjdump(ArrayRef<uint8_t> WODPool, |
There was a problem hiding this comment.
Nit: can we drop the "Objdump" from the name?
| } | ||
| } | ||
|
|
||
| /// Helper to print decoded WOD fields for llvm-readobj. |
There was a problem hiding this comment.
| /// Helper to print decoded WOD fields for llvm-readobj. |
| WODOpcode Opcode; | ||
| uint8_t Register; // For applicable ops (5-bit for int, 4-bit for XMM) | ||
| uint8_t Register2; // For WOD_PUSH2 | ||
| uint8_t Type; // For WOD_PUSH_CANONICAL_FRAME |
There was a problem hiding this comment.
Spec lists which types are possible and refers to windows SDK for details. Could you add a todo to handle this types as well? (Having an enum, nice display, etc.)
Public docs: https://learn.microsoft.com/en-us/cpp/build/x64-unwind-information-v3?view=msvc-170
The change adds Windows x64 unwind v3 info decoding and printing support in LLVM, including new data structures, enums, and decoding functions to handle the different WOD opcodes and epilog descriptors. It also updates the dumping utilities (llvm-readobj and llvm-objdump) to correctly interpret v3 unwind info.
NOTE: This does not yet support large unwind or epilog info variants.