github.com/saferwall/pe@v1.5.2/exception.go (about) 1 // Copyright 2018 Saferwall. All rights reserved. 2 // Use of this source code is governed by Apache v2 license 3 // license that can be found in the LICENSE file. 4 5 package pe 6 7 import ( 8 "encoding/binary" 9 "strconv" 10 ) 11 12 const ( 13 // Unwind information flags. 14 15 // UnwFlagNHandler - The function has no handler. 16 UnwFlagNHandler = uint8(0x0) 17 18 // UnwFlagEHandler - The function has an exception handler that should 19 // be called when looking for functions that need to examine exceptions. 20 UnwFlagEHandler = uint8(0x1) 21 22 // UnwFlagUHandler - The function has a termination handler that should 23 // be called when unwinding an exception. 24 UnwFlagUHandler = uint8(0x2) 25 26 // UnwFlagChainInfo - This unwind info structure is not the primary one 27 // for the procedure. Instead, the chained unwind info entry is the contents 28 // of a previous RUNTIME_FUNCTION entry. For information, see Chained unwind 29 // info structures. If this flag is set, then the UNW_FLAG_EHANDLER and 30 // UNW_FLAG_UHANDLER flags must be cleared. Also, the frame register and 31 // fixed-stack allocation field must have the same values as in the primary 32 // unwind info. 33 UnwFlagChainInfo = uint8(0x4) 34 ) 35 36 // The meaning of the operation info bits depends upon the operation code. 37 // To encode a general-purpose (integer) register, this mapping is used: 38 const ( 39 rax = iota 40 rcx 41 rdx 42 rbx 43 rsp 44 rbp 45 rsi 46 rdi 47 r8 48 r9 49 r10 50 r11 51 r12 52 r13 53 r14 54 r15 55 ) 56 57 // OpInfoRegisters maps registers to string. 58 var OpInfoRegisters = map[uint8]string{ 59 rax: "RAX", 60 rcx: "RCX", 61 rdx: "RDX", 62 rbx: "RBX", 63 rsp: "RSP", 64 rbp: "RBP", 65 rsi: "RSI", 66 rdi: "RDI", 67 r8: "R8", 68 r9: "R9", 69 r10: "R10", 70 r11: "R11", 71 r12: "R12", 72 r13: "R13", 73 r14: "R14", 74 r15: "R15", 75 } 76 77 // UnwindOpType represents the type of an unwind opcode. 78 type UnwindOpType uint8 79 80 // _UNWIND_OP_CODES 81 const ( 82 // Push a nonvolatile integer register, decrementing RSP by 8. The 83 // operation info is the number of the register. Because of the constraints 84 // on epilogs, UWOP_PUSH_NONVOL unwind codes must appear first in the 85 // prolog and correspondingly, last in the unwind code array. This relative 86 // ordering applies to all other unwind codes except UWOP_PUSH_MACHFRAME. 87 UwOpPushNonVol = UnwindOpType(0) 88 89 // Allocate a large-sized area on the stack. There are two forms. If the 90 // operation info equals 0, then the size of the allocation divided by 8 is 91 // recorded in the next slot, allowing an allocation up to 512K - 8. If the 92 // operation info equals 1, then the unscaled size of the allocation is 93 // recorded in the next two slots in little-endian format, allowing 94 // allocations up to 4GB - 8. 95 UwOpAllocLarge = UnwindOpType(1) 96 97 // Allocate a small-sized area on the stack. The size of the allocation is 98 // the operation info field * 8 + 8, allowing allocations from 8 to 128 99 // bytes. 100 UwOpAllocSmall = UnwindOpType(2) 101 102 // Establish the frame pointer register by setting the register to some 103 // offset of the current RSP. The offset is equal to the Frame Register 104 // offset (scaled) field in the UNWIND_INFO * 16, allowing offsets from 0 105 // to 240. The use of an offset permits establishing a frame pointer that 106 // points to the middle of the fixed stack allocation, helping code density 107 // by allowing more accesses to use short instruction forms. The operation 108 // info field is reserved and shouldn't be used. 109 UwOpSetFpReg = UnwindOpType(3) 110 111 // Save a nonvolatile integer register on the stack using a MOV instead of 112 // a PUSH. This code is primarily used for shrink-wrapping, where a 113 // nonvolatile register is saved to the stack in a position that was 114 // previously allocated. The operation info is the number of the register. 115 // The scaled-by-8 stack offset is recorded in the next unwind operation 116 // code slot, as described in the note above. 117 UwOpSaveNonVol = UnwindOpType(4) 118 119 // Save a nonvolatile integer register on the stack with a long offset, 120 // using a MOV instead of a PUSH. This code is primarily used for 121 // shrink-wrapping, where a nonvolatile register is saved to the stack in a 122 // position that was previously allocated. The operation info is the number 123 // of the register. The unscaled stack offset is recorded in the next two 124 // unwind operation code slots, as described in the note above. 125 UwOpSaveNonVolFar = UnwindOpType(5) 126 127 // For version 1 of the UNWIND_INFO structure, this code was called 128 // UWOP_SAVE_XMM and occupied 2 records, it retained the lower 64 bits of 129 // the XMM register, but was later removed and is now skipped. In practice, 130 // this code has never been used. 131 // For version 2 of the UNWIND_INFO structure, this code is called 132 // UWOP_EPILOG, takes 2 entries, and describes the function epilogue. 133 UwOpEpilog = UnwindOpType(6) 134 135 // For version 1 of the UNWIND_INFO structure, this code was called 136 // UWOP_SAVE_XMM_FAR and occupied 3 records, it saved the lower 64 bits of 137 // the XMM register, but was later removed and is now skipped. In practice, 138 // this code has never been used. 139 // For version 2 of the UNWIND_INFO structure, this code is called 140 // UWOP_SPARE_CODE, takes 3 entries, and makes no sense. 141 UwOpSpareCode = UnwindOpType(7) 142 143 // Save all 128 bits of a nonvolatile XMM register on the stack. The 144 // operation info is the number of the register. The scaled-by-16 stack 145 // offset is recorded in the next slot. 146 UwOpSaveXmm128 = UnwindOpType(8) 147 148 // Save all 128 bits of a nonvolatile XMM register on the stack with a long 149 // offset. The operation info is the number of the register. The unscaled 150 // stack offset is recorded in the next two slots. 151 UwOpSaveXmm128Far = UnwindOpType(9) 152 153 // Push a machine frame. This unwind code is used to record the effect of a 154 // hardware interrupt or exception. 155 UwOpPushMachFrame = UnwindOpType(10) 156 157 // UWOP_SET_FPREG_LARGE is a CLR Unix-only extension to the Windows AMD64 158 // unwind codes. It is not part of the standard Windows AMD64 unwind codes 159 // specification. UWOP_SET_FPREG allows for a maximum of a 240 byte offset 160 // between RSP and the frame pointer, when the frame pointer is 161 // established. UWOP_SET_FPREG_LARGE has a 32-bit range scaled by 16. When 162 // UWOP_SET_FPREG_LARGE is used, UNWIND_INFO.FrameRegister must be set to 163 // the frame pointer register, and UNWIND_INFO.FrameOffset must be set to 164 // 15 (its maximum value). UWOP_SET_FPREG_LARGE is followed by two 165 // UNWIND_CODEs that are combined to form a 32-bit offset (the same as 166 // UWOP_SAVE_NONVOL_FAR). This offset is then scaled by 16. The result must 167 // be less than 2^32 (that is, the top 4 bits of the unscaled 32-bit number 168 // must be zero). This result is used as the frame pointer register offset 169 // from RSP at the time the frame pointer is established. Either 170 // UWOP_SET_FPREG or UWOP_SET_FPREG_LARGE can be used, but not both. 171 UwOpSetFpRegLarge = UnwindOpType(11) 172 ) 173 174 // ImageRuntimeFunctionEntry represents an entry in the function table on 64-bit 175 // Windows (IMAGE_RUNTIME_FUNCTION_ENTRY). Table-based exception handling request 176 // a table entry for all functions that allocate stack space or call another 177 // function (for example, non-leaf functions). 178 type ImageRuntimeFunctionEntry struct { 179 // The address of the start of the function. 180 BeginAddress uint32 `json:"begin_address"` 181 182 // The address of the end of the function. 183 EndAddress uint32 `json:"end_address"` 184 185 // The unwind data info structure is used to record the effects a function 186 // has on the stack pointer, and where the nonvolatile registers are saved 187 // on the stack. 188 UnwindInfoAddress uint32 `json:"unwind_info_address"` 189 } 190 191 // ImageARMRuntimeFunctionEntry represents the function table entry for the ARM 192 // platform. 193 type ImageARMRuntimeFunctionEntry struct { 194 // Function Start RVA is the 32-bit RVA of the start of the function. If 195 // the function contains thumb code, the low bit of this address must be set. 196 BeginAddress uint32 `bitfield:",functionstart" json:"begin_address"` 197 198 // Flag is a 2-bit field that indicates how to interpret the remaining 199 // 30 bits of the second .pdata word. If Flag is 0, then the remaining bits 200 // form an Exception Information RVA (with the low two bits implicitly 0). 201 // If Flag is non-zero, then the remaining bits form a Packed Unwind Data 202 // structure. 203 Flag uint8 `json:"flag"` 204 205 /* Exception Information RVA or Packed Unwind Data. 206 207 Exception Information RVA is the address of the variable-length exception 208 information structure, stored in the .xdata section. 209 This data must be 4-byte aligned. 210 211 Packed Unwind Data is a compressed description of the operations required 212 to unwind from a function, assuming a canonical form. In this case, no 213 .xdata record is required. */ 214 ExceptionFlag uint32 `json:"exception_flag"` 215 } 216 217 // UnwindCode is used to record the sequence of operations in the prolog that 218 // affect the nonvolatile registers and RSP. Each code item has this format: 219 /* typedef union _UNWIND_CODE { 220 struct { 221 UCHAR CodeOffset; 222 UCHAR UnwindOp : 4; 223 UCHAR OpInfo : 4; 224 } DUMMYUNIONNAME; 225 226 struct { 227 UCHAR OffsetLow; 228 UCHAR UnwindOp : 4; 229 UCHAR OffsetHigh : 4; 230 } EpilogueCode; 231 232 USHORT FrameOffset; 233 } UNWIND_CODE, *PUNWIND_CODE;*/ 234 // 235 // It provides information about the amount of stack space allocated, the location 236 // of saved non-volatile registers, and whether or not a frame register is used 237 // and what relation it has to the rest of the stack. 238 type UnwindCode struct { 239 // Offset (from the beginning of the prolog) of the end of the instruction 240 // that performs is operation, plus 1 (that is, the offset of the start of 241 // the next instruction). 242 CodeOffset uint8 `json:"code_offset"` 243 244 // The unwind operation code. 245 UnwindOp UnwindOpType `json:"unwind_op"` 246 247 // Operation info. 248 OpInfo uint8 `json:"op_info"` 249 250 // Allocation size. 251 Operand string `json:"operand"` 252 FrameOffset uint16 `json:"frame_offset"` 253 } 254 255 // UnwindInfo represents the _UNWIND_INFO structure. It is used to record the 256 // effects a function has on the stack pointer, and where the nonvolatile 257 // registers are saved on the stack. 258 type UnwindInfo struct { 259 // (3 bits) Version number of the unwind data, currently 1 and 2. 260 Version uint8 `json:"version"` 261 262 // (5 bits) Three flags are currently defined above. 263 Flags uint8 `json:"flags"` 264 265 // Length of the function prolog in bytes. 266 SizeOfProlog uint8 `json:"size_of_prolog"` 267 268 // The number of slots in the unwind codes array. Some unwind codes, 269 // for example, UWOP_SAVE_NONVOL, require more than one slot in the array. 270 CountOfCodes uint8 `json:"count_of_codes"` 271 272 // If nonzero, then the function uses a frame pointer (FP), and this field 273 // is the number of the nonvolatile register used as the frame pointer, 274 // using the same encoding for the operation info field of UNWIND_CODE nodes. 275 FrameRegister uint8 `json:"frame_register"` 276 277 // If the frame register field is nonzero, this field is the scaled offset 278 // from RSP that is applied to the FP register when it's established. The 279 // actual FP register is set to RSP + 16 * this number, allowing offsets 280 // from 0 to 240. This offset permits pointing the FP register into the 281 // middle of the local stack allocation for dynamic stack frames, allowing 282 // better code density through shorter instructions. (That is, more 283 // instructions can use the 8-bit signed offset form.) 284 FrameOffset uint8 `json:"frame_offset"` 285 286 // An array of items that explains the effect of the prolog on the 287 // nonvolatile registers and RSP. See the section on UNWIND_CODE for the 288 // meanings of individual items. For alignment purposes, this array always 289 // has an even number of entries, and the final entry is potentially 290 // unused. In that case, the array is one longer than indicated by the 291 // count of unwind codes field. 292 UnwindCodes []UnwindCode `json:"unwind_codes"` 293 294 // Address of exception handler when UNW_FLAG_EHANDLER is set. 295 ExceptionHandler uint32 `json:"exception_handler"` 296 297 // If flag UNW_FLAG_CHAININFO is set, then the UNWIND_INFO structure ends 298 // with three UWORDs. These UWORDs represent the RUNTIME_FUNCTION 299 // information for the function of the chained unwind. 300 FunctionEntry ImageRuntimeFunctionEntry `json:"function_entry"` 301 } 302 303 // 304 // The unwind codes are followed by an optional DWORD aligned field that 305 // contains the exception handler address or the address of chained unwind 306 // information. If an exception handler address is specified, then it is 307 // followed by the language specified exception handler data. 308 // 309 // union { 310 // ULONG ExceptionHandler; 311 // ULONG FunctionEntry; 312 // }; 313 // 314 // ULONG ExceptionData[]; 315 // 316 317 type ScopeRecord struct { 318 // This value indicates the offset of the first instruction within a __try 319 // block located in the function. 320 BeginAddress uint32 `json:"begin_address"` 321 322 // This value indicates the offset to the instruction after the last 323 // instruction within the __try block (conceptually the __except statement). 324 EndAddress uint32 `json:"end_address"` 325 326 // This value indicates the offset to the function located within the 327 // parentheses of the __except() statement. In the documentation you'll 328 // find this routine called the "exception handler" or "exception filter". 329 HandlerAddress uint32 `json:"handler_address"` 330 331 // This value indicates the offset to the first instruction in the __except 332 // block associated with the __try block. 333 JumpTarget uint32 `json:"jump_target"` 334 } 335 336 // ScopeTable represents a variable length structure containing a count followed 337 // by Count "scope records". While the RUNTIME_FUNCTION describes the entire range 338 // of a function that contains SEH, the SCOPE_TABLE describes each of the individual 339 // __try/__except blocks within the function. 340 type ScopeTable struct { 341 // The count of scope records. 342 Count uint32 `json:"count"` 343 344 // A array of scope record. 345 ScopeRecords []ScopeRecord `json:"scope_records"` 346 } 347 348 // typedef struct _SCOPE_TABLE { 349 // ULONG Count; 350 // struct 351 // { 352 // ULONG BeginAddress; 353 // ULONG EndAddress; 354 // ULONG HandlerAddress; 355 // ULONG JumpTarget; 356 // } ScopeRecord[1]; 357 // } SCOPE_TABLE, *PSCOPE_TABLE; 358 359 // Exception represent an entry in the function table. 360 type Exception struct { 361 RuntimeFunction ImageRuntimeFunctionEntry `json:"runtime_function"` 362 UnwindInfo UnwindInfo `json:"unwind_info"` 363 } 364 365 func (pe *File) parseUnwindCode(offset uint32, version uint8) (UnwindCode, int) { 366 367 unwindCode := UnwindCode{} 368 advanceBy := 0 369 370 // Read the unwind code at offset (2 bytes) 371 uc, err := pe.ReadUint16(offset) 372 if err != nil { 373 return unwindCode, advanceBy 374 } 375 376 unwindCode.CodeOffset = uint8(uc & 0xff) 377 unwindCode.UnwindOp = UnwindOpType(uc & 0xf00 >> 8) 378 unwindCode.OpInfo = uint8(uc & 0xf000 >> 12) 379 380 switch unwindCode.UnwindOp { 381 case UwOpAllocSmall: 382 size := int(unwindCode.OpInfo*8 + 8) 383 unwindCode.Operand = "Size=" + strconv.Itoa(size) 384 advanceBy++ 385 case UwOpAllocLarge: 386 if unwindCode.OpInfo == 0 { 387 size := int(binary.LittleEndian.Uint16(pe.data[offset+2:]) * 8) 388 unwindCode.Operand = "Size=" + strconv.Itoa(size) 389 advanceBy += 2 390 } else { 391 size := int(binary.LittleEndian.Uint32(pe.data[offset+2:]) << 16) 392 unwindCode.Operand = "Size=" + strconv.Itoa(size) 393 advanceBy += 3 394 } 395 case UwOpSetFpReg: 396 unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] 397 advanceBy++ 398 case UwOpPushNonVol: 399 unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] 400 advanceBy++ 401 case UwOpSaveNonVol: 402 fo := binary.LittleEndian.Uint16(pe.data[offset+2:]) 403 unwindCode.FrameOffset = fo * 8 404 unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] + 405 ", Offset=" + strconv.Itoa(int(unwindCode.FrameOffset)) 406 advanceBy += 2 407 case UwOpSaveNonVolFar: 408 fo := binary.LittleEndian.Uint32(pe.data[offset+2:]) 409 unwindCode.FrameOffset = uint16(fo * 8) 410 unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] + 411 ", Offset=" + strconv.Itoa(int(unwindCode.FrameOffset)) 412 advanceBy += 3 413 case UwOpSaveXmm128: 414 fo := binary.LittleEndian.Uint16(pe.data[offset+2:]) 415 unwindCode.FrameOffset = fo * 16 416 unwindCode.Operand = "Register=XMM" + strconv.Itoa(int(unwindCode.OpInfo)) + 417 ", Offset=" + strconv.Itoa(int(unwindCode.FrameOffset)) 418 advanceBy += 2 419 case UwOpSaveXmm128Far: 420 fo := binary.LittleEndian.Uint32(pe.data[offset+2:]) 421 unwindCode.FrameOffset = uint16(fo) 422 unwindCode.Operand = "Register=XMM" + strconv.Itoa(int(unwindCode.OpInfo)) + 423 ", Offset=" + strconv.Itoa(int(unwindCode.FrameOffset)) 424 advanceBy += 3 425 case UwOpSetFpRegLarge: 426 unwindCode.Operand = "Register=" + OpInfoRegisters[unwindCode.OpInfo] 427 advanceBy += 2 428 case UwOpPushMachFrame: 429 advanceBy++ 430 case UwOpEpilog: 431 if version == 2 { 432 unwindCode.Operand = "Flags=" + strconv.Itoa(int(unwindCode.OpInfo)) + ", Size=" + strconv.Itoa(int(unwindCode.CodeOffset)) 433 } 434 advanceBy += 2 435 case UwOpSpareCode: 436 advanceBy += 3 437 default: 438 advanceBy++ // so we can get out of the loop 439 pe.logger.Warnf("Wrong unwind opcode %d", unwindCode.UnwindOp) 440 } 441 442 return unwindCode, advanceBy 443 } 444 445 func (pe *File) parseUnwindInfo(unwindInfo uint32) UnwindInfo { 446 447 ui := UnwindInfo{} 448 449 offset := pe.GetOffsetFromRva(unwindInfo) 450 v, err := pe.ReadUint32(offset) 451 if err != nil { 452 return ui 453 } 454 455 // The lowest 3 bits 456 ui.Version = uint8(v & 0x7) 457 458 // The next 5 bits. 459 ui.Flags = uint8(v & 0xf8 >> 3) 460 461 // The next byte 462 ui.SizeOfProlog = uint8(v & 0xff00 >> 8) 463 464 // The next byte 465 ui.CountOfCodes = uint8(v & 0xff0000 >> 16) 466 467 // The next 4 bits 468 ui.FrameRegister = uint8(v & 0xf00000 >> 24) 469 470 // The next 4 bits. 471 ui.FrameOffset = uint8(v&0xf0000000>>28) * 6 472 473 // Each unwind code struct is 2 bytes wide. 474 offset += 4 475 i := 0 476 for i < int(ui.CountOfCodes) { 477 ucOffset := offset + 2*uint32(i) 478 unwindCode, advanceBy := pe.parseUnwindCode(ucOffset, ui.Version) 479 if advanceBy == 0 { 480 return ui 481 } 482 ui.UnwindCodes = append(ui.UnwindCodes, unwindCode) 483 i += advanceBy 484 } 485 486 if ui.CountOfCodes&1 == 1 { 487 offset += 2 488 } 489 490 // An image-relative pointer to either the function's language-specific 491 // exception or termination handler, if flag UNW_FLAG_CHAININFO is clear 492 // and one of the flags UNW_FLAG_EHADLER or UNW_FLAG_UHANDLER is set. 493 if ui.Flags&UnwFlagEHandler != 0 || ui.Flags&UnwFlagUHandler != 0 { 494 if ui.Flags&UnwFlagChainInfo == 0 { 495 handlerOffset := offset + 2*uint32(i) 496 ui.ExceptionHandler = binary.LittleEndian.Uint32(pe.data[handlerOffset:]) 497 } 498 } 499 500 // If the UNW_FLAG_CHAININFO flag is set, then an unwind info structure 501 // is a secondary one, and the shared exception-handler/chained-info 502 // address field contains the primary unwind information. This sample 503 // code retrieves the primary unwind information, assuming that unwindInfo 504 // is the structure that has the UNW_FLAG_CHAININFO flag set. 505 if ui.Flags&UnwFlagChainInfo != 0 { 506 chainOffset := offset + 2*uint32(i) 507 rf := ImageRuntimeFunctionEntry{} 508 size := uint32(binary.Size(ImageRuntimeFunctionEntry{})) 509 err := pe.structUnpack(&rf, chainOffset, size) 510 if err != nil { 511 return ui 512 } 513 ui.FunctionEntry = rf 514 } 515 516 return ui 517 } 518 519 // Exception directory contains an array of function table entries that are used 520 // for exception handling. 521 func (pe *File) parseExceptionDirectory(rva, size uint32) error { 522 523 // The target platform determines which format of the function table entry 524 // to use. 525 var exceptions []Exception 526 fileOffset := pe.GetOffsetFromRva(rva) 527 528 entrySize := uint32(binary.Size(ImageRuntimeFunctionEntry{})) 529 entriesCount := size / entrySize 530 531 for i := uint32(0); i < entriesCount; i++ { 532 functionEntry := ImageRuntimeFunctionEntry{} 533 offset := fileOffset + (entrySize * i) 534 err := pe.structUnpack(&functionEntry, offset, entrySize) 535 if err != nil { 536 return err 537 } 538 539 exception := Exception{RuntimeFunction: functionEntry} 540 541 if pe.Is64 { 542 exception.UnwindInfo = pe.parseUnwindInfo(functionEntry.UnwindInfoAddress) 543 } 544 545 exceptions = append(exceptions, exception) 546 } 547 548 pe.Exceptions = exceptions 549 if len(exceptions) > 0 { 550 pe.HasException = true 551 } 552 return nil 553 } 554 555 // PrettyUnwindInfoHandlerFlags returns the string representation of the 556 // `flags` field of the unwind info structure. 557 func PrettyUnwindInfoHandlerFlags(flags uint8) []string { 558 var values []string 559 560 unwFlagHandlerMap := map[uint8]string{ 561 UnwFlagNHandler: "No Handler", 562 UnwFlagEHandler: "Exception", 563 UnwFlagUHandler: "Termination", 564 UnwFlagChainInfo: "Chain", 565 } 566 567 for k, s := range unwFlagHandlerMap { 568 if k&flags != 0 { 569 values = append(values, s) 570 } 571 } 572 return values 573 } 574 575 // String returns the string representation of the an unwind opcode. 576 func (uo UnwindOpType) String() string { 577 578 unOpToString := map[UnwindOpType]string{ 579 UwOpPushNonVol: "UWOP_PUSH_NONVOL", 580 UwOpAllocLarge: "UWOP_ALLOC_LARE", 581 UwOpAllocSmall: "UWOP_ALLOC_SMALL", 582 UwOpSetFpReg: "UWOP_SET_FPREG", 583 UwOpSaveNonVol: "UWOP_SAVE_NONVOL", 584 UwOpSaveNonVolFar: "UWOP_SAVE_NONVOL_FAR", 585 UwOpEpilog: "UWOP_EPILOG", 586 UwOpSpareCode: "UWOP_SPARE_CODE", 587 UwOpSaveXmm128: "UWOP_SAVE_XMM128", 588 UwOpSaveXmm128Far: "UWOP_SAVE_XMM128_FAR", 589 UwOpPushMachFrame: "UWOP_PUSH_MACHFRAME", 590 UwOpSetFpRegLarge: "UWOP_SET_FPREG_LARGE", 591 } 592 593 if val, ok := unOpToString[uo]; ok { 594 return val 595 } 596 597 return "?" 598 }