From 0b61db423bfec26f110c27b7282c3f51aa0f3006 Mon Sep 17 00:00:00 2001 From: Eli Friedman Date: Tue, 4 Oct 2022 18:55:01 -0700 Subject: [PATCH] [AArch64][Windows] Add llvm-readobj support for save_any_reg unwind opcode. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This is primarily used for Arm64EC, but it could be used for other non-standard calling conventions. The testcase is based on an Arm64EC thunk generated by MSVC. The name save_any_reg comes from Microsoft documentation, but the full encoding isn't specified there; this is reverse-engineered from the behavior of the unwinder. (Thanks to Martin Storsjö for his example of how to write simple unwinder testcases by directly calling RtlVirtualUnwind.) Differential Revision: https://reviews.llvm.org/D135196 --- .../llvm-readobj/COFF/arm64-unwind-save_any_reg.s | 98 ++++++++++++++++ llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp | 129 ++++++++++++++++----- llvm/tools/llvm-readobj/ARMWinEHPrinter.h | 2 + 3 files changed, 203 insertions(+), 26 deletions(-) create mode 100644 llvm/test/tools/llvm-readobj/COFF/arm64-unwind-save_any_reg.s diff --git a/llvm/test/tools/llvm-readobj/COFF/arm64-unwind-save_any_reg.s b/llvm/test/tools/llvm-readobj/COFF/arm64-unwind-save_any_reg.s new file mode 100644 index 000000000000..bcf35f05809e --- /dev/null +++ b/llvm/test/tools/llvm-readobj/COFF/arm64-unwind-save_any_reg.s @@ -0,0 +1,98 @@ +// REQUIRES: aarch64-registered-target +// RUN: llvm-mc -filetype=obj -triple aarch64-windows %s -o %t.o +// RUN: llvm-readobj --unwind %t.o | FileCheck --strict-whitespace %s + +//CHECK: Prologue [ +//CHECK-NEXT: 0xe1 ; mov fp, sp +//CHECK-NEXT: 0x83 ; stp x29, x30, [sp, #-32]! +//CHECK-NEXT: 0xe6 ; save next +//CHECK-NEXT: 0xe6 ; save next +//CHECK-NEXT: 0xe6 ; save next +//CHECK-NEXT: 0xe6 ; save next +//CHECK-NEXT: 0xe76689 ; stp q6, q7, [sp, #-160]! +//CHECK-NEXT: 0xe4 ; end +//CHECK-NEXT: ] +//CHECK-NEXT: EpilogueScopes [ +//CHECK-NEXT: EpilogueScope { +//CHECK-NEXT: StartOffset: 12 +//CHECK-NEXT: EpilogueStartIndex: 10 +//CHECK-NEXT: Opcodes [ +//CHECK-NEXT: 0x83 ; ldp x29, x30, [sp], #32 +//CHECK-NEXT: 0xe74e88 ; ldp q14, q15, [sp, #128] +//CHECK-NEXT: 0xe74c86 ; ldp q12, q13, [sp, #96] +//CHECK-NEXT: 0xe74a84 ; ldp q10, q11, [sp, #64] +//CHECK-NEXT: 0xe74882 ; ldp q8, q9, [sp, #32] +//CHECK-NEXT: 0xe76689 ; ldp q6, q7, [sp], #160 +//CHECK-NEXT: 0xe3 ; nop +//CHECK-NEXT: 0xe3 ; nop +//CHECK-NEXT: 0xe4 ; end +//CHECK-NEXT: ] +//CHECK-NEXT: } +//CHECK-NEXT: ] + +//CHECK: Prologue [ +//CHECK-NEXT: 0xe70001 ; str x0, [sp, #8] +//CHECK-NEXT: 0xe70041 ; str d0, [sp, #8] +//CHECK-NEXT: 0xe70081 ; str q0, [sp, #16] +//CHECK-NEXT: 0xe72001 ; str x0, [sp, #-32]! +//CHECK-NEXT: 0xe77d01 ; stp x29, x30, [sp, #-32]! +//CHECK-NEXT: 0xe4 ; end +//CHECK-NEXT: ] +//CHECK-NEXT: EpilogueScopes [ +//CHECK-NEXT: ] + +.section .pdata,"dr" + .long func@IMGREL + .long "$unwind$func"@IMGREL + .long func2@IMGREL + .long "$unwind$func2"@IMGREL + + .text + .globl func +func: + stp q6, q7, [sp, #-160]! + stp q8, q9, [sp, #32] + stp q10, q11, [sp, #64] + stp q12, q13, [sp, #96] + stp q14, q15, [sp, #128] + stp x29, x30, [sp, #-32]! + mov x29, sp + str x0, [sp, #16] + str x9, [sp, #24] + ldr x0, [sp, #16] + ldr x8, [sp, #24] + blr x8 + ldp x29, x30, [sp], #32 + ldp q14, q15, [sp, #128] + ldp q12, q13, [sp, #96] + ldp q10, q11, [sp, #64] + ldp q8, q9, [sp, #32] + ldp q6, q7, [sp], #160 + nop + ldr x16, [x16] + br x16 + +func2: + ret + +.section .xdata,"dr" +"$unwind$func": +.byte 0x15, 0x00, 0x40, 0x40 +.byte 0x0c, 0x00, 0x80, 0x02 +.byte 0xe1, 0x83, 0xe6, 0xe6 +.byte 0xe6, 0xe6, 0xe7, 0x66 +.byte 0x89, 0xe4, 0x83, 0xe7 +.byte 0x4e, 0x88, 0xe7, 0x4c +.byte 0x86, 0xe7, 0x4a, 0x84 +.byte 0xe7, 0x48, 0x82, 0xe7 +.byte 0x66, 0x89, 0xe3, 0xe3 +.byte 0xe4, 0xe3, 0xe3, 0xe3 +"$unwind$func2": +.byte 0x15, 0x00, 0x00, 0x40 +.byte 0xe7, 0x00, 0x01 +.byte 0xe7, 0x00, 0x41 +.byte 0xe7, 0x00, 0x81 +.byte 0xe7, 0x20, 0x01 +.byte 0xe7, 0x7d, 0x01 +.byte 0xe4 +.fill 20, 1, 0xe3 diff --git a/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp b/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp index fab99c8b7e7d..188199c6c129 100644 --- a/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp +++ b/llvm/tools/llvm-readobj/ARMWinEHPrinter.cpp @@ -143,35 +143,35 @@ const Decoder::RingEntry Decoder::Ring[] = { { 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 }, - { 0xff, 0xe6, 1, &Decoder::opcode_save_next }, - { 0xff, 0xe8, 1, &Decoder::opcode_trap_frame }, - { 0xff, 0xe9, 1, &Decoder::opcode_machine_frame }, - { 0xff, 0xea, 1, &Decoder::opcode_context }, - { 0xff, 0xec, 1, &Decoder::opcode_clear_unwound_to_call }, + {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}, + {0xff, 0xe6, 1, &Decoder::opcode_save_next}, + {0xff, 0xe7, 3, &Decoder::opcode_save_any_reg}, + {0xff, 0xe8, 1, &Decoder::opcode_trap_frame}, + {0xff, 0xe9, 1, &Decoder::opcode_machine_frame}, + {0xff, 0xea, 1, &Decoder::opcode_context}, + {0xff, 0xec, 1, &Decoder::opcode_clear_unwound_to_call}, }; static void printRange(raw_ostream &OS, ListSeparator &LS, unsigned First, @@ -869,6 +869,83 @@ bool Decoder::opcode_save_next(const uint8_t *OC, unsigned &Offset, return false; } +bool Decoder::opcode_save_any_reg(const uint8_t *OC, unsigned &Offset, + unsigned Length, bool Prologue) { + // Whether the instruction has writeback + bool Writeback = (OC[Offset + 1] & 0x20) == 0x20; + // Whether the instruction is paired. (Paired instructions are required + // to save/restore adjacent registers.) + bool Paired = (OC[Offset + 1] & 0x40) == 0x40; + // The kind of register saved: + // - 0 is an x register + // - 1 is the low half of a q register + // - 2 is a whole q register + int RegKind = (OC[Offset + 2] & 0xC0) >> 6; + // Encoded register name (0 -> x0/q0, 1 -> x1/q1, etc.) + int Reg = OC[Offset + 1] & 0x1F; + // Encoded stack offset of load/store instruction; decoding varies by mode. + int StackOffset = OC[Offset + 2] & 0x3F; + if (Writeback) + StackOffset++; + if (!Writeback && !Paired && RegKind != 2) + StackOffset *= 8; + else + StackOffset *= 16; + + SW.startLine() << format("0x%02x%02x%02x ; ", OC[Offset], + OC[Offset + 1], OC[Offset + 2]); + + // Verify the encoding is in a form we understand. The high bit of the first + // byte, and mode 3 for the register kind are apparently reserved. The + // encoded register must refer to a valid register. + int MaxReg = 0x1F; + if (Paired) + --MaxReg; + if (RegKind == 0) + --MaxReg; + if ((OC[Offset + 1] & 0x80) == 0x80 || RegKind == 3 || Reg > MaxReg) { + SW.getOStream() << "invalid save_any_reg encoding\n"; + Offset += 3; + return false; + } + + if (Paired) { + if (Prologue) + SW.getOStream() << "stp "; + else + SW.getOStream() << "ldp "; + } else { + if (Prologue) + SW.getOStream() << "str "; + else + SW.getOStream() << "ldr "; + } + + char RegChar = 'x'; + if (RegKind == 1) { + RegChar = 'd'; + } else if (RegKind == 2) { + RegChar = 'q'; + } + + if (Paired) + SW.getOStream() << format("%c%d, %c%d, ", RegChar, Reg, RegChar, Reg + 1); + else + SW.getOStream() << format("%c%d, ", RegChar, Reg); + + if (Writeback) { + if (Prologue) + SW.getOStream() << format("[sp, #-%d]!\n", StackOffset); + else + SW.getOStream() << format("[sp], #%d\n", StackOffset); + } else { + SW.getOStream() << format("[sp, #%d]\n", StackOffset); + } + + Offset += 3; + return false; +} + bool Decoder::opcode_trap_frame(const uint8_t *OC, unsigned &Offset, unsigned Length, bool Prologue) { SW.startLine() << format("0x%02x ; trap frame\n", OC[Offset]); diff --git a/llvm/tools/llvm-readobj/ARMWinEHPrinter.h b/llvm/tools/llvm-readobj/ARMWinEHPrinter.h index ceaa866ff215..eb2393de8f67 100644 --- a/llvm/tools/llvm-readobj/ARMWinEHPrinter.h +++ b/llvm/tools/llvm-readobj/ARMWinEHPrinter.h @@ -121,6 +121,8 @@ class Decoder { bool Prologue); bool opcode_save_next(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, bool Prologue); + bool opcode_save_any_reg(const uint8_t *Opcodes, unsigned &Offset, + unsigned Length, bool Prologue); bool opcode_trap_frame(const uint8_t *Opcodes, unsigned &Offset, unsigned Length, bool Prologue); bool opcode_machine_frame(const uint8_t *Opcodes, unsigned &Offset, -- 2.11.4.GIT