| 1 | //===-- Win64EH.cpp - Win64 EH V3 Support -----------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file implements decoding helpers for V3 unwind information on Win64. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "llvm/Support/Win64EH.h" |
| 14 | #include "llvm/Support/Endian.h" |
| 15 | #include "llvm/Support/Error.h" |
| 16 | #include "llvm/Support/MathExtras.h" |
| 17 | |
| 18 | using namespace llvm; |
| 19 | using namespace llvm::Win64EH; |
| 20 | |
| 21 | StringRef Win64EH::getRegisterNameV3(unsigned Reg) { |
| 22 | static const char *const Names[] = { |
| 23 | "RAX" , "RCX" , "RDX" , "RBX" , "RSP" , "RBP" , "RSI" , "RDI" , |
| 24 | "R8" , "R9" , "R10" , "R11" , "R12" , "R13" , "R14" , "R15" , |
| 25 | "R16" , "R17" , "R18" , "R19" , "R20" , "R21" , "R22" , "R23" , |
| 26 | "R24" , "R25" , "R26" , "R27" , "R28" , "R29" , "R30" , "R31" , |
| 27 | }; |
| 28 | if (Reg >= std::size(Names)) |
| 29 | return "<invalid>" ; |
| 30 | return Names[Reg]; |
| 31 | } |
| 32 | |
| 33 | Expected<DecodedWOD> Win64EH::decodeWOD(ArrayRef<uint8_t> Pool, |
| 34 | unsigned Offset) { |
| 35 | if (Offset >= Pool.size()) |
| 36 | return createStringError(Fmt: "WOD pool overflow at offset %u" , Vals: Offset); |
| 37 | |
| 38 | uint8_t FirstByte = Pool[Offset]; |
| 39 | DecodedWOD W = {}; |
| 40 | |
| 41 | // Determine opcode from variable-width prefix encoding. |
| 42 | // The dispatch order matters: check shorter prefixes first since they |
| 43 | // occupy the lowest bits, then fall through to longer prefixes. |
| 44 | // 3-bit prefix (bits [2:0] >= 4): opcodes 4-7 |
| 45 | // 4-bit prefix (bits [3:0] >= 8): opcodes 8-10 |
| 46 | // 6-bit prefix (bits [5:0] == 0x20): opcode 32 (PUSH2) |
| 47 | // 8-bit prefix (full byte 0-3): opcodes 0-3 |
| 48 | uint8_t Low3 = FirstByte & 0x07; |
| 49 | |
| 50 | // 3-bit opcode: bits [2:0] in {4, 5, 6, 7} |
| 51 | if (Low3 >= 4) { |
| 52 | switch (Low3) { |
| 53 | case WOD_PUSH: { |
| 54 | W.Opcode = WOD_PUSH; |
| 55 | W.ByteSize = 1; |
| 56 | W.Register = (FirstByte >> 3) & 0x1F; // 5-bit register |
| 57 | return W; |
| 58 | } |
| 59 | case WOD_SAVE_NONVOL_FAR: { |
| 60 | W.Opcode = WOD_SAVE_NONVOL_FAR; |
| 61 | W.ByteSize = 5; |
| 62 | if (Offset + 5 > Pool.size()) |
| 63 | return createStringError(Fmt: "WOD_SAVE_NONVOL_FAR truncated at offset %u" , |
| 64 | Vals: Offset); |
| 65 | W.Register = (FirstByte >> 3) & 0x1F; |
| 66 | W.Displacement = support::endian::read32le(P: &Pool[Offset + 1]); |
| 67 | return W; |
| 68 | } |
| 69 | case WOD_SAVE_NONVOL: { |
| 70 | W.Opcode = WOD_SAVE_NONVOL; |
| 71 | W.ByteSize = 3; |
| 72 | if (Offset + 3 > Pool.size()) |
| 73 | return createStringError(Fmt: "WOD_SAVE_NONVOL truncated at offset %u" , |
| 74 | Vals: Offset); |
| 75 | W.Register = (FirstByte >> 3) & 0x1F; |
| 76 | W.Displacement = |
| 77 | (uint32_t)support::endian::read16le(P: &Pool[Offset + 1]) * 8; |
| 78 | return W; |
| 79 | } |
| 80 | case WOD_PUSH_CONSECUTIVE_2: { |
| 81 | W.Opcode = WOD_PUSH_CONSECUTIVE_2; |
| 82 | W.ByteSize = 1; |
| 83 | W.Register = (FirstByte >> 3) & 0x1F; |
| 84 | if (W.Register > 30) |
| 85 | return createStringError( |
| 86 | Fmt: "WOD_PUSH_CONSECUTIVE_2 Register=%u out of range [0,30] at pool " |
| 87 | "offset %u" , |
| 88 | Vals: W.Register, Vals: Offset); |
| 89 | return W; |
| 90 | } |
| 91 | default: |
| 92 | return createStringError(Fmt: "unknown WOD opcode 0x%02X at pool offset %u" , |
| 93 | Vals: FirstByte, Vals: Offset); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | // 4-bit opcode: bits [3:0] in {8, 9, 10, ...} |
| 98 | uint8_t Low4 = FirstByte & 0x0F; |
| 99 | if (Low4 >= 8) { |
| 100 | switch (Low4) { |
| 101 | case WOD_ALLOC_SMALL: { |
| 102 | W.Opcode = WOD_ALLOC_SMALL; |
| 103 | W.ByteSize = 1; |
| 104 | W.Size = (unsigned)(((FirstByte >> 4) & 0x0F) + 1) * 8; |
| 105 | return W; |
| 106 | } |
| 107 | case WOD_SAVE_XMM128_FAR: { |
| 108 | W.Opcode = WOD_SAVE_XMM128_FAR; |
| 109 | W.ByteSize = 5; |
| 110 | if (Offset + 5 > Pool.size()) |
| 111 | return createStringError(Fmt: "WOD_SAVE_XMM128_FAR truncated at offset %u" , |
| 112 | Vals: Offset); |
| 113 | W.Register = (FirstByte >> 4) & 0x0F; |
| 114 | W.Displacement = support::endian::read32le(P: &Pool[Offset + 1]); |
| 115 | return W; |
| 116 | } |
| 117 | case WOD_SAVE_XMM128: { |
| 118 | W.Opcode = WOD_SAVE_XMM128; |
| 119 | W.ByteSize = 3; |
| 120 | if (Offset + 3 > Pool.size()) |
| 121 | return createStringError(Fmt: "WOD_SAVE_XMM128 truncated at offset %u" , |
| 122 | Vals: Offset); |
| 123 | W.Register = (FirstByte >> 4) & 0x0F; |
| 124 | W.Displacement = |
| 125 | (uint32_t)support::endian::read16le(P: &Pool[Offset + 1]) * 16; |
| 126 | return W; |
| 127 | } |
| 128 | default: |
| 129 | return createStringError(Fmt: "unknown WOD opcode 0x%02X at pool offset %u" , |
| 130 | Vals: FirstByte, Vals: Offset); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | // 6-bit opcode: bits [5:0] == 0x20 (WOD_PUSH2) |
| 135 | uint8_t Low6 = FirstByte & 0x3F; |
| 136 | if (Low6 == WOD_PUSH2) { |
| 137 | W.Opcode = WOD_PUSH2; |
| 138 | W.ByteSize = 2; |
| 139 | if (Offset + 2 > Pool.size()) |
| 140 | return createStringError(Fmt: "WOD_PUSH2 truncated at offset %u" , Vals: Offset); |
| 141 | uint8_t SecondByte = Pool[Offset + 1]; |
| 142 | // First reg from bits [7:6] of first byte (2 bits) and bits [2:0] of second |
| 143 | // (3 bits) |
| 144 | W.Register = ((FirstByte >> 6) & 0x03) | ((SecondByte & 0x07) << 2); |
| 145 | W.Register2 = (SecondByte >> 3) & 0x1F; |
| 146 | return W; |
| 147 | } |
| 148 | |
| 149 | // 8-bit opcode: full byte is opcode (values 0-3) |
| 150 | switch (FirstByte) { |
| 151 | case WOD_SET_FPREG: { |
| 152 | W.Opcode = WOD_SET_FPREG; |
| 153 | W.ByteSize = 2; |
| 154 | if (Offset + 2 > Pool.size()) |
| 155 | return createStringError(Fmt: "WOD_SET_FPREG truncated at offset %u" , Vals: Offset); |
| 156 | uint8_t SecondByte = Pool[Offset + 1]; |
| 157 | W.Register = SecondByte & 0x0F; // 4-bit register |
| 158 | W.Displacement = (unsigned)((SecondByte >> 4) & 0x0F) * 16; |
| 159 | return W; |
| 160 | } |
| 161 | case WOD_ALLOC_HUGE: { |
| 162 | W.Opcode = WOD_ALLOC_HUGE; |
| 163 | W.ByteSize = 5; |
| 164 | if (Offset + 5 > Pool.size()) |
| 165 | return createStringError(Fmt: "WOD_ALLOC_HUGE truncated at offset %u" , Vals: Offset); |
| 166 | W.Size = support::endian::read32le(P: &Pool[Offset + 1]); |
| 167 | return W; |
| 168 | } |
| 169 | case WOD_ALLOC_LARGE: { |
| 170 | W.Opcode = WOD_ALLOC_LARGE; |
| 171 | W.ByteSize = 3; |
| 172 | if (Offset + 3 > Pool.size()) |
| 173 | return createStringError(Fmt: "WOD_ALLOC_LARGE truncated at offset %u" , |
| 174 | Vals: Offset); |
| 175 | W.Size = (uint32_t)support::endian::read16le(P: &Pool[Offset + 1]) * 8; |
| 176 | return W; |
| 177 | } |
| 178 | case WOD_PUSH_CANONICAL_FRAME: { |
| 179 | W.Opcode = WOD_PUSH_CANONICAL_FRAME; |
| 180 | W.ByteSize = 2; |
| 181 | if (Offset + 2 > Pool.size()) |
| 182 | return createStringError( |
| 183 | Fmt: "WOD_PUSH_CANONICAL_FRAME truncated at offset %u" , Vals: Offset); |
| 184 | W.Type = Pool[Offset + 1]; |
| 185 | return W; |
| 186 | } |
| 187 | default: |
| 188 | return createStringError(Fmt: "unknown WOD opcode 0x%02X at pool offset %u" , |
| 189 | Vals: FirstByte, Vals: Offset); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | Expected<DecodedUnwindInfoV3> |
| 194 | Win64EH::decodeUnwindInfoV3(ArrayRef<uint8_t> Data) { |
| 195 | if (Data.size() < 4) |
| 196 | return createStringError(Fmt: "V3 unwind info too short: %zu bytes" , |
| 197 | Vals: Data.size()); |
| 198 | |
| 199 | DecodedUnwindInfoV3 Info; |
| 200 | Info.Version = Data[0] & 0x07; |
| 201 | Info.Flags = (Data[0] >> 3) & 0x1F; |
| 202 | Info.SizeOfProlog = Data[1]; |
| 203 | Info.PayloadWords = Data[2]; |
| 204 | Info.NumberOfOps = Data[3] & 0x1F; |
| 205 | Info.NumberOfEpilogs = (Data[3] >> 5) & 0x07; |
| 206 | |
| 207 | // The fixed header is always 4 bytes. When UNW_FlagLarge is set, the first |
| 208 | // byte of the payload is the UNWIND_INFO_LARGE_V3 extension byte (which |
| 209 | // extends SizeOfProlog to 16 bits and widens prolog IP offset entries to |
| 210 | // 16 bits). That byte IS counted in PayloadWords. |
| 211 | unsigned Offset = 4; // Start of payload |
| 212 | |
| 213 | // Compute the end of the payload area declared by PayloadWords. All |
| 214 | // subsequent reads of payload structures (the optional UNWIND_INFO_LARGE_V3 |
| 215 | // byte, prolog IP offsets, epilog descriptors) must stay within this region; |
| 216 | // reading past it would either overflow the buffer or cross into the |
| 217 | // trailing handler/chain data, both of which indicate a malformed record. |
| 218 | unsigned PayloadEnd = 4 + Info.PayloadWords * 2; |
| 219 | if (PayloadEnd > Data.size()) |
| 220 | return createStringError( |
| 221 | Fmt: "V3 unwind info PayloadWords (%u) extends past end of buffer" , |
| 222 | Vals: Info.PayloadWords); |
| 223 | |
| 224 | bool IsLarge = Info.isLarge(); |
| 225 | if (IsLarge) { |
| 226 | if (Offset >= PayloadEnd) |
| 227 | return createStringError( |
| 228 | Fmt: "V3 unwind info with UNW_FlagLarge too short: PayloadWords (%u) " |
| 229 | "leaves no room for UNWIND_INFO_LARGE_V3" , |
| 230 | Vals: Info.PayloadWords); |
| 231 | Info.SizeOfProlog |= static_cast<uint16_t>(Data[Offset]) << 8; |
| 232 | Offset += 1; |
| 233 | } |
| 234 | |
| 235 | // Read prolog IP offsets (8-bit each, or 16-bit when LARGE) |
| 236 | for (unsigned I = 0; I < Info.NumberOfOps; ++I) { |
| 237 | if (IsLarge) { |
| 238 | if (Offset + 2 > PayloadEnd) |
| 239 | return createStringError( |
| 240 | Fmt: "V3 payload truncated reading prolog IP offset %u" , Vals: I); |
| 241 | Info.PrologIpOffsets.push_back(Elt: support::endian::read16le(P: &Data[Offset])); |
| 242 | Offset += 2; |
| 243 | } else { |
| 244 | if (Offset >= PayloadEnd) |
| 245 | return createStringError( |
| 246 | Fmt: "V3 payload truncated reading prolog IP offset %u" , Vals: I); |
| 247 | Info.PrologIpOffsets.push_back(Elt: Data[Offset++]); |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | // Read epilog descriptors |
| 252 | int32_t PrevResolvedOffset = 0; |
| 253 | // Index of the most recent full descriptor (NumberOfOps != 0). Per the V3 |
| 254 | // spec, a descriptor with NumberOfOps == 0 inherits its effective fields |
| 255 | // from the first *preceding* descriptor with NumberOfOps != 0, which is not |
| 256 | // necessarily the immediately preceding descriptor. |
| 257 | int BaseEpilogIdx = -1; |
| 258 | for (unsigned I = 0; I < Info.NumberOfEpilogs; ++I) { |
| 259 | DecodedEpilogV3 Epi; |
| 260 | if (Offset >= PayloadEnd) |
| 261 | return createStringError( |
| 262 | Fmt: "V3 payload truncated reading epilog %u FlagsAndNumOps" , Vals: I); |
| 263 | uint8_t FlagsAndNumOps = Data[Offset++]; |
| 264 | Epi.Flags = FlagsAndNumOps & 0x07; |
| 265 | Epi.NumberOfOps = (FlagsAndNumOps >> 3) & 0x1F; |
| 266 | |
| 267 | if (Offset + 2 > PayloadEnd) |
| 268 | return createStringError( |
| 269 | Fmt: "V3 payload truncated reading epilog %u EpilogOffset" , Vals: I); |
| 270 | int16_t RawOffset = |
| 271 | static_cast<int16_t>(support::endian::read16le(P: &Data[Offset])); |
| 272 | Offset += 2; |
| 273 | |
| 274 | // The first epilog's EpilogOffset is absolute (from fragment start or |
| 275 | // tail). Subsequent epilogs store a delta from the previous epilog's |
| 276 | // resolved position. Accumulate to resolve all to absolute. |
| 277 | if (I == 0) |
| 278 | Epi.EpilogOffset = RawOffset; |
| 279 | else |
| 280 | Epi.EpilogOffset = PrevResolvedOffset + RawOffset; |
| 281 | PrevResolvedOffset = Epi.EpilogOffset; |
| 282 | |
| 283 | // Inherited descriptors (NumberOfOps == 0) are only 3 bytes: |
| 284 | // FlagsAndNumOps(1) + EpilogOffset(2). They have no FirstOp, |
| 285 | // IpOffsetOfLastInstruction, or IP offset fields appended; instead, the |
| 286 | // effective NumberOfOps, FirstOp, IpOffsetOfLastInstruction, and IP offset |
| 287 | // array are inherited from the first preceding descriptor with |
| 288 | // NumberOfOps != 0 (the "base"). Per the V3 spec, Flags bits 0 and 1 are |
| 289 | // NOT inherited: the producer is required to replicate them into this |
| 290 | // record, so we keep the value read from this descriptor's own flags byte. |
| 291 | // |
| 292 | // If there is no preceding base descriptor to inherit from — the record is |
| 293 | // malformed. We leave the extended fields zero-initialized so callers can |
| 294 | // still see the (broken) header and EpilogOffset; downstream consumers |
| 295 | // (e.g. the dumpers) surface a warning when they encounter NumberOfOps == 0 |
| 296 | // at index 0. |
| 297 | if (Epi.NumberOfOps == 0) { |
| 298 | if (BaseEpilogIdx >= 0) { |
| 299 | const DecodedEpilogV3 &Base = Info.Epilogs[BaseEpilogIdx]; |
| 300 | // Flags bits 0 and 1 are producer-replicated, not inherited: a |
| 301 | // compliant producer must have written the base's values into this |
| 302 | // descriptor's own flags byte, so they should already match the base. |
| 303 | // We intentionally keep this record's own bits (rather than copying |
| 304 | // the base's) so a non-compliant producer is not silently masked. This |
| 305 | // decoder runs on potentially-malformed object files, so a mismatch is |
| 306 | // not asserted here; downstream consumers (the dumpers) surface a |
| 307 | // warning when the replicated bits disagree with the base. |
| 308 | Epi.FirstOp = Base.FirstOp; |
| 309 | Epi.IpOffsetOfLastInstruction = Base.IpOffsetOfLastInstruction; |
| 310 | Epi.IpOffsets = Base.IpOffsets; |
| 311 | } else { |
| 312 | Epi.FirstOp = 0; |
| 313 | Epi.IpOffsetOfLastInstruction = 0; |
| 314 | } |
| 315 | Info.Epilogs.push_back(Elt: std::move(Epi)); |
| 316 | continue; |
| 317 | } |
| 318 | |
| 319 | bool EpiLarge = Epi.isLarge(); |
| 320 | |
| 321 | if (Offset + 2 > PayloadEnd) |
| 322 | return createStringError(Fmt: "V3 payload truncated reading epilog %u FirstOp" , |
| 323 | Vals: I); |
| 324 | Epi.FirstOp = support::endian::read16le(P: &Data[Offset]); |
| 325 | Offset += 2; |
| 326 | |
| 327 | // IpOffsetOfLastInstruction: 8-bit normally, 16-bit when EPILOG_INFO_LARGE |
| 328 | if (EpiLarge) { |
| 329 | if (Offset + 2 > PayloadEnd) |
| 330 | return createStringError( |
| 331 | Fmt: "V3 payload truncated reading epilog %u IpOffsetOfLastInstruction" , |
| 332 | Vals: I); |
| 333 | Epi.IpOffsetOfLastInstruction = support::endian::read16le(P: &Data[Offset]); |
| 334 | Offset += 2; |
| 335 | } else { |
| 336 | if (Offset >= PayloadEnd) |
| 337 | return createStringError( |
| 338 | Fmt: "V3 payload truncated reading epilog %u IpOffsetOfLastInstruction" , |
| 339 | Vals: I); |
| 340 | Epi.IpOffsetOfLastInstruction = Data[Offset++]; |
| 341 | } |
| 342 | |
| 343 | // Read epilog IP offsets (8-bit each, or 16-bit when EPILOG_INFO_LARGE) |
| 344 | for (unsigned J = 0; J < Epi.NumberOfOps; ++J) { |
| 345 | if (EpiLarge) { |
| 346 | if (Offset + 2 > PayloadEnd) |
| 347 | return createStringError( |
| 348 | Fmt: "V3 payload truncated reading epilog %u IP offset %u" , Vals: I, Vals: J); |
| 349 | Epi.IpOffsets.push_back(Elt: support::endian::read16le(P: &Data[Offset])); |
| 350 | Offset += 2; |
| 351 | } else { |
| 352 | if (Offset >= PayloadEnd) |
| 353 | return createStringError( |
| 354 | Fmt: "V3 payload truncated reading epilog %u IP offset %u" , Vals: I, Vals: J); |
| 355 | Epi.IpOffsets.push_back(Elt: Data[Offset++]); |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | // This is a full descriptor (NumberOfOps != 0); it becomes the base that |
| 360 | // subsequent inherited descriptors reference. |
| 361 | BaseEpilogIdx = Info.Epilogs.size(); |
| 362 | Info.Epilogs.push_back(Elt: std::move(Epi)); |
| 363 | } |
| 364 | |
| 365 | // Identify WOD pool: everything from current offset until the end of |
| 366 | // the payload area declared by PayloadWords. |
| 367 | if (Offset < PayloadEnd) |
| 368 | Info.WODPool = Data.slice(N: Offset, M: PayloadEnd - Offset); |
| 369 | else |
| 370 | Info.WODPool = ArrayRef<uint8_t>(); |
| 371 | |
| 372 | // When PayloadWords is odd, the encoder emits 2 trailing zero bytes inside |
| 373 | // the payload region as padding before the handler/chain. Report the |
| 374 | // aligned offset so consumers locate the next field correctly. |
| 375 | Info.PayloadSize = alignTo(Value: PayloadEnd, Align: 4); |
| 376 | |
| 377 | return Info; |
| 378 | } |
| 379 | |