[llvm-readobj] Fix printing of Windows ARM unwind opcodes, add tests

The existing code was essentially untested; in some cases, it used
too narrow variable types to fit all the bits, in some cases the
bit manipulation operations were incorrect.

For the "ldr lr, [sp], #x" opcode, there's nothing in the documentation
that says it cannot be used in a prologue. (In practice, it would
probably seldom be used there, but technically there's nothing
stopping it from being used.) The documentation only specifies the
operation to replay for unwinding it, but the corresponding mirror
instruction to be printed for a prologue is "str lr, [sp, #-x]!".

Also improve printing of register masks, by aggregating registers
into ranges where possible, and make the printing of the terminating
branches clearer, as "bx <reg>" and "b.w <target>".

Differential Revision: https://reviews.llvm.org/D125643
This commit is contained in:
Martin Storsjö 2021-11-23 00:44:58 +02:00
parent d81064949f
commit 92f1028ceb
2 changed files with 290 additions and 29 deletions

View File

@ -0,0 +1,239 @@
// REQUIRES: arm-registered-target
// RUN: llvm-mc -filetype=obj -triple thumbv7-windows-gnu %s -o %t.o
// RUN: llvm-readobj --unwind %t.o | FileCheck --strict-whitespace %s
// CHECK: RuntimeFunction {
// CHECK-NEXT: Function: func0
// CHECK: Prologue [
// CHECK-NEXT: 0xcb ; mov r11, sp
// CHECK-NEXT: 0x95 0x00 ; push.w {r8, r10, r12}
// CHECK-NEXT: 0xf6 0x13 ; vpush {d17-d19}
// CHECK-NEXT: 0xfc ; nop.w
// CHECK-NEXT: 0xf5 0x35 ; vpush {d3-d5}
// CHECK-NEXT: 0xfb ; nop
// CHECK-NEXT: 0xe2 ; vpush {d8-d10}
// CHECK-NEXT: 0x08 ; sub sp, #(8 * 4)
// CHECK-NEXT: 0xd6 ; push {r4-r6, lr}
// CHECK-NEXT: ]
// CHECK-NEXT: EpilogueScopes [
// CHECK-NEXT: EpilogueScope {
// CHECK-NEXT: StartOffset: 15
// CHECK-NEXT: Condition: 14
// CHECK-NEXT: EpilogueStartIndex: 13
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0xe2 ; vpop {d8-d10}
// CHECK-NEXT: 0xcb ; mov sp, r11
// CHECK-NEXT: 0x08 ; add sp, #(8 * 4)
// CHECK-NEXT: 0xd6 ; pop {r4-r6, pc}
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK: RuntimeFunction {
// CHECK-NEXT: Function: func1
// CHECK: Prologue [
// CHECK-NEXT: 0xef 0x08 ; str.w lr, [sp, #-32]!
// CHECK-NEXT: 0xd1 ; push {r4-r5}
// CHECK-NEXT: 0xfd ; bx <reg>
// CHECK-NEXT: ]
// CHECK-NEXT: EpilogueScopes [
// CHECK-NEXT: EpilogueScope {
// CHECK-NEXT: StartOffset: 4
// CHECK-NEXT: Condition: 14
// CHECK-NEXT: EpilogueStartIndex: 4
// CHECK-NEXT: Opcodes [
// CHECK-NEXT: 0xef 0x08 ; ldr.w lr, [sp], #32
// CHECK-NEXT: 0xd1 ; pop {r4-r5}
// CHECK-NEXT: 0xfd ; bx <reg>
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK: RuntimeFunction {
// CHECK-NEXT: Function: func2
// CHECK-NEXT: ExceptionRecord:
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength:
// CHECK-NEXT: Version:
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: Yes
// CHECK-NEXT: Fragment: No
// CHECK-NEXT: EpilogueOffset: 0
// CHECK-NEXT: ByteCodeLength:
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0x04 ; sub sp, #(4 * 4)
// CHECK-NEXT: 0xec 0x80 ; push {r7}
// CHECK-NEXT: 0xc7 ; mov r7, sp
// CHECK-NEXT: 0xfe ; b.w <target>
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: func3
// CHECK-NEXT: ExceptionRecord:
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength:
// CHECK-NEXT: Version:
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: Yes
// CHECK-NEXT: Fragment: Yes
// CHECK-NEXT: EpilogueOffset: 1
// CHECK-NEXT: ByteCodeLength:
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0x04 ; sub sp, #(4 * 4)
// CHECK-NEXT: 0xdf ; push.w {r4-r11, lr}
// CHECK-NEXT: ]
// CHECK-NEXT: Epilogue [
// CHECK-NEXT: 0xdf ; pop.w {r4-r11, pc}
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: func4
// CHECK-NEXT: ExceptionRecord:
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength:
// CHECK-NEXT: Version:
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: Yes
// CHECK-NEXT: Fragment: No
// CHECK-NEXT: EpilogueOffset: 0
// CHECK-NEXT: ByteCodeLength:
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xec 0x50 ; push {r4, r6}
// CHECK-NEXT: 0xb5 0x00 ; push.w {r8, r10, r12, lr}
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
// CHECK-NEXT: RuntimeFunction {
// CHECK-NEXT: Function: func5
// CHECK-NEXT: ExceptionRecord:
// CHECK-NEXT: ExceptionData {
// CHECK-NEXT: FunctionLength:
// CHECK-NEXT: Version:
// CHECK-NEXT: ExceptionData: No
// CHECK-NEXT: EpiloguePacked: Yes
// CHECK-NEXT: Fragment: No
// CHECK-NEXT: EpilogueOffset: 16
// CHECK-NEXT: ByteCodeLength:
// CHECK-NEXT: Prologue [
// CHECK-NEXT: 0xfa 0x00 0x00 0x20 ; sub.w sp, sp, #(32 * 4)
// CHECK-NEXT: 0xf9 0x00 0x10 ; sub.w sp, sp, #(16 * 4)
// CHECK-NEXT: 0xf8 0x00 0x00 0x08 ; sub sp, sp, #(8 * 4)
// CHECK-NEXT: 0xf7 0x00 0x04 ; sub sp, sp, #(4 * 4)
// CHECK-NEXT: 0xe8 0x02 ; sub.w sp, #(2 * 4)
// CHECK-NEXT: 0xed 0x50 ; push {r4, r6, lr}
// CHECK-NEXT: ]
// CHECK-NEXT: Epilogue [
// CHECK-NEXT: 0xed 0x50 ; pop {r4, r6, pc}
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: }
.thumb
.syntax unified
func0:
push {r4-r6, lr}
sub sp, sp, #32
vpush {d8-d10}
nop
vpush {d3-d5}
nop.w
vpush {d17-d19}
push {r8, r10, r12}
mov r11, sp
nop
vpop {d8-d10}
mov sp, r11
add sp, sp, #32
pop {r4-r6, pc}
func1:
push {r4-r5}
str lr, [sp, #-32]!
nop
ldr lr, [sp], #32
pop {r4-r5}
bx lr
func2:
mov r7, sp
push {r7}
sub sp, sp, #16
nop
add sp, sp, #16
pop {r7}
mov sp, r7
b tailcall
func3:
nop.w
nop
nop
add sp, sp, #16
pop {r4-r11, pc}
func4:
push {r8, r10, r12, lr}
push {r4, r6}
nop
pop {r4, r6}
pop {r8, r10, r12, pc}
func5:
push {r4, r6, lr}
subw sp, sp, #8
sub sp, sp, #16
sub sp, sp, #32
subw sp, sp, #64
subw sp, sp, #128
nop
pop {r4, r6, pc}
.section .pdata,"dr"
.rva func0
.rva .Lunwind_func0
.rva func1
.rva .Lunwind_func1
.rva func2
.rva .Lunwind_func2
.rva func3
.rva .Lunwind_func3
.rva func4
.rva .Lunwind_func4
.rva func5
.rva .Lunwind_func5
.section .xdata,"dr"
.Lunwind_func0:
.byte 0x14, 0x00, 0x80, 0x50
.byte 0x0f, 0x00, 0xe0, 0x0d
.byte 0xcb, 0x95, 0x00, 0xf6
.byte 0x13, 0xfc, 0xf5, 0x35
.byte 0xfb, 0xe2, 0x08, 0xd6
.byte 0xff, 0xe2, 0xcb, 0x08
.byte 0xd6, 0xff, 0x00, 0x00
.Lunwind_func1:
.byte 0x08, 0x00, 0x00, 0x00
.byte 0x01, 0x00, 0x02, 0x00
.byte 0x04, 0x00, 0xe0, 0x04
.byte 0xef, 0x08, 0xd1, 0xfd
.byte 0xef, 0x08, 0xd1, 0xfd
.Lunwind_func2:
.byte 0x09, 0x00, 0x20, 0x20
.byte 0x04, 0xec, 0x80, 0xc7
.byte 0xfe, 0x00, 0x00, 0x00
.Lunwind_func3:
.byte 0x07, 0x00, 0xe0, 0x10
.byte 0x04, 0xdf, 0xff, 0x00
.Lunwind_func4:
.byte 0x07, 0x00, 0x20, 0x20
.byte 0xec, 0x50, 0xb5, 0x00
.byte 0xff, 0x00, 0x00, 0x00
.Lunwind_func5:
.byte 0x0b, 0x00, 0x20, 0x58
.byte 0xfa, 0x00, 0x00, 0x20
.byte 0xf9, 0x00, 0x10, 0xf8
.byte 0x00, 0x00, 0x08, 0xf7
.byte 0x00, 0x04, 0xe8, 0x02
.byte 0xed, 0x50, 0xff, 0x00

View File

@ -78,10 +78,10 @@ raw_ostream &operator<<(raw_ostream &OS, const ARM::WinEH::ReturnType &RT) {
OS << "pop {pc}";
break;
case ARM::WinEH::ReturnType::RT_B:
OS << "b target";
OS << "bx <reg>";
break;
case ARM::WinEH::ReturnType::RT_BW:
OS << "b.w target";
OS << "b.w <target>";
break;
case ARM::WinEH::ReturnType::RT_NoEpilogue:
OS << "(no epilogue)";
@ -174,26 +174,45 @@ const Decoder::RingEntry Decoder::Ring64[] = {
{ 0xff, 0xec, 1, &Decoder::opcode_clear_unwound_to_call },
};
void Decoder::printRegisters(const std::pair<uint16_t, uint32_t> &RegisterMask) {
static const char * const GPRRegisterNames[16] = {
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10",
"r11", "ip", "sp", "lr", "pc",
};
static void printRange(raw_ostream &OS, ListSeparator &LS, unsigned First,
unsigned Last, char Letter) {
if (First == Last)
OS << LS << Letter << First;
else
OS << LS << Letter << First << "-" << Letter << Last;
}
static void printRange(raw_ostream &OS, uint32_t Mask, ListSeparator &LS,
unsigned Start, unsigned End, char Letter) {
int First = -1;
for (unsigned RI = Start; RI <= End; ++RI) {
if (Mask & (1 << RI)) {
if (First < 0)
First = RI;
} else {
if (First >= 0) {
printRange(OS, LS, First, RI - 1, Letter);
First = -1;
}
}
}
if (First >= 0)
printRange(OS, LS, First, End, Letter);
}
void Decoder::printRegisters(
const std::pair<uint16_t, uint32_t> &RegisterMask) {
const uint16_t GPRMask = std::get<0>(RegisterMask);
const uint16_t VFPMask = std::get<1>(RegisterMask);
const uint32_t VFPMask = std::get<1>(RegisterMask);
OS << '{';
ListSeparator LS;
for (unsigned RI = 0, RE = 11; RI < RE; ++RI)
if (GPRMask & (1 << RI))
OS << LS << GPRRegisterNames[RI];
for (unsigned RI = 0, RE = 32; RI < RE; ++RI)
if (VFPMask & (1 << RI))
OS << LS << "d" << unsigned(RI);
for (unsigned RI = 11, RE = 16; RI < RE; ++RI)
if (GPRMask & (1 << RI))
OS << LS << GPRRegisterNames[RI];
printRange(OS, GPRMask, LS, 0, 12, 'r');
printRange(OS, VFPMask, LS, 0, 31, 'd');
if (GPRMask & (1 << 14))
OS << LS << "lr";
if (GPRMask & (1 << 15))
OS << LS << "pc";
OS << '}';
}
@ -346,7 +365,7 @@ bool Decoder::opcode_1100xxxx(const uint8_t *OC, unsigned &Offset,
bool Decoder::opcode_11010Lxx(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
unsigned Link = (OC[Offset] & 0x4) >> 3;
unsigned Link = (OC[Offset] & 0x4) >> 2;
unsigned Count = (OC[Offset] & 0x3);
uint16_t GPRMask = (Link << (Prologue ? 14 : 15))
@ -407,8 +426,8 @@ bool Decoder::opcode_111010xx(const uint8_t *OC, unsigned &Offset,
bool Decoder::opcode_1110110L(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
uint8_t GPRMask = ((OC[Offset + 0] & 0x01) << (Prologue ? 14 : 15))
| ((OC[Offset + 1] & 0xff) << 0);
uint16_t GPRMask = ((OC[Offset + 0] & 0x01) << (Prologue ? 14 : 15))
| ((OC[Offset + 1] & 0xff) << 0);
SW.startLine() << format("0x%02x 0x%02x ; %s ", OC[Offset + 0],
OC[Offset + 1], Prologue ? "push" : "pop");
@ -437,11 +456,13 @@ bool Decoder::opcode_11101110(const uint8_t *OC, unsigned &Offset,
bool Decoder::opcode_11101111(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
assert(!Prologue && "may not be used in prologue");
if (OC[Offset + 1] & 0xf0)
SW.startLine() << format("0x%02x 0x%02x ; reserved\n",
OC[Offset + 0], OC[Offset + 1]);
else if (Prologue)
SW.startLine()
<< format("0x%02x 0x%02x ; str.w lr, [sp, #-%u]!\n",
OC[Offset + 0], OC[Offset + 1], OC[Offset + 1] << 2);
else
SW.startLine()
<< format("0x%02x 0x%02x ; ldr.w lr, [sp], #%u\n",
@ -455,7 +476,7 @@ bool Decoder::opcode_11110101(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
unsigned Start = (OC[Offset + 1] & 0xf0) >> 4;
unsigned End = (OC[Offset + 1] & 0x0f) >> 0;
uint32_t VFPMask = ((1 << (End - Start)) - 1) << Start;
uint32_t VFPMask = ((1 << (End + 1 - Start)) - 1) << Start;
SW.startLine() << format("0x%02x 0x%02x ; %s ", OC[Offset + 0],
OC[Offset + 1], Prologue ? "vpush" : "vpop");
@ -470,7 +491,7 @@ bool Decoder::opcode_11110110(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
unsigned Start = (OC[Offset + 1] & 0xf0) >> 4;
unsigned End = (OC[Offset + 1] & 0x0f) >> 0;
uint32_t VFPMask = ((1 << (End - Start)) - 1) << 16;
uint32_t VFPMask = ((1 << (End + 1 - Start)) - 1) << (16 + Start);
SW.startLine() << format("0x%02x 0x%02x ; %s ", OC[Offset + 0],
OC[Offset + 1], Prologue ? "vpush" : "vpop");
@ -553,14 +574,14 @@ bool Decoder::opcode_11111100(const uint8_t *OC, unsigned &Offset,
bool Decoder::opcode_11111101(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
SW.startLine() << format("0x%02x ; b\n", OC[Offset]);
SW.startLine() << format("0x%02x ; bx <reg>\n", OC[Offset]);
++Offset;
return true;
}
bool Decoder::opcode_11111110(const uint8_t *OC, unsigned &Offset,
unsigned Length, bool Prologue) {
SW.startLine() << format("0x%02x ; b.w\n", OC[Offset]);
SW.startLine() << format("0x%02x ; b.w <target>\n", OC[Offset]);
++Offset;
return true;
}
@ -948,7 +969,7 @@ bool Decoder::dumpXDataRecord(const COFFObjectFile &COFF,
if (XData.E()) {
ArrayRef<uint8_t> UC = XData.UnwindByteCode();
if (isAArch64 || !XData.F()) {
{
ListScope PS(SW, "Prologue");
decodeOpcodes(UC, 0, /*Prologue=*/true);
}
@ -971,8 +992,9 @@ bool Decoder::dumpXDataRecord(const COFFObjectFile &COFF,
SW.printNumber("EpilogueStartIndex",
isAArch64 ? ES.EpilogueStartIndexAArch64()
: ES.EpilogueStartIndexARM());
if (ES.ES & ~0xffc3ffff)
SW.printNumber("ReservedBits", (ES.ES >> 18) & 0xF);
unsigned ReservedMask = isAArch64 ? 0xF : 0x3;
if ((ES.ES >> 18) & ReservedMask)
SW.printNumber("ReservedBits", (ES.ES >> 18) & ReservedMask);
ListScope Opcodes(SW, "Opcodes");
decodeOpcodes(XData.UnwindByteCode(),