diff --git a/llvm/include/llvm/Support/ARMWinEH.h b/llvm/include/llvm/Support/ARMWinEH.h index 1463629f45dc..4f05965ed257 100644 --- a/llvm/include/llvm/Support/ARMWinEH.h +++ b/llvm/include/llvm/Support/ARMWinEH.h @@ -207,6 +207,8 @@ std::pair SavedRegisterMask(const RuntimeFunction &RF); /// ExceptionDataRecord - An entry in the table of exception data (.xdata) /// +/// The format on ARM is: +/// /// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 /// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 /// +-------+---------+-+-+-+---+-----------------------------------+ @@ -215,6 +217,16 @@ std::pair SavedRegisterMask(const RuntimeFunction &RF); /// | Reserved |Ex. Code Words| (Extended Epilogue Count) | /// +-------+--------+--------------+-------------------------------+ /// +/// The format on ARM64 is: +/// +/// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 +/// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +/// +---------+---------+-+-+---+-----------------------------------+ +/// | C Wrd | Epi Cnt |E|X|Ver| Function Length | +/// +---------+------+--'-'-'---'---+-------------------------------+ +/// | Reserved |Ex. Code Words| (Extended Epilogue Count) | +/// +-------+--------+--------------+-------------------------------+ +/// /// Function Length : 18-bit field indicating the total length of the function /// in bytes divided by 2. If a function is larger than /// 512KB, then multiple pdata and xdata records must be used. @@ -225,7 +237,7 @@ std::pair SavedRegisterMask(const RuntimeFunction &RF); /// header /// F : 1-bit field indicating that the record describes a function fragment /// (implies that no prologue is present, and prologue processing should be -/// skipped) +/// skipped) (ARM only) /// Epilogue Count : 5-bit field that differs in meaning based on the E field. /// /// If E is set, then this field specifies the index of the @@ -235,33 +247,43 @@ std::pair SavedRegisterMask(const RuntimeFunction &RF); /// scopes. If more than 31 scopes exist, then this field and /// the Code Words field must both be set to 0 to indicate that /// an extension word is required. -/// Code Words : 4-bit field that species the number of 32-bit words needed to -/// contain all the unwind codes. If more than 15 words (63 code -/// bytes) are required, then this field and the Epilogue Count -/// field must both be set to 0 to indicate that an extension word -/// is required. +/// Code Words : 4-bit (5-bit on ARM64) field that specifies the number of +/// 32-bit words needed to contain all the unwind codes. If more +/// than 15 words (31 words on ARM64) are required, then this field +/// and the Epilogue Count field must both be set to 0 to indicate +/// that an extension word is required. /// Extended Epilogue Count, Extended Code Words : /// Valid only if Epilog Count and Code Words are both /// set to 0. Provides an 8-bit extended code word /// count and 16-bits for epilogue count /// +/// The epilogue scope format on ARM is: +/// /// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 /// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 /// +----------------+------+---+---+-------------------------------+ /// | Ep Start Idx | Cond |Res| Epilogue Start Offset | /// +----------------+------+---+-----------------------------------+ /// +/// The epilogue scope format on ARM64 is: +/// +/// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 +/// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +/// +-------------------+-------+---+-------------------------------+ +/// | Ep Start Idx | Res | Epilogue Start Offset | +/// +-------------------+-------+-----------------------------------+ +/// /// If the E bit is unset in the header, the header is followed by a series of /// epilogue scopes, which are sorted by their offset. /// /// Epilogue Start Offset: 18-bit field encoding the offset of epilogue relative /// to the start of the function in bytes divided by two /// Res : 2-bit field reserved for future expansion (must be set to 0) -/// Condition : 4-bit field providing the condition under which the epilogue is -/// executed. Unconditional epilogues should set this field to 0xe. -/// Epilogues must be entirely conditional or unconditional, and in -/// Thumb-2 mode. The epilogue beings with the first instruction -/// after the IT opcode. +/// Condition : (ARM only) 4-bit field providing the condition under which the +/// epilogue is executed. Unconditional epilogues should set this +/// field to 0xe. Epilogues must be entirely conditional or +/// unconditional, and in Thumb-2 mode. The epilogue begins with +/// the first instruction after the IT opcode. /// Epilogue Start Index : 8-bit field indicating the byte index of the first /// unwind code describing the epilogue /// @@ -293,18 +315,33 @@ struct EpilogueScope { const support::ulittle32_t ES; EpilogueScope(const support::ulittle32_t Data) : ES(Data) {} + // Same for both ARM and AArch64. uint32_t EpilogueStartOffset() const { return (ES & 0x0003ffff); } - uint8_t Res() const { + + // Different implementations for ARM and AArch64. + uint8_t ResARM() const { return ((ES & 0x000c0000) >> 18); } + + uint8_t ResAArch64() const { + return ((ES & 0x000f0000) >> 18); + } + + // Condition is only applicable to ARM. uint8_t Condition() const { return ((ES & 0x00f00000) >> 20); } - uint8_t EpilogueStartIndex() const { + + // Different implementations for ARM and AArch64. + uint8_t EpilogueStartIndexARM() const { return ((ES & 0xff000000) >> 24); } + + uint8_t EpilogueStartIndexAArch64() const { + return ((ES & 0xffc00000) >> 22); + } }; struct ExceptionDataRecord; @@ -312,13 +349,23 @@ inline size_t HeaderWords(const ExceptionDataRecord &XR); struct ExceptionDataRecord { const support::ulittle32_t *Data; + bool isAArch64; - ExceptionDataRecord(const support::ulittle32_t *Data) : Data(Data) {} + ExceptionDataRecord(const support::ulittle32_t *Data, bool isAArch64) : + Data(Data), isAArch64(isAArch64) {} uint32_t FunctionLength() const { return (Data[0] & 0x0003ffff); } + uint32_t FunctionLengthInBytesARM() const { + return FunctionLength() << 1; + } + + uint32_t FunctionLengthInBytesAArch64() const { + return FunctionLength() << 2; + } + uint8_t Vers() const { return (Data[0] & 0x000C0000) >> 18; } @@ -332,18 +379,25 @@ struct ExceptionDataRecord { } bool F() const { + assert(!isAArch64 && "Fragments are only supported on ARMv7 WinEH"); return ((Data[0] & 0x00400000) >> 22); } uint8_t EpilogueCount() const { - if (HeaderWords(*this) == 1) + if (HeaderWords(*this) == 1) { + if (isAArch64) + return (Data[0] & 0x07C00000) >> 22; return (Data[0] & 0x0f800000) >> 23; + } return Data[1] & 0x0000ffff; } uint8_t CodeWords() const { - if (HeaderWords(*this) == 1) + if (HeaderWords(*this) == 1) { + if (isAArch64) + return (Data[0] & 0xf8000000) >> 27; return (Data[0] & 0xf0000000) >> 28; + } return (Data[1] & 0x00ff0000) >> 16; } @@ -373,6 +427,8 @@ struct ExceptionDataRecord { }; inline size_t HeaderWords(const ExceptionDataRecord &XR) { + if (XR.isAArch64) + return (XR.Data[0] & 0xffc0000) ? 1 : 2; return (XR.Data[0] & 0xff800000) ? 1 : 2; } } diff --git a/llvm/test/tools/llvm-readobj/Inputs/arm64-win1.obj b/llvm/test/tools/llvm-readobj/Inputs/arm64-win1.obj new file mode 100755 index 000000000000..025e1db6cce4 Binary files /dev/null and b/llvm/test/tools/llvm-readobj/Inputs/arm64-win1.obj differ diff --git a/llvm/test/tools/llvm-readobj/Inputs/arm64-win2.obj b/llvm/test/tools/llvm-readobj/Inputs/arm64-win2.obj new file mode 100755 index 000000000000..7e506eedda65 Binary files /dev/null and b/llvm/test/tools/llvm-readobj/Inputs/arm64-win2.obj differ diff --git a/llvm/test/tools/llvm-readobj/arm64-win-error1.s b/llvm/test/tools/llvm-readobj/arm64-win-error1.s new file mode 100644 index 000000000000..ba59edf3dea3 --- /dev/null +++ b/llvm/test/tools/llvm-readobj/arm64-win-error1.s @@ -0,0 +1,53 @@ +## Check that error handling for bad opcodes works. +## .xdata below contains the bad opcode 0xdf in the 4th word of .xdata. + +// REQUIRES: aarch64-registered-target +// RUN: llvm-mc -filetype=obj -triple aarch64-windows %s -o - \ +// RUN: | llvm-readobj -unwind - | FileCheck %s + +// CHECK: Prologue [ +// CHECK: 0xdf ; Bad opcode! +// CHECK: 0xd600 ; stp x19, lr, [sp, #0] +// CHECK: 0x01 ; sub sp, #16 +// CHECK: 0xe4 ; end +// CHECK: ] + + .text + .globl "?func@@YAHXZ" + .p2align 3 +"?func@@YAHXZ": + sub sp,sp,#0x10 + stp x19,lr,[sp] + sub sp,sp,#0x1F0 + mov w19,w0 + bl "?func2@@YAXXZ" + cmp w19,#2 + ble .LBB0_1 + bl "?func2@@YAHXZ" + add sp,sp,#0x1F0 + ldp x19,lr,[sp] + add sp,sp,#0x10 + ret +.LBB0_1: + mov x0,sp + bl "?func3@@YAHPEAH@Z" + add sp,sp,#0x1F0 + ldp x19,lr,[sp] + add sp,sp,#0x10 + ret + + +.section .pdata,"dr" + .long "?func@@YAHXZ"@IMGREL + .long "$unwind$func@@YAHXZ"@IMGREL + + +.section .xdata,"dr" +"$unwind$func@@YAHXZ": + .p2align 3 + .long 0x10800012 + .long 0x8 + .long 0xe + .long 0x100d6df + .long 0xe3e3e3e4 + diff --git a/llvm/test/tools/llvm-readobj/arm64-win-error2.s b/llvm/test/tools/llvm-readobj/arm64-win-error2.s new file mode 100644 index 000000000000..93c461de8ee3 --- /dev/null +++ b/llvm/test/tools/llvm-readobj/arm64-win-error2.s @@ -0,0 +1,50 @@ +## Check that the sanity check for an inconsistent header works. +## The first word contains the bad value for CodeWords, 0xf, which indicates +## that we need 0x11110 << 2 = 120 bytes of space for the unwind codes. +## It follows that the .xdata section is badly formed as only 8 bytes are +## allocated for the unwind codes. + +// REQUIRES: aarch64-registered-target +// RUN: llvm-mc -filetype=obj -triple aarch64-windows %s -o - \ +// RUN: | not llvm-readobj -unwind - 2>&1 | FileCheck %s + +// CHECK: LLVM ERROR: Malformed unwind data + + .text + .globl "?func@@YAHXZ" + .p2align 3 +"?func@@YAHXZ": + sub sp,sp,#0x10 + stp x19,lr,[sp] + sub sp,sp,#0x1F0 + mov w19,w0 + bl "?func2@@YAXXZ" + cmp w19,#2 + ble .LBB0_1 + bl "?func2@@YAHXZ" + add sp,sp,#0x1F0 + ldp x19,lr,[sp] + add sp,sp,#0x10 + ret +.LBB0_1: + mov x0,sp + bl "?func3@@YAHPEAH@Z" + add sp,sp,#0x1F0 + ldp x19,lr,[sp] + add sp,sp,#0x10 + ret + +.section .pdata,"dr" + .long "?func@@YAHXZ"@IMGREL + .long "$unwind$func@@YAHXZ"@IMGREL + + +.section .xdata,"dr" +"$unwind$func@@YAHXZ": + .p2align 3 + .long 0xf0800012 + .long 0x8 + .long 0xe + .long 0x100d61f + .long 0xe3e3e3e4 + diff --git a/llvm/test/tools/llvm-readobj/arm64-win-error3.s b/llvm/test/tools/llvm-readobj/arm64-win-error3.s new file mode 100644 index 000000000000..5cbc3d7c585c --- /dev/null +++ b/llvm/test/tools/llvm-readobj/arm64-win-error3.s @@ -0,0 +1,51 @@ +## Check that error handling for going past the unwind data works. +## .xdata below contains bad opcodes in the last word. The last byte, 0xe0, +## indicates that we have come across alloc_l, which requires 4 bytes. In this +## case, unwind code processing will go past the allocated unwind data. + +// REQUIRES: aarch64-registered-target +// RUN: llvm-mc -filetype=obj -triple aarch64-windows %s -o - \ +// RUN: | llvm-readobj -unwind - | FileCheck %s + +// CHECK: Prologue [ +// CHECK: Opcode 0xe0 goes past the unwind data + + .text + .globl "?func@@YAHXZ" + .p2align 3 +"?func@@YAHXZ": + sub sp,sp,#0x10 + stp x19,lr,[sp] + sub sp,sp,#0x1F0 + mov w19,w0 + bl "?func2@@YAXXZ" + cmp w19,#2 + ble .LBB0_1 + bl "?func2@@YAHXZ" + add sp,sp,#0x1F0 + ldp x19,lr,[sp] + add sp,sp,#0x10 + ret +.LBB0_1: + mov x0,sp + bl "?func3@@YAHPEAH@Z" + add sp,sp,#0x1F0 + ldp x19,lr,[sp] + add sp,sp,#0x10 + ret + + +.section .pdata,"dr" + .long "?func@@YAHXZ"@IMGREL + .long "$unwind$func@@YAHXZ"@IMGREL + + +.section .xdata,"dr" +"$unwind$func@@YAHXZ": + .p2align 3 + .long 0x10800012 + .long 0x8 + .long 0xe + .long 0x100d61f + .long 0xe0000000 + diff --git a/llvm/test/tools/llvm-readobj/unwind-arm64-windows.test b/llvm/test/tools/llvm-readobj/unwind-arm64-windows.test new file mode 100644 index 000000000000..879afe27efba --- /dev/null +++ b/llvm/test/tools/llvm-readobj/unwind-arm64-windows.test @@ -0,0 +1,69 @@ +RUN: llvm-readobj -unwind %p/Inputs/arm64-win1.obj | FileCheck %s -check-prefix=UNWIND1 +RUN: llvm-readobj -unwind %p/Inputs/arm64-win2.obj | FileCheck %s -check-prefix=UNWIND2 + +UNWIND1: ExceptionData { +UNWIND1-NEXT: FunctionLength: 340 +UNWIND1-NEXT: Version: 0 +UNWIND1-NEXT: ExceptionData: No +UNWIND1-NEXT: EpiloguePacked: Yes +UNWIND1-NEXT: EpilogueOffset: 15 +UNWIND1-NEXT: ByteCodeLength: 28 +UNWIND1-NEXT: Prologue [ +UNWIND1-NEXT: 0xe002dac8 ; sub sp, #2993280 +UNWIND1-NEXT: 0xe3 ; nop +UNWIND1-NEXT: 0xe3 ; nop +UNWIND1-NEXT: 0xe3 ; nop +UNWIND1-NEXT: 0xd885 ; stp d10, d11, [sp, #40] +UNWIND1-NEXT: 0xd803 ; stp d8, d9, [sp, #24] +UNWIND1-NEXT: 0xd2c2 ; str x30, [sp, #16] +UNWIND1-NEXT: 0x28 ; stp x19, x20, [sp, #-64]! +UNWIND1-NEXT: 0xe4 ; end +UNWIND1-NEXT: ] +UNWIND1-NEXT: Epilogue [ +UNWIND1-NEXT: 0xe002dac8 ; add sp, #2993280 +UNWIND1-NEXT: 0xd885 ; ldp d10, d11, [sp, #40] +UNWIND1-NEXT: 0xd803 ; ldp d8, d9, [sp, #24] +UNWIND1-NEXT: 0xd2c2 ; ldr x30, [sp, #16] +UNWIND1-NEXT: 0x28 ; ldp x19, x20, [sp], #64 +UNWIND1-NEXT: 0xe4 ; end +UNWIND1-NEXT: ] +UNWIND1_NEXT: } + + +UNWIND2: ExceptionData { +UNWIND2-NEXT: FunctionLength: 72 +UNWIND2-NEXT: Version: 0 +UNWIND2-NEXT: ExceptionData: No +UNWIND2-NEXT: EpiloguePacked: No +UNWIND2-NEXT: EpilogueScopes: 2 +UNWIND2-NEXT: ByteCodeLength: 8 +UNWIND2-NEXT: Prologue [ +UNWIND2-NEXT: 0x1f ; sub sp, #496 +UNWIND2-NEXT: 0xd600 ; stp x19, lr, [sp, #0] +UNWIND2-NEXT: 0x01 ; sub sp, #16 +UNWIND2-NEXT: 0xe4 ; end +UNWIND2-NEXT: ] +UNWIND2-NEXT: EpilogueScopes [ +UNWIND2-NEXT: EpilogueScope { +UNWIND2-NEXT: StartOffset: 8 +UNWIND2-NEXT: EpilogueStartIndex: 0 +UNWIND2-NEXT: Opcodes [ +UNWIND2-NEXT: 0x1f ; add sp, #496 +UNWIND2-NEXT: 0xd600 ; ldp x19, lr, [sp, #0] +UNWIND2-NEXT: 0x01 ; add sp, #16 +UNWIND2-NEXT: 0xe4 ; end +UNWIND2-NEXT: ] +UNWIND2-NEXT: } +UNWIND2-NEXT: EpilogueScope { +UNWIND2-NEXT: StartOffset: 14 +UNWIND2-NEXT: EpilogueStartIndex: 0 +UNWIND2-NEXT: Opcodes [ +UNWIND2-NEXT: 0x1f ; add sp, #496 +UNWIND2-NEXT: 0xd600 ; ldp x19, lr, [sp, #0] +UNWIND2-NEXT: 0x01 ; add sp, #16 +UNWIND2-NEXT: 0xe4 ; end +UNWIND2-NEXT: ] +UNWIND2-NEXT: } +UNWIND2-NEXT: ] +UNWIND2-NEXT: } + diff --git a/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp b/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp index a90840b22c8d..56dd6c0aed40 100644 --- a/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp +++ b/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp @@ -118,31 +118,57 @@ const size_t Decoder::PDataEntrySize = sizeof(RuntimeFunction); // TODO name the uops more appropriately const Decoder::RingEntry Decoder::Ring[] = { - { 0x80, 0x00, &Decoder::opcode_0xxxxxxx }, // UOP_STACK_FREE (16-bit) - { 0xc0, 0x80, &Decoder::opcode_10Lxxxxx }, // UOP_POP (32-bit) - { 0xf0, 0xc0, &Decoder::opcode_1100xxxx }, // UOP_STACK_SAVE (16-bit) - { 0xf8, 0xd0, &Decoder::opcode_11010Lxx }, // UOP_POP (16-bit) - { 0xf8, 0xd8, &Decoder::opcode_11011Lxx }, // UOP_POP (32-bit) - { 0xf8, 0xe0, &Decoder::opcode_11100xxx }, // UOP_VPOP (32-bit) - { 0xfc, 0xe8, &Decoder::opcode_111010xx }, // UOP_STACK_FREE (32-bit) - { 0xfe, 0xec, &Decoder::opcode_1110110L }, // UOP_POP (16-bit) - { 0xff, 0xee, &Decoder::opcode_11101110 }, // UOP_MICROSOFT_SPECIFIC (16-bit) + { 0x80, 0x00, 1, &Decoder::opcode_0xxxxxxx }, // UOP_STACK_FREE (16-bit) + { 0xc0, 0x80, 2, &Decoder::opcode_10Lxxxxx }, // UOP_POP (32-bit) + { 0xf0, 0xc0, 1, &Decoder::opcode_1100xxxx }, // UOP_STACK_SAVE (16-bit) + { 0xf8, 0xd0, 1, &Decoder::opcode_11010Lxx }, // UOP_POP (16-bit) + { 0xf8, 0xd8, 1, &Decoder::opcode_11011Lxx }, // UOP_POP (32-bit) + { 0xf8, 0xe0, 1, &Decoder::opcode_11100xxx }, // UOP_VPOP (32-bit) + { 0xfc, 0xe8, 2, &Decoder::opcode_111010xx }, // UOP_STACK_FREE (32-bit) + { 0xfe, 0xec, 2, &Decoder::opcode_1110110L }, // UOP_POP (16-bit) + { 0xff, 0xee, 2, &Decoder::opcode_11101110 }, // UOP_MICROSOFT_SPECIFIC (16-bit) // UOP_PUSH_MACHINE_FRAME // UOP_PUSH_CONTEXT // UOP_PUSH_TRAP_FRAME // UOP_REDZONE_RESTORE_LR - { 0xff, 0xef, &Decoder::opcode_11101111 }, // UOP_LDRPC_POSTINC (32-bit) - { 0xff, 0xf5, &Decoder::opcode_11110101 }, // UOP_VPOP (32-bit) - { 0xff, 0xf6, &Decoder::opcode_11110110 }, // UOP_VPOP (32-bit) - { 0xff, 0xf7, &Decoder::opcode_11110111 }, // UOP_STACK_RESTORE (16-bit) - { 0xff, 0xf8, &Decoder::opcode_11111000 }, // UOP_STACK_RESTORE (16-bit) - { 0xff, 0xf9, &Decoder::opcode_11111001 }, // UOP_STACK_RESTORE (32-bit) - { 0xff, 0xfa, &Decoder::opcode_11111010 }, // UOP_STACK_RESTORE (32-bit) - { 0xff, 0xfb, &Decoder::opcode_11111011 }, // UOP_NOP (16-bit) - { 0xff, 0xfc, &Decoder::opcode_11111100 }, // UOP_NOP (32-bit) - { 0xff, 0xfd, &Decoder::opcode_11111101 }, // UOP_NOP (16-bit) / END - { 0xff, 0xfe, &Decoder::opcode_11111110 }, // UOP_NOP (32-bit) / END - { 0xff, 0xff, &Decoder::opcode_11111111 }, // UOP_END + { 0xff, 0xef, 2, &Decoder::opcode_11101111 }, // UOP_LDRPC_POSTINC (32-bit) + { 0xff, 0xf5, 2, &Decoder::opcode_11110101 }, // UOP_VPOP (32-bit) + { 0xff, 0xf6, 2, &Decoder::opcode_11110110 }, // UOP_VPOP (32-bit) + { 0xff, 0xf7, 3, &Decoder::opcode_11110111 }, // UOP_STACK_RESTORE (16-bit) + { 0xff, 0xf8, 4, &Decoder::opcode_11111000 }, // UOP_STACK_RESTORE (16-bit) + { 0xff, 0xf9, 3, &Decoder::opcode_11111001 }, // UOP_STACK_RESTORE (32-bit) + { 0xff, 0xfa, 4, &Decoder::opcode_11111010 }, // UOP_STACK_RESTORE (32-bit) + { 0xff, 0xfb, 1, &Decoder::opcode_11111011 }, // UOP_NOP (16-bit) + { 0xff, 0xfc, 1, &Decoder::opcode_11111100 }, // UOP_NOP (32-bit) + { 0xff, 0xfd, 1, &Decoder::opcode_11111101 }, // UOP_NOP (16-bit) / END + { 0xff, 0xfe, 1, &Decoder::opcode_11111110 }, // UOP_NOP (32-bit) / END + { 0xff, 0xff, 1, &Decoder::opcode_11111111 }, // UOP_END +}; + + +// Unwind opcodes for ARM64. +// https://docs.microsoft.com/en-us/cpp/build/arm64-exception-handling +const Decoder::RingEntry Decoder::Ring64[] = { + { 0xe0, 0x00, 1, &Decoder::opcode_alloc_s }, + { 0xe0, 0x20, 1, &Decoder::opcode_save_r19r20_x }, + { 0xc0, 0x40, 1, &Decoder::opcode_save_fplr }, + { 0xc0, 0x80, 1, &Decoder::opcode_save_fplr_x }, + { 0xf8, 0xc0, 2, &Decoder::opcode_alloc_m }, + { 0xfc, 0xc8, 2, &Decoder::opcode_save_regp }, + { 0xfc, 0xcc, 2, &Decoder::opcode_save_regp_x }, + { 0xfc, 0xd0, 2, &Decoder::opcode_save_reg }, + { 0xfe, 0xd4, 2, &Decoder::opcode_save_reg_x }, + { 0xfe, 0xd6, 2, &Decoder::opcode_save_lrpair }, + { 0xfe, 0xd8, 2, &Decoder::opcode_save_fregp }, + { 0xfe, 0xda, 2, &Decoder::opcode_save_fregp_x }, + { 0xfe, 0xdc, 2, &Decoder::opcode_save_freg }, + { 0xff, 0xde, 2, &Decoder::opcode_save_freg_x }, + { 0xff, 0xe0, 4, &Decoder::opcode_alloc_l }, + { 0xff, 0xe1, 1, &Decoder::opcode_setfp }, + { 0xff, 0xe2, 2, &Decoder::opcode_addfp }, + { 0xff, 0xe3, 1, &Decoder::opcode_nop }, + { 0xff, 0xe4, 1, &Decoder::opcode_end }, + { 0xff, 0xe5, 1, &Decoder::opcode_end_c }, }; void Decoder::printRegisters(const std::pair &RegisterMask) { @@ -493,18 +519,291 @@ bool Decoder::opcode_11111111(const uint8_t *OC, unsigned &Offset, return true; } +// ARM64 unwind codes start here. +bool Decoder::opcode_alloc_s(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t NumBytes = (OC[Offset] & 0x1F) << 4; + SW.startLine() << format("0x%02x ; %s sp, #%u\n", OC[Offset], + static_cast(Prologue ? "sub" : "add"), + NumBytes); + ++Offset; + return false; +} + +bool Decoder::opcode_save_r19r20_x(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Off = (OC[Offset] & 0x1F) << 3; + if (Prologue) + SW.startLine() << format( + "0x%02x ; stp x19, x20, [sp, #-%u]!\n", OC[Offset], Off); + else + SW.startLine() << format( + "0x%02x ; ldp x19, x20, [sp], #%u\n", OC[Offset], Off); + ++Offset; + return false; +} + +bool Decoder::opcode_save_fplr(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Off = (OC[Offset] & 0x3F) << 3; + SW.startLine() << format( + "0x%02x ; %s x29, x30, [sp, #%u]\n", OC[Offset], + static_cast(Prologue ? "stp" : "ldp"), Off); + ++Offset; + return false; +} + +bool Decoder::opcode_save_fplr_x(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Off = ((OC[Offset] & 0x3F) + 1) << 3; + if (Prologue) + SW.startLine() << format( + "0x%02x ; stp x29, x30, [sp, #-%u]!\n", OC[Offset], Off); + else + SW.startLine() << format( + "0x%02x ; ldp x29, x30, [sp], #%u\n", OC[Offset], Off); + ++Offset; + return false; +} + +bool Decoder::opcode_alloc_m(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t NumBytes = ((OC[Offset] & 0x07) << 8); + NumBytes |= (OC[Offset + 1] & 0xFF); + NumBytes <<= 4; + SW.startLine() << format("0x%02x%02x ; %s sp, #%u\n", + OC[Offset], OC[Offset + 1], + static_cast(Prologue ? "sub" : "add"), + NumBytes); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_regp(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = ((OC[Offset] & 0x03) << 8); + Reg |= (OC[Offset + 1] & 0xC0); + Reg >>= 6; + Reg += 19; + uint32_t Off = (OC[Offset + 1] & 0x3F) << 3; + SW.startLine() << format( + "0x%02x%02x ; %s x%u, x%u, [sp, #%u]\n", + OC[Offset], OC[Offset + 1], + static_cast(Prologue ? "stp" : "ldp"), Reg, Reg + 1, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_regp_x(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = ((OC[Offset] & 0x03) << 8); + Reg |= (OC[Offset + 1] & 0xC0); + Reg >>= 6; + Reg += 19; + uint32_t Off = ((OC[Offset + 1] & 0x3F) + 1) << 3; + if (Prologue) + SW.startLine() << format( + "0x%02x%02x ; stp x%u, x%u, [sp, #-%u]!\n", + OC[Offset], OC[Offset + 1], Reg, + Reg + 1, Off); + else + SW.startLine() << format( + "0x%02x%02x ; ldp x%u, x%u, [sp], #%u\n", + OC[Offset], OC[Offset + 1], Reg, + Reg + 1, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_reg(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = (OC[Offset] & 0x03) << 8; + Reg |= (OC[Offset + 1] & 0xC0); + Reg >>= 6; + Reg += 19; + uint32_t Off = (OC[Offset + 1] & 0x3F) << 3; + SW.startLine() << format("0x%02x%02x ; %s x%u, [sp, #%u]\n", + OC[Offset], OC[Offset + 1], + static_cast(Prologue ? "str" : "ldr"), + Reg, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_reg_x(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = (OC[Offset] & 0x01) << 8; + Reg |= (OC[Offset + 1] & 0xE0); + Reg >>= 5; + Reg += 19; + uint32_t Off = ((OC[Offset + 1] & 0x1F) + 1) << 3; + if (Prologue) + SW.startLine() << format("0x%02x%02x ; str x%u, [sp, #%u]!\n", + OC[Offset], OC[Offset + 1], Reg, Off); + else + SW.startLine() << format("0x%02x%02x ; ldr x%u, [sp], #%u\n", + OC[Offset], OC[Offset + 1], Reg, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_lrpair(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = (OC[Offset] & 0x01) << 8; + Reg |= (OC[Offset + 1] & 0xC0); + Reg >>= 6; + Reg *= 2; + Reg += 19; + uint32_t Off = (OC[Offset + 1] & 0x3F) << 3; + SW.startLine() << format("0x%02x%02x ; %s x%u, lr, [sp, #%u]\n", + OC[Offset], OC[Offset + 1], + static_cast(Prologue ? "stp" : "ldp"), + Reg, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_fregp(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = (OC[Offset] & 0x01) << 8; + Reg |= (OC[Offset + 1] & 0xC0); + Reg >>= 6; + Reg += 8; + uint32_t Off = (OC[Offset + 1] & 0x3F) << 3; + SW.startLine() << format("0x%02x%02x ; %s d%u, d%u, [sp, #%u]\n", + OC[Offset], OC[Offset + 1], + static_cast(Prologue ? "stp" : "ldp"), + Reg, Reg + 1, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_fregp_x(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = (OC[Offset] & 0x01) << 8; + Reg |= (OC[Offset + 1] & 0xC0); + Reg >>= 6; + Reg += 8; + uint32_t Off = ((OC[Offset + 1] & 0x3F) + 1) << 3; + if (Prologue) + SW.startLine() << format( + "0x%02x%02x ; stp d%u, d%u, [sp, #-%u]!\n", OC[Offset], + OC[Offset + 1], Reg, Reg + 1, Off); + else + SW.startLine() << format( + "0x%02x%02x ; ldp d%u, d%u, [sp], #%u\n", OC[Offset], + OC[Offset + 1], Reg, Reg + 1, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_freg(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = (OC[Offset] & 0x01) << 8; + Reg |= (OC[Offset + 1] & 0xC0); + Reg >>= 6; + Reg += 8; + uint32_t Off = (OC[Offset + 1] & 0x3F) << 3; + SW.startLine() << format("0x%02x%02x ; %s d%u, [sp, #%u]\n", + OC[Offset], OC[Offset + 1], + static_cast(Prologue ? "str" : "ldr"), + Reg, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_save_freg_x(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + uint32_t Reg = ((OC[Offset + 1] & 0xE0) >> 5) + 8; + uint32_t Off = ((OC[Offset + 1] & 0x1F) + 1) << 3; + if (Prologue) + SW.startLine() << format( + "0x%02x%02x ; str d%u, [sp, #-%u]!\n", OC[Offset], + OC[Offset + 1], Reg, Off); + else + SW.startLine() << format( + "0x%02x%02x ; ldr d%u, [sp], #%u\n", OC[Offset], + OC[Offset + 1], Reg, Off); + Offset += 2; + return false; +} + +bool Decoder::opcode_alloc_l(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + unsigned Off = + (OC[Offset + 1] << 16) | (OC[Offset + 2] << 8) | (OC[Offset + 3] << 0); + Off <<= 4; + SW.startLine() << format( + "0x%02x%02x%02x%02x ; %s sp, #%u\n", OC[Offset], OC[Offset + 1], + OC[Offset + 2], OC[Offset + 3], + static_cast(Prologue ? "sub" : "add"), Off); + Offset += 4; + return false; +} + +bool Decoder::opcode_setfp(const uint8_t *OC, unsigned &Offset, unsigned Length, + bool Prologue) { + SW.startLine() << format("0x%02x ; mov fp, sp\n", OC[Offset]); + ++Offset; + return false; +} + +bool Decoder::opcode_addfp(const uint8_t *OC, unsigned &Offset, unsigned Length, + bool Prologue) { + unsigned NumBytes = OC[Offset + 1] << 3; + SW.startLine() << format("0x%02x%02x ; add fp, sp, #%u\n", + OC[Offset], OC[Offset + 1], NumBytes); + Offset += 2; + return false; +} + +bool Decoder::opcode_nop(const uint8_t *OC, unsigned &Offset, unsigned Length, + bool Prologue) { + SW.startLine() << format("0x%02x ; nop\n", OC[Offset]); + ++Offset; + return false; +} + +bool Decoder::opcode_end(const uint8_t *OC, unsigned &Offset, unsigned Length, + bool Prologue) { + SW.startLine() << format("0x%02x ; end\n", OC[Offset]); + ++Offset; + return true; +} + +bool Decoder::opcode_end_c(const uint8_t *OC, unsigned &Offset, unsigned Length, + bool Prologue) { + SW.startLine() << format("0x%02x ; end_c\n", OC[Offset]); + ++Offset; + return true; +} + void Decoder::decodeOpcodes(ArrayRef Opcodes, unsigned Offset, bool Prologue) { assert((!Prologue || Offset == 0) && "prologue should always use offset 0"); - + const RingEntry* DecodeRing = isAArch64 ? Ring64 : Ring; bool Terminated = false; for (unsigned OI = Offset, OE = Opcodes.size(); !Terminated && OI < OE; ) { for (unsigned DI = 0;; ++DI) { - if ((Opcodes[OI] & Ring[DI].Mask) == Ring[DI].Value) { - Terminated = (this->*Ring[DI].Routine)(Opcodes.data(), OI, 0, Prologue); + if ((isAArch64 && (DI >= array_lengthof(Ring64))) || + (!isAArch64 && (DI >= array_lengthof(Ring)))) { + SW.startLine() << format("0x%02x ; Bad opcode!\n", + Opcodes.data()[Offset]); + ++OI; + break; + } + + if ((Opcodes[OI] & DecodeRing[DI].Mask) == DecodeRing[DI].Value) { + if (OI + DecodeRing[DI].Length > OE) { + SW.startLine() << format("Opcode 0x%02x goes past the unwind data\n", + Opcodes[OI]); + OI += DecodeRing[DI].Length; + break; + } + Terminated = + (this->*DecodeRing[DI].Routine)(Opcodes.data(), OI, 0, Prologue); break; } - assert(DI < array_lengthof(Ring) && "unhandled opcode"); } } } @@ -520,22 +819,36 @@ bool Decoder::dumpXDataRecord(const COFFObjectFile &COFF, uint64_t Offset = VA - SectionVA; const ulittle32_t *Data = reinterpret_cast(Contents.data() + Offset); - const ExceptionDataRecord XData(Data); + // Sanity check to ensure that the .xdata header is present. + // A header is one or two words, followed by at least one word to describe + // the unwind codes. Applicable to both ARM and AArch64. + if (Contents.size() - Offset < 8) + report_fatal_error(".xdata must be at least 8 bytes in size"); + + const ExceptionDataRecord XData(Data, isAArch64); DictScope XRS(SW, "ExceptionData"); - SW.printNumber("FunctionLength", XData.FunctionLength() << 1); + SW.printNumber("FunctionLength", + isAArch64 ? XData.FunctionLengthInBytesAArch64() : + XData.FunctionLengthInBytesARM()); SW.printNumber("Version", XData.Vers()); SW.printBoolean("ExceptionData", XData.X()); SW.printBoolean("EpiloguePacked", XData.E()); - SW.printBoolean("Fragment", XData.F()); + if (!isAArch64) + SW.printBoolean("Fragment", XData.F()); SW.printNumber(XData.E() ? "EpilogueOffset" : "EpilogueScopes", XData.EpilogueCount()); - SW.printNumber("ByteCodeLength", - static_cast(XData.CodeWords() * sizeof(uint32_t))); + uint64_t ByteCodeLength = XData.CodeWords() * sizeof(uint32_t); + SW.printNumber("ByteCodeLength", ByteCodeLength); + + if ((int64_t)(Contents.size() - Offset - 4 * HeaderWords(XData) - + (XData.E() ? 0 : XData.EpilogueCount() * 4) - + (XData.X() ? 8 : 0)) < (int64_t)ByteCodeLength) + report_fatal_error("Malformed unwind data"); if (XData.E()) { ArrayRef UC = XData.UnwindByteCode(); - if (!XData.F()) { + if (isAArch64 || !XData.F()) { ListScope PS(SW, "Prologue"); decodeOpcodes(UC, 0, /*Prologue=*/true); } @@ -544,16 +857,25 @@ bool Decoder::dumpXDataRecord(const COFFObjectFile &COFF, decodeOpcodes(UC, XData.EpilogueCount(), /*Prologue=*/false); } } else { + { + ListScope PS(SW, "Prologue"); + decodeOpcodes(XData.UnwindByteCode(), 0, /*Prologue=*/true); + } ArrayRef EpilogueScopes = XData.EpilogueScopes(); ListScope ESS(SW, "EpilogueScopes"); for (const EpilogueScope ES : EpilogueScopes) { DictScope ESES(SW, "EpilogueScope"); SW.printNumber("StartOffset", ES.EpilogueStartOffset()); - SW.printNumber("Condition", ES.Condition()); - SW.printNumber("EpilogueStartIndex", ES.EpilogueStartIndex()); + if (!isAArch64) + SW.printNumber("Condition", ES.Condition()); + SW.printNumber("EpilogueStartIndex", + isAArch64 ? ES.EpilogueStartIndexAArch64() + : ES.EpilogueStartIndexARM()); ListScope Opcodes(SW, "Opcodes"); - decodeOpcodes(XData.UnwindByteCode(), ES.EpilogueStartIndex(), + decodeOpcodes(XData.UnwindByteCode(), + isAArch64 ? ES.EpilogueStartIndexAArch64() + : ES.EpilogueStartIndexARM(), /*Prologue=*/false); } } @@ -725,8 +1047,9 @@ bool Decoder::dumpPackedEntry(const object::COFFObjectFile &COFF, } SW.printString("Function", formatSymbol(FunctionName, FunctionAddress)); - SW.printBoolean("Fragment", - RF.Flag() == RuntimeFunctionFlag::RFF_PackedFragment); + if (!isAArch64) + SW.printBoolean("Fragment", + RF.Flag() == RuntimeFunctionFlag::RFF_PackedFragment); SW.printNumber("FunctionLength", RF.FunctionLength()); SW.startLine() << "ReturnType: " << RF.Ret() << '\n'; SW.printBoolean("HomedParameters", RF.H()); @@ -749,6 +1072,10 @@ bool Decoder::dumpProcedureDataEntry(const COFFObjectFile &COFF, DictScope RFS(SW, "RuntimeFunction"); if (Entry.Flag() == RuntimeFunctionFlag::RFF_Unpacked) return dumpUnpackedEntry(COFF, Section, Offset, Index, Entry); + if (isAArch64) { + llvm::errs() << "Packed unwind data not yet supported for ARM64\n"; + return false; + } return dumpPackedEntry(COFF, Section, Offset, Index, Entry); } diff --git a/llvm/tools/llvm-readobj/ARMWinEHPrinter.h b/llvm/tools/llvm-readobj/ARMWinEHPrinter.h index 95f521702268..e271a1e6fe77 100644 --- a/llvm/tools/llvm-readobj/ARMWinEHPrinter.h +++ b/llvm/tools/llvm-readobj/ARMWinEHPrinter.h @@ -24,13 +24,16 @@ class Decoder { ScopedPrinter &SW; raw_ostream &OS; + bool isAArch64; struct RingEntry { uint8_t Mask; uint8_t Value; + uint8_t Length; bool (Decoder::*Routine)(const uint8_t *, unsigned &, unsigned, bool); }; static const RingEntry Ring[]; + static const RingEntry Ring64[]; bool opcode_0xxxxxxx(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, bool Prologue); @@ -75,6 +78,50 @@ class Decoder { bool opcode_11111111(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, bool Prologue); + // ARM64 unwind codes start here. + bool opcode_alloc_s(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, + bool Prologue); + bool opcode_save_r19r20_x(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_fplr(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_fplr_x(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_alloc_m(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, + bool Prologue); + bool opcode_save_regp(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_regp_x(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_reg(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_reg_x(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_lrpair(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_fregp(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_fregp_x(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_freg(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_save_freg_x(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + bool opcode_alloc_l(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, + bool Prologue); + bool opcode_setfp(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, + bool Prologue); + bool opcode_addfp(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, + bool Prologue); + bool opcode_nop(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, + bool Prologue); + bool opcode_end(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, + bool Prologue); + bool opcode_end_c(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, + bool Prologue); + bool opcode_save_next(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); + void decodeOpcodes(ArrayRef Opcodes, unsigned Offset, bool Prologue); @@ -107,7 +154,9 @@ class Decoder { const object::SectionRef Section); public: - Decoder(ScopedPrinter &SW) : SW(SW), OS(SW.getOStream()) {} + Decoder(ScopedPrinter &SW, bool isAArch64) : SW(SW), + OS(SW.getOStream()), + isAArch64(isAArch64) {} std::error_code dumpProcedureData(const object::COFFObjectFile &COFF); }; } diff --git a/llvm/tools/llvm-readobj/COFFDumper.cpp b/llvm/tools/llvm-readobj/COFFDumper.cpp index fe31c36b6025..26fe1aa622fc 100644 --- a/llvm/tools/llvm-readobj/COFFDumper.cpp +++ b/llvm/tools/llvm-readobj/COFFDumper.cpp @@ -1549,8 +1549,10 @@ void COFFDumper::printUnwindInfo() { Dumper.printData(Ctx); break; } + case COFF::IMAGE_FILE_MACHINE_ARM64: case COFF::IMAGE_FILE_MACHINE_ARMNT: { - ARM::WinEH::Decoder Decoder(W); + ARM::WinEH::Decoder Decoder(W, Obj->getMachine() == + COFF::IMAGE_FILE_MACHINE_ARM64); Decoder.dumpProcedureData(*Obj); break; }