/* Copyright (c) 2018, Magnus Norddahl Copyright 2021 flyinghead This file is part of Flycast. Flycast is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. Flycast is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ // Based on Asmjit unwind info registration and stack walking code for Windows, Linux and macOS // https://gist.github.com/dpjudas/925d5c4ffef90bd8114be3b465069fff #include "build.h" #include "oslib/oslib.h" extern "C" { void __register_frame(const void*); void __deregister_frame(const void*); } #if HOST_CPU == CPU_X64 constexpr int dwarfRegId[16] = { 0, // RAX 2, // RCX 1, // RDX 3, // RBX 7, // RSP 6, // RBP 4, // RSI 5, // RDI 8, // R8 9, // R9 10, // R10 11, // R11 12, // R12 13, // R13 14, // R14 15, // R15 }; inline static int registerId(int x64Id) { return dwarfRegId[x64Id]; } constexpr int dwarfRegRAId = 16; constexpr int dwarfRegXmmId = 17; constexpr int dwarfRegSP = dwarfRegId[4]; // RSP #elif HOST_CPU == CPU_X86 inline static int registerId(int x86Id) { return x86Id; } constexpr int dwarfRegRAId = 8; constexpr int dwarfRegSP = 4; // ESP #elif HOST_CPU == CPU_ARM64 // https://developer.arm.com/documentation/ihi0057/latest // // register codes: // x0-x30 0 - 30 // SP 31 // d0-d31 64 - 95 inline static int registerId(int armId) { return armId; } constexpr int dwarfRegRAId = 30; constexpr int dwarfRegSP = 31; #endif #if HOST_CPU == CPU_X64 || HOST_CPU == CPU_ARM64 || HOST_CPU == CPU_X86 using ByteStream = std::vector; static void writeLength(ByteStream &stream, u32 pos, u32 v) { *(u32*)(&stream[pos]) = v; } template void write(ByteStream &stream, T v) { for (size_t i = 0; i < sizeof(T); i++) stream.push_back(v >> (i * 8)); } static void writeULEB128(ByteStream &stream, u32 v) { while (true) { if (v < 128) { write(stream, v); break; } else { write(stream, (v & 0x7f) | 0x80); v >>= 7; } } } static void writeSLEB128(ByteStream &stream, int32_t v) { bool more; do { u8 byte = v & 0x7f; v >>= 7; more = !(((v == 0 && (byte & 0x40) == 0) || (v == -1 && (byte & 0x40) != 0))); if (more) byte |= 0x80; // Mark this byte to show that more bytes will follow. write(stream, byte); } while (more); } static void writePadding(ByteStream &stream) { int padding = stream.size() % sizeof(uintptr_t); if (padding != 0) { padding = sizeof(uintptr_t) - padding; for (int i = 0; i < padding; i++) write(stream, 0); } } static void writeCIE(ByteStream &stream, const ByteStream &cieInstructions, u8 returnAddressReg) { u32 lengthPos = stream.size(); write(stream, 0); // Length write(stream, 0); // CIE ID write(stream, 1); // CIE Version write(stream, 'z'); write(stream, 'R'); // fde encoding write(stream, 0); writeULEB128(stream, 1); // code alignment. Loc multiplier writeSLEB128(stream, -1); // data alignment. Stack offset multiplier. writeULEB128(stream, returnAddressReg); writeULEB128(stream, 1); // LEB128 augmentation size write(stream, 0); // DW_EH_PE_absptr (FDE uses absolute pointers) stream.insert(stream.end(), cieInstructions.begin(), cieInstructions.end()); writePadding(stream); writeLength(stream, lengthPos, stream.size() - lengthPos - 4); } static void writeFDE(ByteStream &stream, const ByteStream &fdeInstructions, u32 cieLocation, u32 &functionStart) { u32 lengthPos = stream.size(); write(stream, 0); // Length u32 offsetToCIE = stream.size() - cieLocation; write(stream, offsetToCIE); functionStart = stream.size(); write(stream, 0); // func start write(stream, 0); // func size writeULEB128(stream, 0); // LEB128 augmentation size stream.insert(stream.end(), fdeInstructions.begin(), fdeInstructions.end()); writePadding(stream); writeLength(stream, lengthPos, stream.size() - lengthPos - 4); } static void writeAdvanceLoc(ByteStream &fdeInstructions, uintptr_t offset, uintptr_t &lastOffset) { uintptr_t delta = offset - lastOffset; if (delta == 0) return; if (delta < (1 << 6)) { write(fdeInstructions, (1 << 6) | delta); // DW_CFA_advance_loc } else if (delta < (1 << 8)) { write(fdeInstructions, 2); // DW_CFA_advance_loc1 write(fdeInstructions, delta); } else if (delta < (1 << 16)) { write(fdeInstructions, 3); // DW_CFA_advance_loc2 write(fdeInstructions, delta); } else { write(fdeInstructions, 4); // DW_CFA_advance_loc3 write(fdeInstructions, delta); } lastOffset = offset; } static void writeDefineCFA(ByteStream &cieInstructions, int dwarfRegId, int stackOffset) { write(cieInstructions, 0x0c); // DW_CFA_def_cfa writeULEB128(cieInstructions, dwarfRegId); writeULEB128(cieInstructions, stackOffset); } static void writeDefineStackOffset(ByteStream &fdeInstructions, int stackOffset) { write(fdeInstructions, 0x0e); // DW_CFA_def_cfa_offset writeULEB128(fdeInstructions, stackOffset); } static void writeRegisterStackLocation(ByteStream &instructions, int dwarfRegId, int stackLocation) { write(instructions, (2 << 6) | dwarfRegId); // DW_CFA_offset writeULEB128(instructions, stackLocation); } static void writeRegisterStackLocationExtended(ByteStream &instructions, int dwarfRegId, int stackLocation) { write(instructions, 5); // DW_CFA_offset_extended writeULEB128(instructions, dwarfRegId); writeULEB128(instructions, stackLocation); } void UnwindInfo::start(void *address) { startAddr = (u8 *)address; #if HOST_CPU == CPU_X64 || HOST_CPU == CPU_X86 stackOffset = sizeof(uintptr_t); #else stackOffset = 0; #endif lastOffset = 0; cieInstructions.clear(); fdeInstructions.clear(); writeDefineCFA(cieInstructions, dwarfRegSP, stackOffset); if (stackOffset > 0) // Return address pushed on stack writeRegisterStackLocation(cieInstructions, dwarfRegRAId, stackOffset); } void UnwindInfo::pushReg(u32 offset, int reg) { stackOffset += sizeof(uintptr_t); writeAdvanceLoc(fdeInstructions, offset, lastOffset); writeDefineStackOffset(fdeInstructions, stackOffset); writeRegisterStackLocation(fdeInstructions, registerId(reg), stackOffset); } void UnwindInfo::saveReg(u32 offset, int reg, int stackOffset) { writeAdvanceLoc(fdeInstructions, offset, lastOffset); writeRegisterStackLocation(fdeInstructions, registerId(reg), stackOffset); } void UnwindInfo::saveExtReg(u32 offset, int reg, int stackOffset) { writeAdvanceLoc(fdeInstructions, offset, lastOffset); writeRegisterStackLocationExtended(fdeInstructions, registerId(reg), stackOffset); } void UnwindInfo::allocStack(u32 offset, int size) { stackOffset += size; writeAdvanceLoc(fdeInstructions, offset, lastOffset); writeDefineStackOffset(fdeInstructions, stackOffset); } void UnwindInfo::endProlog(u32 offset) { } size_t UnwindInfo::end(u32 offset, ptrdiff_t rwRxOffset) { ByteStream unwindInfo; writeCIE(unwindInfo, cieInstructions, dwarfRegRAId); u32 functionStart; writeFDE(unwindInfo, fdeInstructions, 0, functionStart); write(unwindInfo, 0); u8 *endAddr = startAddr + offset; // 16 bytes alignment if ((uintptr_t)endAddr & 0xf) offset += 16 - ((uintptr_t)endAddr & 0xf); u8 *unwindInfoDest = startAddr + offset; memcpy(unwindInfoDest, &unwindInfo[0], unwindInfo.size()); if (!unwindInfo.empty()) { uintptr_t *unwindfuncaddr = (uintptr_t *)(unwindInfoDest + functionStart); unwindfuncaddr[0] = (uintptr_t)startAddr + rwRxOffset; unwindfuncaddr[1] = (ptrdiff_t)(endAddr - startAddr); #ifdef __APPLE__ // On macOS __register_frame takes a single FDE as an argument u8 *entry = unwindInfoDest; while (true) { u32 length = *((u32 *)entry); if (length == 0) break; if (length == 0xffffffff) { u64 length64 = *((u64 *)(entry + 4)); if (length64 == 0) break; u64 offset = *((u64 *)(entry + 12)); if (offset != 0) { __register_frame(entry); registeredFrames.push_back(entry); } entry += length64 + 12; } else { u32 offset = *((u32 *)(entry + 4)); if (offset != 0) { __register_frame(entry); registeredFrames.push_back(entry); } entry += length + 4; } } #else // On Linux it takes a pointer to the entire .eh_frame __register_frame(unwindInfoDest); registeredFrames.push_back(unwindInfoDest); #endif } DEBUG_LOG(DYNAREC, "RegisterFrame %p sz %d tables: %d", startAddr, (u32)(endAddr - startAddr), (u32)registeredFrames.size()); return (unwindInfoDest + unwindInfo.size()) - endAddr; } void UnwindInfo::clear() { DEBUG_LOG(DYNAREC, "UnwindInfo::clear"); for (u8 *frame : registeredFrames) __deregister_frame(frame); registeredFrames.clear(); } #endif