mirror of
https://github.com/FEX-Emu/FEX.git
synced 2025-02-13 11:13:38 +00:00
Merge pull request #1787 from FEX-Emu/skmp/gdb-jit-integration
gdb: jit integration
This commit is contained in:
commit
4449b60459
@ -1,6 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(FEX)
|
||||
|
||||
INCLUDE (CheckIncludeFiles)
|
||||
CHECK_INCLUDE_FILES ("gdb/jit-reader.h" HAVE_GDB_JIT_READER_H)
|
||||
|
||||
option(BUILD_TESTS "Build unit tests to ensure sanity" TRUE)
|
||||
option(BUILD_FEX_LINUX_TESTS "Build FEXLinuxTests, requires g++/g++-multilib or g++-x86-64-linux-gnu/g++-multilib-x86-64-linux-gnu" FALSE)
|
||||
option(BUILD_THUNKS "Build thunks" FALSE)
|
||||
@ -13,6 +16,7 @@ option(ENABLE_MOLD "Enable linking with mold" FALSE)
|
||||
option(ENABLE_ASAN "Enables Clang ASAN" FALSE)
|
||||
option(ENABLE_TSAN "Enables Clang TSAN" FALSE)
|
||||
option(ENABLE_ASSERTIONS "Enables assertions in build" FALSE)
|
||||
option(ENABLE_GDB_SYMBOLS "Enables GDBSymbols integration support" ${HAVE_GDB_JIT_READER_H})
|
||||
option(ENABLE_VISUAL_DEBUGGER "Enables the visual debugger for compiling" FALSE)
|
||||
option(ENABLE_STRICT_WERROR "Enables stricter -Werror for CI" FALSE)
|
||||
option(ENABLE_WERROR "Enables -Werror" FALSE)
|
||||
@ -44,6 +48,12 @@ if (ENABLE_ASSERTIONS)
|
||||
add_definitions(-DASSERTIONS_ENABLED=1)
|
||||
endif()
|
||||
|
||||
if (ENABLE_GDB_SYMBOLS)
|
||||
message(STATUS "GDBSymbols support enabled")
|
||||
add_definitions(-DGDB_SYMBOLS_ENABLED=1)
|
||||
endif()
|
||||
|
||||
|
||||
if (ENABLE_INTERPRETER)
|
||||
message(STATUS "Interpreter enabled")
|
||||
add_definitions(-DINTERPRETER_ENABLED=1)
|
||||
|
1
External/FEXCore/Source/CMakeLists.txt
vendored
1
External/FEXCore/Source/CMakeLists.txt
vendored
@ -118,6 +118,7 @@ set (SRCS
|
||||
Interface/Core/X86Tables/X87Tables.cpp
|
||||
Interface/Core/X86Tables/XOPTables.cpp
|
||||
Interface/HLE/Thunks/Thunks.cpp
|
||||
Interface/GDBJIT/GDBJIT.cpp
|
||||
Interface/IR/AOTIR.cpp
|
||||
Interface/IR/IRDumper.cpp
|
||||
Interface/IR/IRParser.cpp
|
||||
|
@ -208,6 +208,16 @@
|
||||
"Useful for determining hot blocks of code",
|
||||
"Has some file writing overhead per JIT block"
|
||||
]
|
||||
},
|
||||
"GDBSymbols": {
|
||||
"Type": "bool",
|
||||
"Default": "false",
|
||||
"Desc": [
|
||||
"Integrates with GDB using the JIT interface.",
|
||||
"Needs the fex jit loader in GDB, which can be loaded via `jit-reader-load libFEXGDBReader.so.`",
|
||||
"Also needs x86_64-linux-gnu-objdump in PATH.",
|
||||
"Can be very slow."
|
||||
]
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
|
@ -156,6 +156,7 @@ namespace FEXCore::Context {
|
||||
|
||||
void SetSyscallHandler(FEXCore::Context::Context *CTX, FEXCore::HLE::SyscallHandler *Handler) {
|
||||
CTX->SyscallHandler = Handler;
|
||||
CTX->SourcecodeResolver = Handler->GetSourcecodeResolver();
|
||||
}
|
||||
|
||||
FEXCore::CPUID::FunctionResults RunCPUIDFunction(FEXCore::Context::Context *CTX, uint32_t Function, uint32_t Leaf) {
|
||||
|
@ -49,6 +49,8 @@ namespace CPU {
|
||||
namespace HLE {
|
||||
struct SyscallArguments;
|
||||
class SyscallHandler;
|
||||
class SourcecodeResolver;
|
||||
struct SourcecodeMap;
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,6 +109,7 @@ namespace FEXCore::Context {
|
||||
FEX_CONFIG_OPT(GlobalJITNaming, GLOBALJITNAMING);
|
||||
FEX_CONFIG_OPT(LibraryJITNaming, LIBRARYJITNAMING);
|
||||
FEX_CONFIG_OPT(BlockJITNaming, BLOCKJITNAMING);
|
||||
FEX_CONFIG_OPT(GDBSymbols, GDBSYMBOLS);
|
||||
FEX_CONFIG_OPT(ParanoidTSO, PARANOIDTSO);
|
||||
FEX_CONFIG_OPT(CacheObjectCodeCompilation, CACHEOBJECTCODECOMPILATION);
|
||||
FEX_CONFIG_OPT(x87ReducedPrecision, X87REDUCEDPRECISION);
|
||||
@ -130,6 +133,7 @@ namespace FEXCore::Context {
|
||||
|
||||
FEXCore::CPUIDEmu CPUID;
|
||||
FEXCore::HLE::SyscallHandler *SyscallHandler{};
|
||||
FEXCore::HLE::SourcecodeResolver *SourcecodeResolver{};
|
||||
std::unique_ptr<FEXCore::ThunkHandler> ThunkHandler;
|
||||
std::unique_ptr<FEXCore::CPU::Dispatcher> Dispatcher;
|
||||
|
||||
@ -195,7 +199,7 @@ namespace FEXCore::Context {
|
||||
uint64_t StartAddr;
|
||||
uint64_t Length;
|
||||
};
|
||||
[[nodiscard]] GenerateIRResult GenerateIR(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP);
|
||||
[[nodiscard]] GenerateIRResult GenerateIR(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP, bool ExtendedDebugInfo);
|
||||
|
||||
struct CompileCodeResult {
|
||||
void* CompiledCode;
|
||||
|
46
External/FEXCore/Source/Interface/Core/Core.cpp
vendored
46
External/FEXCore/Source/Interface/Core/Core.cpp
vendored
@ -7,6 +7,7 @@ desc: Glues Frontend, OpDispatcher and IR Opts & Compilation, LookupCache, Dispa
|
||||
$end_info$
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include "Interface/Context/Context.h"
|
||||
#include "Interface/Core/LookupCache.h"
|
||||
#include "Interface/Core/Core.h"
|
||||
@ -33,6 +34,7 @@ $end_info$
|
||||
#include <FEXCore/Debug/InternalThreadState.h>
|
||||
#include <FEXCore/Debug/X86Tables.h>
|
||||
#include <FEXCore/HLE/SyscallHandler.h>
|
||||
#include <FEXCore/HLE/SourcecodeResolver.h>
|
||||
#include <FEXCore/HLE/Linux/ThreadManagement.h>
|
||||
#include <FEXCore/IR/IR.h>
|
||||
#include <FEXCore/IR/IREmitter.h>
|
||||
@ -50,7 +52,6 @@ $end_info$
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <fstream>
|
||||
@ -75,6 +76,7 @@ $end_info$
|
||||
#include <vector>
|
||||
#include <xxhash.h>
|
||||
|
||||
|
||||
namespace FEXCore::CPU {
|
||||
bool CreateCPUCore(FEXCore::Context::Context *CTX) {
|
||||
// This should be used for generating things that are shared between threads
|
||||
@ -715,7 +717,7 @@ namespace FEXCore::Context {
|
||||
}
|
||||
}
|
||||
|
||||
Context::GenerateIRResult Context::GenerateIR(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP) {
|
||||
Context::GenerateIRResult Context::GenerateIR(FEXCore::Core::InternalThreadState *Thread, uint64_t GuestRIP, bool ExtendedDebugInfo) {
|
||||
Thread->OpDispatcher->ReownOrClaimBuffer();
|
||||
Thread->OpDispatcher->ResetWorkingList();
|
||||
|
||||
@ -770,6 +772,10 @@ namespace FEXCore::Context {
|
||||
DecodedInfo = &Block.DecodedInstructions[i];
|
||||
bool IsLocked = DecodedInfo->Flags & FEXCore::X86Tables::DecodeFlags::FLAG_LOCK;
|
||||
|
||||
if (ExtendedDebugInfo) {
|
||||
Thread->OpDispatcher->_GuestOpcode(Block.Entry + BlockInstructionsLength - GuestRIP);
|
||||
}
|
||||
|
||||
if (Config.SMCChecks == FEXCore::Config::CONFIG_SMC_FULL) {
|
||||
auto ExistingCodePtr = reinterpret_cast<uint64_t*>(Block.Entry + BlockInstructionsLength);
|
||||
|
||||
@ -896,18 +902,26 @@ namespace FEXCore::Context {
|
||||
auto CompiledCode = Thread->CPUBackend->RelocateJITObjectCode(GuestRIP, CodeCacheEntry);
|
||||
if (CompiledCode) {
|
||||
return {
|
||||
.CompiledCode = CompiledCode,
|
||||
.IRData = nullptr, // No IR data generated
|
||||
.DebugData = nullptr, // nullptr here ensures that code serialization doesn't occur on from cache read
|
||||
.RAData = nullptr, // No RA data generated
|
||||
.GeneratedIR = false, // nullptr here ensures IR cache mechanisms won't run
|
||||
.StartAddr = 0, // Unused
|
||||
.Length = 0, // Unused
|
||||
.CompiledCode = CompiledCode,
|
||||
.IRData = nullptr, // No IR data generated
|
||||
.DebugData = nullptr, // nullptr here ensures that code serialization doesn't occur on from cache read
|
||||
.RAData = nullptr, // No RA data generated
|
||||
.GeneratedIR = false, // nullptr here ensures IR cache mechanisms won't run
|
||||
.StartAddr = 0, // Unused
|
||||
.Length = 0, // Unused
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SourcecodeResolver && Config.GDBSymbols()) {
|
||||
auto AOTIRCacheEntry = SyscallHandler->LookupAOTIRCacheEntry(GuestRIP);
|
||||
if (AOTIRCacheEntry.Entry && !AOTIRCacheEntry.Entry->ContainsCode) {
|
||||
AOTIRCacheEntry.Entry->SourcecodeMap =
|
||||
SourcecodeResolver->GenerateMap(AOTIRCacheEntry.Entry->Filename, AOTIRCacheEntry.Entry->FileId);
|
||||
}
|
||||
}
|
||||
|
||||
// AOT IR bookkeeping and cache
|
||||
{
|
||||
auto [IRCopy, RACopy, DebugDataCopy, _StartAddr, _Length, _GeneratedIR] = IRCaptureCache.PreGenerateIRFetch(GuestRIP, IRList);
|
||||
@ -924,7 +938,7 @@ namespace FEXCore::Context {
|
||||
|
||||
if (IRList == nullptr) {
|
||||
// Generate IR + Meta Info
|
||||
auto [IRCopy, RACopy, TotalInstructions, TotalInstructionsLength, _StartAddr, _Length] = GenerateIR(Thread, GuestRIP);
|
||||
auto [IRCopy, RACopy, TotalInstructions, TotalInstructionsLength, _StartAddr, _Length] = GenerateIR(Thread, GuestRIP, Config.GDBSymbols());
|
||||
|
||||
// Setup pointers to internal structures
|
||||
IRList = IRCopy;
|
||||
@ -1002,20 +1016,20 @@ namespace FEXCore::Context {
|
||||
auto FragmentBasePtr = reinterpret_cast<uint8_t *>(CodePtr);
|
||||
|
||||
if (DebugData) {
|
||||
auto GuestRIPLookup = this->SyscallHandler->LookupAOTIRCacheEntry(GuestRIP);
|
||||
auto GuestRIPLookup = SyscallHandler->LookupAOTIRCacheEntry(GuestRIP);
|
||||
|
||||
if (DebugData->Subblocks.size()) {
|
||||
for (auto& Subblock: DebugData->Subblocks) {
|
||||
auto BlockBasePtr = FragmentBasePtr + Subblock.HostCodeOffset;
|
||||
if (GuestRIPLookup.Entry) {
|
||||
Symbols.Register(BlockBasePtr, DebugData->HostCodeSize, GuestRIPLookup.Entry->Filename, GuestRIP - GuestRIPLookup.Offset);
|
||||
Symbols.Register(BlockBasePtr, DebugData->HostCodeSize, GuestRIPLookup.Entry->Filename, GuestRIP - GuestRIPLookup.VAFileStart);
|
||||
} else {
|
||||
Symbols.Register(BlockBasePtr, GuestRIP, Subblock.HostCodeSize);
|
||||
}
|
||||
Symbols.Register(BlockBasePtr, GuestRIP, Subblock.HostCodeSize);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (GuestRIPLookup.Entry) {
|
||||
Symbols.Register(FragmentBasePtr, DebugData->HostCodeSize, GuestRIPLookup.Entry->Filename, GuestRIP - GuestRIPLookup.Offset);
|
||||
Symbols.Register(FragmentBasePtr, DebugData->HostCodeSize, GuestRIPLookup.Entry->Filename, GuestRIP - GuestRIPLookup.VAFileStart);
|
||||
} else {
|
||||
Symbols.Register(FragmentBasePtr, GuestRIP, DebugData->HostCodeSize);
|
||||
}
|
||||
@ -1278,4 +1292,4 @@ namespace FEXCore::Context {
|
||||
Thread->FrontendDecoder->SetExternalBranches(ExternalBranches);
|
||||
Thread->FrontendDecoder->SetSectionMaxAddress(SectionMaxAddress);
|
||||
}
|
||||
}
|
||||
}
|
@ -164,6 +164,7 @@ constexpr OpHandlerArray InterpreterOpHandlers = [] {
|
||||
REGISTER_OP(CODEBLOCK, NoOp);
|
||||
REGISTER_OP(BEGINBLOCK, NoOp);
|
||||
REGISTER_OP(ENDBLOCK, NoOp);
|
||||
REGISTER_OP(GUESTOPCODE, NoOp);
|
||||
REGISTER_OP(FENCE, Fence);
|
||||
REGISTER_OP(BREAK, Break);
|
||||
REGISTER_OP(PHI, NoOp);
|
||||
|
@ -672,6 +672,7 @@ void *Arm64JITCore::CompileCode(uint64_t Entry, [[maybe_unused]] FEXCore::IR::IR
|
||||
|
||||
this->Entry = Entry;
|
||||
this->RAData = RAData;
|
||||
this->DebugData = DebugData;
|
||||
|
||||
#ifndef NDEBUG
|
||||
LoadConstant(x0, Entry);
|
||||
|
@ -147,6 +147,7 @@ private:
|
||||
void EmitDetectionString();
|
||||
IR::RegisterAllocationPass *RAPass;
|
||||
IR::RegisterAllocationData *RAData;
|
||||
FEXCore::Core::DebugData *DebugData;
|
||||
|
||||
void ResetStack();
|
||||
/**
|
||||
@ -348,7 +349,7 @@ private:
|
||||
DEF_OP(CacheLineZero);
|
||||
|
||||
///< Misc ops
|
||||
DEF_OP(EndBlock);
|
||||
DEF_OP(GuestOpcode);
|
||||
DEF_OP(Fence);
|
||||
DEF_OP(Break);
|
||||
DEF_OP(Phi);
|
||||
|
@ -6,12 +6,19 @@ $end_info$
|
||||
|
||||
#include <syscall.h>
|
||||
#include "Interface/Core/JIT/Arm64/JITClass.h"
|
||||
#include "FEXCore/Debug/InternalThreadState.h"
|
||||
|
||||
namespace FEXCore::CPU {
|
||||
using namespace vixl;
|
||||
using namespace vixl::aarch64;
|
||||
#define DEF_OP(x) void Arm64JITCore::Op_##x(IR::IROp_Header *IROp, IR::NodeID Node)
|
||||
|
||||
DEF_OP(GuestOpcode) {
|
||||
auto Op = IROp->C<IR::IROp_GuestOpcode>();
|
||||
// metadata
|
||||
DebugData->GuestOpcodes.push_back({Op->GuestEntryOffset, GetCursorAddress<uint8_t*>() - GuestEntry});
|
||||
}
|
||||
|
||||
DEF_OP(Fence) {
|
||||
auto Op = IROp->C<IR::IROp_Fence>();
|
||||
switch (Op->Fence) {
|
||||
@ -232,6 +239,7 @@ void Arm64JITCore::RegisterMiscHandlers() {
|
||||
REGISTER_OP(CODEBLOCK, NoOp);
|
||||
REGISTER_OP(BEGINBLOCK, NoOp);
|
||||
REGISTER_OP(ENDBLOCK, NoOp);
|
||||
REGISTER_OP(GUESTOPCODE, GuestOpcode);
|
||||
REGISTER_OP(FENCE, Fence);
|
||||
REGISTER_OP(BREAK, Break);
|
||||
REGISTER_OP(PHI, NoOp);
|
||||
|
@ -575,6 +575,7 @@ void *X86JITCore::CompileCode(uint64_t Entry, [[maybe_unused]] FEXCore::IR::IRLi
|
||||
|
||||
this->Entry = Entry;
|
||||
this->RAData = RAData;
|
||||
this->DebugData = DebugData;
|
||||
|
||||
// Fairly excessive buffer range to make sure we don't overflow
|
||||
uint32_t BufferRange = SSACount * 16 + GDBEnabled * Dispatcher::MaxGDBPauseCheckSize;
|
||||
@ -582,7 +583,7 @@ void *X86JITCore::CompileCode(uint64_t Entry, [[maybe_unused]] FEXCore::IR::IRLi
|
||||
CTX->ClearCodeCache(ThreadState);
|
||||
}
|
||||
|
||||
auto GuestEntry = getCurr<uint8_t*>();
|
||||
GuestEntry = getCurr<uint8_t*>();
|
||||
CursorEntry = getSize();
|
||||
this->IR = IR;
|
||||
|
||||
@ -646,12 +647,13 @@ void *X86JITCore::CompileCode(uint64_t Entry, [[maybe_unused]] FEXCore::IR::IRLi
|
||||
|
||||
for (auto [BlockNode, BlockHeader] : IR->GetBlocks()) {
|
||||
using namespace FEXCore::IR;
|
||||
{
|
||||
#if defined(ASSERTIONS_ENABLED) && ASSERTIONS_ENABLED
|
||||
auto BlockIROp = BlockHeader->CW<IROp_CodeBlock>();
|
||||
LOGMAN_THROW_A_FMT(BlockIROp->Header.Op == IR::OP_CODEBLOCK, "IR type failed to be a code block");
|
||||
auto BlockIROp = BlockHeader->CW<IROp_CodeBlock>();
|
||||
LOGMAN_THROW_A_FMT(BlockIROp->Header.Op == IR::OP_CODEBLOCK, "IR type failed to be a code block");
|
||||
#endif
|
||||
|
||||
auto BlockStartHostCode = getCurr<uint8_t *>();
|
||||
{
|
||||
const auto Node = IR->GetID(BlockNode);
|
||||
const auto IsTarget = JumpTargets.try_emplace(Node).first;
|
||||
|
||||
@ -706,6 +708,13 @@ void *X86JITCore::CompileCode(uint64_t Entry, [[maybe_unused]] FEXCore::IR::IRLi
|
||||
OpHandler Handler = OpHandlers[IROp->Op];
|
||||
(this->*Handler)(IROp, ID);
|
||||
}
|
||||
|
||||
if (DebugData) {
|
||||
DebugData->Subblocks.push_back({
|
||||
static_cast<uint32_t>(BlockStartHostCode - GuestEntry),
|
||||
static_cast<uint32_t>(getCurr<uint8_t *>() - BlockStartHostCode)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure last branch is generated. It certainly can't be eliminated here.
|
||||
|
@ -189,6 +189,7 @@ private:
|
||||
|
||||
IR::RegisterAllocationPass *RAPass;
|
||||
FEXCore::IR::RegisterAllocationData *RAData;
|
||||
FEXCore::Core::DebugData *DebugData;
|
||||
|
||||
#ifdef BLOCKSTATS
|
||||
bool GetSamplingData {true};
|
||||
@ -200,6 +201,11 @@ private:
|
||||
void EmitDetectionString();
|
||||
|
||||
uint32_t SpillSlots{};
|
||||
/**
|
||||
* @brief Current guest RIP entrypoint
|
||||
*/
|
||||
uint8_t *GuestEntry{};
|
||||
|
||||
using SetCC = void (X86JITCore::*)(const Operand& op);
|
||||
using CMovCC = void (X86JITCore::*)(const Reg& reg, const Operand& op);
|
||||
using JCC = void (X86JITCore::*)(const Label& label, LabelType type);
|
||||
@ -341,7 +347,7 @@ private:
|
||||
DEF_OP(CacheLineZero);
|
||||
|
||||
///< Misc ops
|
||||
DEF_OP(EndBlock);
|
||||
DEF_OP(GuestOpcode);
|
||||
DEF_OP(Fence);
|
||||
DEF_OP(Break);
|
||||
DEF_OP(Phi);
|
||||
|
@ -7,6 +7,7 @@ $end_info$
|
||||
#include "Interface/Context/Context.h"
|
||||
#include "Interface/Core/Dispatcher/Dispatcher.h"
|
||||
#include "Interface/Core/JIT/x86_64/JITClass.h"
|
||||
#include "FEXCore/Debug/InternalThreadState.h"
|
||||
|
||||
#include <FEXCore/Core/CoreState.h>
|
||||
#include <FEXCore/Utils/LogManager.h>
|
||||
@ -20,6 +21,12 @@ $end_info$
|
||||
namespace FEXCore::CPU {
|
||||
#define DEF_OP(x) void X86JITCore::Op_##x(IR::IROp_Header *IROp, IR::NodeID Node)
|
||||
|
||||
DEF_OP(GuestOpcode) {
|
||||
auto Op = IROp->C<IR::IROp_GuestOpcode>();
|
||||
// metadata
|
||||
DebugData->GuestOpcodes.push_back({Op->GuestEntryOffset, getCurr<uint8_t*>() - GuestEntry});
|
||||
}
|
||||
|
||||
DEF_OP(Fence) {
|
||||
auto Op = IROp->C<IR::IROp_Fence>();
|
||||
switch (Op->Fence) {
|
||||
@ -178,6 +185,7 @@ void X86JITCore::RegisterMiscHandlers() {
|
||||
REGISTER_OP(CODEBLOCK, NoOp);
|
||||
REGISTER_OP(BEGINBLOCK, NoOp);
|
||||
REGISTER_OP(ENDBLOCK, NoOp);
|
||||
REGISTER_OP(GUESTOPCODE, GuestOpcode);
|
||||
REGISTER_OP(FENCE, Fence);
|
||||
REGISTER_OP(BREAK, Break);
|
||||
REGISTER_OP(PHI, NoOp);
|
||||
|
127
External/FEXCore/Source/Interface/GDBJIT/GDBJIT.cpp
vendored
Normal file
127
External/FEXCore/Source/Interface/GDBJIT/GDBJIT.cpp
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
#include "GDBJIT.h"
|
||||
|
||||
#include <FEXCore/Debug/InternalThreadState.h>
|
||||
#include <FEXCore/HLE/SourcecodeResolver.h>
|
||||
#include <FEXCore/Utils/LogManager.h>
|
||||
|
||||
#if defined(GDB_SYMBOLS_ENABLED)
|
||||
|
||||
#include <FEXCore/Debug/GDBReaderInterface.h>
|
||||
|
||||
extern "C" {
|
||||
enum jit_actions_t { JIT_NOACTION = 0, JIT_REGISTER_FN, JIT_UNREGISTER_FN };
|
||||
|
||||
struct jit_code_entry {
|
||||
jit_code_entry *next_entry;
|
||||
jit_code_entry *prev_entry;
|
||||
const char *symfile_addr;
|
||||
uint64_t symfile_size;
|
||||
};
|
||||
|
||||
struct jit_descriptor {
|
||||
uint32_t version;
|
||||
/* This type should be jit_actions_t, but we use uint32_t
|
||||
to be explicit about the bitwidth. */
|
||||
uint32_t action_flag;
|
||||
jit_code_entry *relevant_entry;
|
||||
jit_code_entry *first_entry;
|
||||
};
|
||||
|
||||
/* Make sure to specify the version statically, because the
|
||||
debugger may check the version before we can set it. */
|
||||
|
||||
constinit jit_descriptor __jit_debug_descriptor = {.version = 1};
|
||||
|
||||
/* GDB puts a breakpoint in this function. */
|
||||
void __attribute__((noinline)) __jit_debug_register_code() {
|
||||
asm volatile("" ::"r"(&__jit_debug_descriptor));
|
||||
};
|
||||
}
|
||||
|
||||
namespace FEXCore {
|
||||
|
||||
void GDBJITRegister(FEXCore::IR::AOTIRCacheEntry *Entry, uintptr_t VAFileStart,
|
||||
uint64_t GuestRIP, uintptr_t HostEntry,
|
||||
FEXCore::Core::DebugData *DebugData) {
|
||||
auto map = Entry->SourcecodeMap.get();
|
||||
|
||||
if (map) {
|
||||
auto FileOffset = GuestRIP - VAFileStart;
|
||||
|
||||
auto Sym = map->FindSymbolMapping(FileOffset);
|
||||
|
||||
std::string SymName = HLE::SourcecodeSymbolMapping::SymName(
|
||||
Sym, Entry->Filename, HostEntry, FileOffset);
|
||||
|
||||
std::vector<gdb_line_mapping> Lines;
|
||||
for (const auto &GuestOpcode : DebugData->GuestOpcodes) {
|
||||
auto Line = map->FindLineMapping(GuestRIP + GuestOpcode.GuestEntryOffset -
|
||||
VAFileStart);
|
||||
if (Line) {
|
||||
Lines.push_back(
|
||||
{Line->LineNumber, HostEntry + GuestOpcode.HostEntryOffset});
|
||||
}
|
||||
}
|
||||
|
||||
size_t size = sizeof(info_t) + 1 * sizeof(blocks_t) +
|
||||
Lines.size() * sizeof(gdb_line_mapping);
|
||||
|
||||
auto mem = (uint8_t *)malloc(size);
|
||||
auto base = mem;
|
||||
info_t *info = (info_t *)mem;
|
||||
mem += sizeof(info_t);
|
||||
|
||||
strncpy(info->filename, map->SourceFile.c_str(), 511);
|
||||
|
||||
info->nblocks = 1;
|
||||
|
||||
auto blocks = (blocks_t *)mem;
|
||||
info->blocks_ofs = mem - base;
|
||||
|
||||
mem += info->nblocks * sizeof(blocks_t);
|
||||
|
||||
for (int i = 0; i < info->nblocks; i++) {
|
||||
strncpy(blocks[i].name, SymName.c_str(), 511);
|
||||
blocks[i].start = HostEntry;
|
||||
blocks[i].end = HostEntry + DebugData->HostCodeSize;
|
||||
}
|
||||
|
||||
info->nlines = Lines.size();
|
||||
|
||||
auto lines = (gdb_line_mapping *)mem;
|
||||
info->lines_ofs = mem - base;
|
||||
mem += info->nlines * sizeof(gdb_line_mapping);
|
||||
|
||||
if (info->nlines) {
|
||||
memcpy(lines, &Lines.at(0), info->nlines * sizeof(gdb_line_mapping));
|
||||
}
|
||||
|
||||
auto entry = new jit_code_entry{0, 0, 0, 0};
|
||||
|
||||
entry->symfile_addr = (const char *)info;
|
||||
entry->symfile_size = size;
|
||||
|
||||
if (__jit_debug_descriptor.first_entry) {
|
||||
__jit_debug_descriptor.relevant_entry->next_entry = entry;
|
||||
entry->prev_entry = __jit_debug_descriptor.relevant_entry;
|
||||
} else {
|
||||
__jit_debug_descriptor.first_entry = entry;
|
||||
}
|
||||
|
||||
__jit_debug_descriptor.relevant_entry = entry;
|
||||
__jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
|
||||
__jit_debug_register_code();
|
||||
}
|
||||
}
|
||||
} // namespace FEXCore
|
||||
#else
|
||||
namespace FEXCore {
|
||||
void GDBJITRegister([[maybe_unused]] FEXCore::IR::AOTIRCacheEntry *Entry,
|
||||
[[maybe_unused]] uintptr_t VAFileStart,
|
||||
[[maybe_unused]] uint64_t GuestRIP,
|
||||
[[maybe_unused]] uintptr_t HostEntry,
|
||||
[[maybe_unused]] FEXCore::Core::DebugData *DebugData) {
|
||||
ERROR_AND_DIE_FMT("GDBSymbols support not compiled in");
|
||||
}
|
||||
} // namespace FEXCore
|
||||
#endif
|
7
External/FEXCore/Source/Interface/GDBJIT/GDBJIT.h
vendored
Normal file
7
External/FEXCore/Source/Interface/GDBJIT/GDBJIT.h
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
#include <Interface/IR/AOTIR.h>
|
||||
|
||||
namespace FEXCore {
|
||||
void GDBJITRegister(FEXCore::IR::AOTIRCacheEntry *Entry, uintptr_t VAFileStart, uint64_t GuestRIP, uintptr_t HostEntry, FEXCore::Core::DebugData *DebugData);
|
||||
}
|
21
External/FEXCore/Source/Interface/IR/AOTIR.cpp
vendored
21
External/FEXCore/Source/Interface/IR/AOTIR.cpp
vendored
@ -6,6 +6,7 @@
|
||||
#include <FEXCore/Utils/Allocator.h>
|
||||
#include <FEXCore/HLE/SyscallHandler.h>
|
||||
#include <Interface/Core/LookupCache.h>
|
||||
#include <Interface/GDBJIT/GDBJIT.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@ -17,6 +18,7 @@
|
||||
#include <unistd.h>
|
||||
#include <xxhash.h>
|
||||
|
||||
|
||||
namespace FEXCore::IR {
|
||||
AOTIRInlineEntry *AOTIRInlineIndex::GetInlineEntry(uint64_t DataOffset) {
|
||||
uintptr_t This = (uintptr_t)this;
|
||||
@ -242,9 +244,9 @@ namespace FEXCore::IR {
|
||||
|
||||
AOTIRCaptureCache::PreGenerateIRFetchResult AOTIRCaptureCache::PreGenerateIRFetch(uint64_t GuestRIP, FEXCore::IR::IRListView *IRList) {
|
||||
auto AOTIRCacheEntry = CTX->SyscallHandler->LookupAOTIRCacheEntry(GuestRIP);
|
||||
|
||||
|
||||
PreGenerateIRFetchResult Result{};
|
||||
|
||||
|
||||
if (AOTIRCacheEntry.Entry) {
|
||||
AOTIRCacheEntry.Entry->ContainsCode = true;
|
||||
|
||||
@ -253,7 +255,7 @@ namespace FEXCore::IR {
|
||||
|
||||
if (Mod != nullptr)
|
||||
{
|
||||
auto AOTEntry = Mod->Find(GuestRIP - AOTIRCacheEntry.Offset);
|
||||
auto AOTEntry = Mod->Find(GuestRIP - AOTIRCacheEntry.VAFileStart);
|
||||
|
||||
if (AOTEntry) {
|
||||
// verify hash
|
||||
@ -291,8 +293,9 @@ namespace FEXCore::IR {
|
||||
FEXCore::IR::IRListView *IRList,
|
||||
FEXCore::Core::DebugData *DebugData,
|
||||
bool GeneratedIR) {
|
||||
|
||||
// Both generated ir and LibraryJITName need a named region lookup
|
||||
if (GeneratedIR || CTX->Config.LibraryJITNaming()) {
|
||||
if (GeneratedIR || CTX->Config.LibraryJITNaming() || CTX->Config.GDBSymbols()) {
|
||||
|
||||
auto AOTIRCacheEntry = CTX->SyscallHandler->LookupAOTIRCacheEntry(GuestRIP);
|
||||
|
||||
@ -301,14 +304,18 @@ namespace FEXCore::IR {
|
||||
CTX->Symbols.RegisterNamedRegion(CodePtr, DebugData->HostCodeSize, AOTIRCacheEntry.Entry->Filename);
|
||||
}
|
||||
|
||||
if (CTX->Config.GDBSymbols()) {
|
||||
GDBJITRegister(AOTIRCacheEntry.Entry, AOTIRCacheEntry.VAFileStart, GuestRIP, (uintptr_t)CodePtr, DebugData);
|
||||
}
|
||||
|
||||
// Add to AOT cache if aot generation is enabled
|
||||
if (GeneratedIR && RAData &&
|
||||
(CTX->Config.AOTIRCapture() || CTX->Config.AOTIRGenerate())) {
|
||||
|
||||
auto hash = XXH3_64bits((void*)StartAddr, Length);
|
||||
|
||||
auto LocalRIP = GuestRIP - AOTIRCacheEntry.Offset;
|
||||
auto LocalStartAddr = StartAddr - AOTIRCacheEntry.Offset;
|
||||
auto LocalRIP = GuestRIP - AOTIRCacheEntry.VAFileStart;
|
||||
auto LocalStartAddr = StartAddr - AOTIRCacheEntry.VAFileStart;
|
||||
auto FileId = AOTIRCacheEntry.Entry->FileId;
|
||||
// The underlying pointer and the unique_ptr deleter for RAData must
|
||||
// be marshalled separately to the lambda below. Otherwise, the
|
||||
@ -377,7 +384,7 @@ namespace FEXCore::IR {
|
||||
|
||||
std::unique_lock lk(AOTIRCacheLock);
|
||||
|
||||
auto Inserted = AOTIRCache.insert({fileid, AOTIRCacheEntry{0, 0, 0, fileid, filename, false}});
|
||||
auto Inserted = AOTIRCache.insert({fileid, AOTIRCacheEntry { .FileId = fileid, .Filename = filename }});
|
||||
auto Entry = &(Inserted.first->second);
|
||||
|
||||
LOGMAN_THROW_A_FMT(Entry->Array == nullptr, "Duplicate LoadAOTIRCacheEntry");
|
||||
|
2
External/FEXCore/Source/Interface/IR/AOTIR.h
vendored
2
External/FEXCore/Source/Interface/IR/AOTIR.h
vendored
@ -12,6 +12,7 @@
|
||||
#include <unordered_map>
|
||||
#include <shared_mutex>
|
||||
#include <queue>
|
||||
#include <FEXCore/HLE/SourcecodeResolver.h>
|
||||
|
||||
namespace FEXCore::Core {
|
||||
struct DebugData;
|
||||
@ -75,6 +76,7 @@ namespace FEXCore::IR {
|
||||
AOTIRInlineIndex *Array;
|
||||
void *FilePtr;
|
||||
size_t Size;
|
||||
std::unique_ptr<FEXCore::HLE::SourcecodeMap> SourcecodeMap;
|
||||
std::string FileId;
|
||||
std::string Filename;
|
||||
bool ContainsCode;
|
||||
|
5
External/FEXCore/Source/Interface/IR/IR.json
vendored
5
External/FEXCore/Source/Interface/IR/IR.json
vendored
@ -181,6 +181,11 @@
|
||||
"RAOverride": "0"
|
||||
},
|
||||
|
||||
"GuestOpcode u32:$GuestEntryOffset": {
|
||||
"Desc": ["Marks the beginning of a guest opcode"],
|
||||
"HasSideEffects": true
|
||||
},
|
||||
|
||||
"GPR = ValidateCode u64:$CodeOriginalLow, u64:$CodeOriginalhigh, i64:$Offset, u8:$CodeLength": {
|
||||
"HasSideEffects": true,
|
||||
"HasDest": true,
|
||||
|
22
External/FEXCore/include/FEXCore/Debug/GDBReaderInterface.h
vendored
Normal file
22
External/FEXCore/include/FEXCore/Debug/GDBReaderInterface.h
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include <gdb/jit-reader.h>
|
||||
|
||||
// everything is stored inline as it is marshaled cross process by gdb
|
||||
|
||||
struct blocks_t {
|
||||
char name[512];
|
||||
GDB_CORE_ADDR start;
|
||||
GDB_CORE_ADDR end;
|
||||
};
|
||||
|
||||
struct info_t {
|
||||
char filename[512];
|
||||
|
||||
ptrdiff_t blocks_ofs;
|
||||
ptrdiff_t lines_ofs;
|
||||
|
||||
int nblocks;
|
||||
int nlines;
|
||||
};
|
@ -46,6 +46,11 @@ namespace FEXCore::Core {
|
||||
uint32_t HostCodeSize;
|
||||
};
|
||||
|
||||
struct DebugDataGuestOpcode {
|
||||
uint64_t GuestEntryOffset;
|
||||
ptrdiff_t HostEntryOffset;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Contains debug data for a block of code for later debugger analysis
|
||||
*
|
||||
@ -54,6 +59,7 @@ namespace FEXCore::Core {
|
||||
struct DebugData {
|
||||
uint64_t HostCodeSize; ///< The size of the code generated in the host JIT
|
||||
std::vector<DebugDataSubblock> Subblocks;
|
||||
std::vector<DebugDataGuestOpcode> GuestOpcodes;
|
||||
std::vector<FEXCore::CPU::Relocation> *Relocations;
|
||||
};
|
||||
|
||||
|
96
External/FEXCore/include/FEXCore/HLE/SourcecodeResolver.h
vendored
Normal file
96
External/FEXCore/include/FEXCore/HLE/SourcecodeResolver.h
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace FEXCore::IR {
|
||||
struct AOTIRCacheEntry;
|
||||
}
|
||||
|
||||
namespace FEXCore::HLE {
|
||||
|
||||
struct SourcecodeLineMapping {
|
||||
uintptr_t FileGuestBegin;
|
||||
uintptr_t FileGuestEnd;
|
||||
|
||||
int LineNumber;
|
||||
};
|
||||
|
||||
struct SourcecodeSymbolMapping {
|
||||
uintptr_t FileGuestBegin;
|
||||
uintptr_t FileGuestEnd;
|
||||
|
||||
std::string Name;
|
||||
|
||||
static std::string SymName(const SourcecodeSymbolMapping *Sym, const std::string &GuestFilename, uintptr_t HostEntry, uintptr_t FileBegin) {
|
||||
if (Sym) {
|
||||
auto SymOffset = FileBegin - Sym->FileGuestBegin;
|
||||
if (SymOffset) {
|
||||
return fmt::format("{}: {}+{} @{:x}", std::filesystem::path(GuestFilename).stem().string(), Sym->Name,
|
||||
SymOffset, HostEntry);
|
||||
} else {
|
||||
return fmt::format("{}: {} @{:x}", std::filesystem::path(GuestFilename).stem().string(), Sym->Name,
|
||||
HostEntry);
|
||||
}
|
||||
} else {
|
||||
return fmt::format("{}: +{} @{:x}", std::filesystem::path(GuestFilename).stem().string(), FileBegin,
|
||||
HostEntry);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct SourcecodeMap {
|
||||
std::string SourceFile;
|
||||
std::vector<SourcecodeLineMapping> SortedLineMappings;
|
||||
std::vector<SourcecodeSymbolMapping> SortedSymbolMappings;
|
||||
|
||||
template<typename F>
|
||||
void IterateLineMappings(uintptr_t FileBegin, uintptr_t Size, const F &Callback) const {
|
||||
auto Begin = FileBegin;
|
||||
auto End = FileBegin + Size;
|
||||
|
||||
auto Found = std::lower_bound(SortedLineMappings.cbegin(), SortedLineMappings.cend(), Begin, [](const auto &Range, const auto Position) {
|
||||
return Range.FileGuestEnd <= Position;
|
||||
});
|
||||
|
||||
while (Found != SortedLineMappings.cend()) {
|
||||
if (Found->FileGuestBegin < End && Found->FileGuestEnd > Begin) {
|
||||
Callback(Found);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
Found++;
|
||||
}
|
||||
}
|
||||
|
||||
const SourcecodeLineMapping *FindLineMapping(uintptr_t FileBegin) const {
|
||||
return Find(FileBegin, SortedLineMappings);
|
||||
}
|
||||
|
||||
const SourcecodeSymbolMapping *FindSymbolMapping(uintptr_t FileBegin) const {
|
||||
return Find(FileBegin, SortedSymbolMappings);
|
||||
}
|
||||
private:
|
||||
template<typename VecT>
|
||||
const typename VecT::value_type *Find(uintptr_t FileBegin, const VecT &SortedMappings) const {
|
||||
auto Found = std::lower_bound(SortedMappings.cbegin(), SortedMappings.cend(), FileBegin, [](const auto &Range, const auto Position) {
|
||||
return Range.FileGuestEnd <= Position;
|
||||
});
|
||||
|
||||
if (Found != SortedMappings.end() && Found->FileGuestBegin <= FileBegin && Found->FileGuestEnd > FileBegin) {
|
||||
return &(*Found);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class SourcecodeResolver {
|
||||
public:
|
||||
virtual std::unique_ptr<SourcecodeMap> GenerateMap(const std::string_view& GuestBinaryFile, const std::string_view& GuestBinaryFileId) = 0;
|
||||
};
|
||||
}
|
@ -50,9 +50,11 @@ namespace FEXCore::HLE {
|
||||
};
|
||||
|
||||
class SyscallHandler;
|
||||
class SourcecodeResolver;
|
||||
|
||||
struct AOTIRCacheEntryLookupResult {
|
||||
AOTIRCacheEntryLookupResult(FEXCore::IR::AOTIRCacheEntry *Entry, uintptr_t Offset, FHU::ScopedSignalMaskWithSharedLock &&lk)
|
||||
: Entry(Entry), Offset(Offset), lk(std::move(lk))
|
||||
AOTIRCacheEntryLookupResult(FEXCore::IR::AOTIRCacheEntry *Entry, uintptr_t VAFileStart, FHU::ScopedSignalMaskWithSharedLock &&lk)
|
||||
: Entry(Entry), VAFileStart(VAFileStart), lk(std::move(lk))
|
||||
{
|
||||
|
||||
}
|
||||
@ -60,7 +62,7 @@ namespace FEXCore::HLE {
|
||||
AOTIRCacheEntryLookupResult(AOTIRCacheEntryLookupResult&&) = default;
|
||||
|
||||
FEXCore::IR::AOTIRCacheEntry *Entry;
|
||||
uintptr_t Offset;
|
||||
uintptr_t VAFileStart;
|
||||
|
||||
friend class SyscallHandler;
|
||||
protected:
|
||||
@ -80,6 +82,7 @@ namespace FEXCore::HLE {
|
||||
virtual void MarkGuestExecutableRange(uint64_t Start, uint64_t Length) { }
|
||||
virtual AOTIRCacheEntryLookupResult LookupAOTIRCacheEntry(uint64_t GuestAddr) = 0;
|
||||
|
||||
virtual SourcecodeResolver *GetSourcecodeResolver() { return nullptr; }
|
||||
protected:
|
||||
SyscallOSABI OSABI;
|
||||
};
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <FEXCore/Utils/LogManager.h>
|
||||
|
||||
#include "Linux/Utils/ELFContainer.h"
|
||||
|
||||
/*
|
||||
@ -191,6 +193,36 @@ struct ELFParser {
|
||||
return true;
|
||||
}
|
||||
|
||||
ptrdiff_t FileToVA(off_t FileOffset) {
|
||||
for (auto phdr : phdrs) {
|
||||
if (phdr.p_offset <= FileOffset && (phdr.p_offset + phdr.p_filesz) > FileOffset) {
|
||||
|
||||
auto SectionFileOffset = FileOffset - phdr.p_offset;
|
||||
|
||||
if (SectionFileOffset < phdr.p_memsz) {
|
||||
return SectionFileOffset + phdr.p_vaddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
off_t VAToFile(ptrdiff_t VAOffset) {
|
||||
for (auto phdr : phdrs) {
|
||||
if (phdr.p_vaddr <= VAOffset && (phdr.p_vaddr + phdr.p_memsz) > VAOffset) {
|
||||
|
||||
auto SectionVAOffset = VAOffset - phdr.p_vaddr;
|
||||
|
||||
if (SectionVAOffset < phdr.p_filesz) {
|
||||
return SectionVAOffset + phdr.p_offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ReadElf(const std::string &file) {
|
||||
int NewFD = ::open(file.c_str(), O_RDONLY);
|
||||
|
||||
|
@ -7,6 +7,7 @@ $end_info$
|
||||
*/
|
||||
|
||||
#include "Linux/Utils/ELFContainer.h"
|
||||
#include "Linux/Utils/ELFParser.h"
|
||||
|
||||
#include "Tests/LinuxSyscalls/LinuxAllocator.h"
|
||||
#include "Tests/LinuxSyscalls/Syscalls.h"
|
||||
@ -37,6 +38,7 @@ $end_info$
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -700,4 +702,313 @@ void SyscallHandler::UnlockAfterFork() {
|
||||
FM.GetFDLock()->unlock();
|
||||
}
|
||||
|
||||
static bool isHEX(char c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
|
||||
}
|
||||
|
||||
std::unique_ptr<FEXCore::HLE::SourcecodeMap> SyscallHandler::GenerateMap(const std::string_view& GuestBinaryFile, const std::string_view& GuestBinaryFileId) {
|
||||
|
||||
ELFParser GuestELF;
|
||||
|
||||
if (!GuestELF.ReadElf(std::string(GuestBinaryFile))) {
|
||||
LogMan::Msg::DFmt("GenerateMap: '{}' is not an elf file?", GuestBinaryFile);
|
||||
return {};
|
||||
}
|
||||
|
||||
struct stat GuestBinaryFileStat;
|
||||
|
||||
if (stat(GuestBinaryFile.data(), &GuestBinaryFileStat)) {
|
||||
LogMan::Msg::DFmt("GenerateMap: failed to stat '{}'", GuestBinaryFile);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
auto FexSrcPath = std::filesystem::path(FEXCore::Config::GetDataDirectory()) / "fexsrc";
|
||||
std::filesystem::create_directories(FexSrcPath, ec);
|
||||
|
||||
if (ec) {
|
||||
LogMan::Msg::DFmt("GenerateMap: failed to create_directories '{}'", FexSrcPath.string());
|
||||
return {};
|
||||
}
|
||||
|
||||
auto GuestSourceFile = (FexSrcPath / GuestBinaryFileId).string() + ".src";
|
||||
|
||||
struct stat GuestSourceFileStat;
|
||||
|
||||
if (stat(GuestSourceFile.data(), &GuestSourceFileStat) != 0 || GuestBinaryFileStat.st_mtime > GuestSourceFileStat.st_mtime) {
|
||||
LogMan::Msg::DFmt("GenerateMap: Generating source for '{}'", GuestBinaryFile);
|
||||
auto command = fmt::format("x86_64-linux-gnu-objdump -SC \'{}\' > '{}'", GuestBinaryFile, GuestSourceFile);
|
||||
if (system(command.c_str()) != 0) {
|
||||
LogMan::Msg::DFmt("GenerateMap: '{}' failed", command);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
auto GuestIndexFile = (FexSrcPath / GuestBinaryFileId).string() + ".idx";
|
||||
struct stat GuestIndexFileStat;
|
||||
|
||||
bool GenerateIndex = stat(GuestIndexFile.data(), &GuestIndexFileStat) != 0 || GuestSourceFileStat.st_mtime > GuestIndexFileStat.st_mtime;
|
||||
|
||||
if (!GenerateIndex) {
|
||||
// Index file de-serialization
|
||||
LogMan::Msg::DFmt("GenerateMap: Reading index '{}'", GuestIndexFile);
|
||||
|
||||
std::ifstream Stream(GuestIndexFile);
|
||||
|
||||
if (!Stream) {
|
||||
LogMan::Msg::DFmt("GenerateMap: Failed to open '{}'", GuestIndexFile);
|
||||
goto DoGenerate;
|
||||
}
|
||||
|
||||
//"fexsrcindex0"
|
||||
char filemagic[12];
|
||||
Stream.read(filemagic, sizeof(filemagic));
|
||||
if (memcmp(filemagic, "fexsrcindex0", sizeof(filemagic)) != 0) {
|
||||
LogMan::Msg::DFmt("GenerateMap: '{}' has invalid magic '{}'", GuestIndexFile, filemagic);
|
||||
goto DoGenerate;
|
||||
}
|
||||
|
||||
auto rv = std::make_unique<FEXCore::HLE::SourcecodeMap>();
|
||||
|
||||
{
|
||||
auto len = rv->SourceFile.size();
|
||||
Stream.read((char*)&len, sizeof(len));
|
||||
rv->SourceFile.resize(len);
|
||||
Stream.read(rv->SourceFile.data(), len);
|
||||
}
|
||||
|
||||
{
|
||||
auto len = rv->SortedLineMappings.size();
|
||||
|
||||
Stream.read((char*)&len, sizeof(len));
|
||||
|
||||
rv->SortedLineMappings.resize(len);
|
||||
|
||||
for (auto &Mapping: rv->SortedLineMappings) {
|
||||
Stream.read((char*)&Mapping.FileGuestBegin, sizeof(Mapping.FileGuestBegin));
|
||||
Stream.read((char*)&Mapping.FileGuestEnd, sizeof(Mapping.FileGuestEnd));
|
||||
Stream.read((char*)&Mapping.LineNumber, sizeof(Mapping.LineNumber));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto len = rv->SortedSymbolMappings.size();
|
||||
|
||||
Stream.read((char*)&len, sizeof(len));
|
||||
|
||||
rv->SortedSymbolMappings.resize(len);
|
||||
|
||||
for (auto &Mapping: rv->SortedSymbolMappings) {
|
||||
Stream.read((char*)&Mapping.FileGuestBegin, sizeof(Mapping.FileGuestBegin));
|
||||
Stream.read((char*)&Mapping.FileGuestEnd, sizeof(Mapping.FileGuestEnd));
|
||||
|
||||
{
|
||||
auto len = Mapping.Name.size();
|
||||
Stream.read((char*)&len, sizeof(len));
|
||||
Mapping.Name.resize(len);
|
||||
Stream.read(Mapping.Name.data(), len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogMan::Msg::DFmt("GenerateMap: Finished reading index");
|
||||
return rv;
|
||||
} else {
|
||||
// objdump output parsing, index generation, index file serialization
|
||||
DoGenerate:
|
||||
LogMan::Msg::DFmt("GenerateMap: Generating index for '{}'", GuestSourceFile);
|
||||
std::ifstream Stream(GuestSourceFile);
|
||||
|
||||
if (!Stream) {
|
||||
LogMan::Msg::DFmt("GenerateMap: Failed to open '{}'", GuestSourceFile);
|
||||
}
|
||||
|
||||
std::ofstream IndexStream(GuestIndexFile);
|
||||
|
||||
if (!IndexStream) {
|
||||
LogMan::Msg::DFmt("GenerateMap: Failed to open '{}' for writing", GuestIndexFile);
|
||||
}
|
||||
|
||||
IndexStream.write("fexsrcindex0", strlen("fexsrcindex0"));
|
||||
|
||||
// objdump parsing
|
||||
std::string Line;
|
||||
int LineNum = 0;
|
||||
|
||||
bool PreviousLineWasEmpty = false;
|
||||
|
||||
uintptr_t LastSymbolOffset{};
|
||||
uintptr_t CurrentSymbolOffset{};
|
||||
std::string LastSymbolName;
|
||||
|
||||
uintptr_t LastOffset{};
|
||||
uintptr_t CurrentOffset{};
|
||||
int LastOffsetLine;
|
||||
|
||||
auto rv = std::make_unique<FEXCore::HLE::SourcecodeMap>();
|
||||
|
||||
rv->SourceFile = GuestSourceFile;
|
||||
|
||||
auto EndSymbol = [&] {
|
||||
if (LastSymbolOffset) {
|
||||
rv->SortedSymbolMappings.push_back({LastSymbolOffset, CurrentSymbolOffset, LastSymbolName});
|
||||
|
||||
// LogMan::Msg::DFmt("Ended Symbol {} - {:x}...{:x}", LastSymbolName, LastSymbolOffset, CurrentSymbolOffset);
|
||||
}
|
||||
LastSymbolOffset = {};
|
||||
};
|
||||
|
||||
auto EndLine = [&] {
|
||||
if (LastOffset) {
|
||||
rv->SortedLineMappings.push_back({LastOffset, CurrentOffset, LastOffsetLine});
|
||||
|
||||
// LogMan::Msg::DFmt("Ended Line {} - {:x}...{:x}", LastOffsetLine, LastOffset, CurrentOffset);
|
||||
}
|
||||
LastOffset = {};
|
||||
};
|
||||
|
||||
while (std::getline(Stream, Line)) {
|
||||
LineNum++;
|
||||
|
||||
auto LineIsEmpty = Line.empty();
|
||||
|
||||
if (LineIsEmpty) {
|
||||
PreviousLineWasEmpty = true;
|
||||
} else {
|
||||
|
||||
// LogMan::Msg::DFmt("Line: '{}'", Line);
|
||||
|
||||
if (isHEX(Line[0])) {
|
||||
std::string addr;
|
||||
int offs = 1;
|
||||
for (; !isspace(Line[offs]) && offs < Line.size(); offs++)
|
||||
;
|
||||
|
||||
if (offs == Line.size())
|
||||
continue;
|
||||
if (offs != 8 && offs != 16)
|
||||
continue;
|
||||
|
||||
auto VAOffset = std::strtoul(Line.substr(0, offs).c_str(), nullptr, 16);
|
||||
|
||||
auto FileOffset = GuestELF.VAToFile(VAOffset);
|
||||
|
||||
if (FileOffset == 0) {
|
||||
LogMan::Msg::EFmt("File Offset {:x} did not map to file?! {}", VAOffset, Line);
|
||||
}
|
||||
|
||||
CurrentSymbolOffset = FileOffset;
|
||||
|
||||
if (PreviousLineWasEmpty) {
|
||||
EndSymbol();
|
||||
}
|
||||
LastSymbolOffset = CurrentSymbolOffset;
|
||||
|
||||
for (; Line[offs] != '<' && offs < Line.size(); offs++)
|
||||
;
|
||||
|
||||
if (offs == Line.size())
|
||||
continue;
|
||||
|
||||
offs++;
|
||||
|
||||
LastSymbolName = Line.substr(offs, Line.size() - 2 - offs);
|
||||
|
||||
// LogMan::Msg::DFmt("Symbol {} @ {:x} -> Line {}", LastSymbolName, LastSymbolOffset, LineNum);
|
||||
} else if (isspace(Line[0])) {
|
||||
int offs = 1;
|
||||
for (; isspace(Line[offs]) && offs < Line.size(); offs++)
|
||||
;
|
||||
|
||||
if (offs == Line.size())
|
||||
continue;
|
||||
|
||||
int start = offs;
|
||||
|
||||
for (; Line[offs] != ':' && offs < Line.size(); offs++)
|
||||
;
|
||||
|
||||
if (offs == Line.size())
|
||||
continue;
|
||||
|
||||
if (Line[offs + 1] == '\t') {
|
||||
auto VAOffsetStr = Line.substr(start, offs - start);
|
||||
auto VAOffset = std::strtoul(VAOffsetStr.c_str(), nullptr, 16);
|
||||
auto FileOffset = GuestELF.VAToFile(VAOffset);
|
||||
if (FileOffset == 0) {
|
||||
LogMan::Msg::EFmt("File Offset {:x} did not map to file?! {}", VAOffset, Line);
|
||||
} else {
|
||||
if (LastOffset > FileOffset) {
|
||||
LogMan::Msg::EFmt("File Offset {:x} less than previous {:} ?! {}", FileOffset, LastOffset, Line);
|
||||
}
|
||||
CurrentOffset = FileOffset;
|
||||
|
||||
EndLine();
|
||||
|
||||
LastOffset = CurrentOffset;
|
||||
LastOffsetLine = LineNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
// something else -- keep going
|
||||
}
|
||||
}
|
||||
|
||||
CurrentOffset = LastOffset + 4;
|
||||
CurrentSymbolOffset = CurrentOffset;
|
||||
|
||||
EndSymbol();
|
||||
EndLine();
|
||||
|
||||
// Index post processing - entires are sorted for faster lookups
|
||||
|
||||
std::sort(rv->SortedLineMappings.begin(), rv->SortedLineMappings.end(),
|
||||
[](const auto &lhs, const auto &rhs) { return lhs.FileGuestEnd <= rhs.FileGuestBegin; });
|
||||
|
||||
std::sort(rv->SortedSymbolMappings.begin(), rv->SortedSymbolMappings.end(),
|
||||
[](const auto &lhs, const auto &rhs) { return lhs.FileGuestEnd <= rhs.FileGuestBegin; });
|
||||
|
||||
// Index serialization
|
||||
{
|
||||
auto len = rv->SourceFile.size();
|
||||
IndexStream.write((const char*)&len, sizeof(len));
|
||||
IndexStream.write(rv->SourceFile.c_str(), len);
|
||||
}
|
||||
|
||||
{
|
||||
auto len = rv->SortedLineMappings.size();
|
||||
|
||||
IndexStream.write((const char*)&len, sizeof(len));
|
||||
|
||||
for (const auto &Mapping: rv->SortedLineMappings) {
|
||||
IndexStream.write((const char*)&Mapping.FileGuestBegin, sizeof(Mapping.FileGuestBegin));
|
||||
IndexStream.write((const char*)&Mapping.FileGuestEnd, sizeof(Mapping.FileGuestEnd));
|
||||
IndexStream.write((const char*)&Mapping.LineNumber, sizeof(Mapping.LineNumber));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto len = rv->SortedSymbolMappings.size();
|
||||
|
||||
IndexStream.write((char*)&len, sizeof(len));
|
||||
|
||||
for (const auto &Mapping: rv->SortedSymbolMappings) {
|
||||
IndexStream.write((const char*)&Mapping.FileGuestBegin, sizeof(Mapping.FileGuestBegin));
|
||||
IndexStream.write((const char*)&Mapping.FileGuestEnd, sizeof(Mapping.FileGuestEnd));
|
||||
|
||||
{
|
||||
auto len = Mapping.Name.size();
|
||||
IndexStream.write((const char*)&len, sizeof(len));
|
||||
IndexStream.write(Mapping.Name.c_str(), len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogMan::Msg::DFmt("GenerateMap: Finished generating index", GuestIndexFile);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ $end_info$
|
||||
|
||||
#include <FEXCore/Config/Config.h>
|
||||
#include <FEXCore/HLE/SyscallHandler.h>
|
||||
#include <FEXCore/HLE/SourcecodeResolver.h>
|
||||
#include <FEXCore/IR/IR.h>
|
||||
#include <FEXCore/Utils/CompilerDefs.h>
|
||||
|
||||
@ -83,7 +84,7 @@ struct ExecveAtArgs {
|
||||
|
||||
uint64_t ExecveHandler(const char *pathname, char* const* argv, char* const* envp, ExecveAtArgs *Args);
|
||||
|
||||
class SyscallHandler : public FEXCore::HLE::SyscallHandler {
|
||||
class SyscallHandler : public FEXCore::HLE::SyscallHandler, FEXCore::HLE::SourcecodeResolver {
|
||||
public:
|
||||
virtual ~SyscallHandler();
|
||||
|
||||
@ -192,6 +193,8 @@ public:
|
||||
///// FORK tracking /////
|
||||
void LockBeforeFork();
|
||||
void UnlockAfterFork();
|
||||
|
||||
SourcecodeResolver *GetSourcecodeResolver() override { return this; }
|
||||
|
||||
protected:
|
||||
SyscallHandler(FEXCore::Context::Context *_CTX, FEX::HLE::SignalDelegator *_SignalDelegation);
|
||||
@ -224,6 +227,8 @@ private:
|
||||
#endif
|
||||
|
||||
std::unique_ptr<FEX::HLE::MemAllocator> Alloc32Handler{};
|
||||
|
||||
std::unique_ptr<FEXCore::HLE::SourcecodeMap> GenerateMap(const std::string_view& GuestBinaryFile, const std::string_view& GuestBinaryFileId) override;
|
||||
|
||||
///// VMA (Virtual Memory Area) tracking /////
|
||||
|
||||
|
@ -173,7 +173,7 @@ FEXCore::HLE::AOTIRCacheEntryLookupResult SyscallHandler::LookupAOTIRCacheEntry(
|
||||
|
||||
if (Entry != _SyscallHandler->VMATracking.VMAs.end()) {
|
||||
rv.Entry = Entry->second.Resource ? Entry->second.Resource->AOTIRCacheEntry : nullptr;
|
||||
rv.Offset = Entry->second.Base - Entry->second.Offset;
|
||||
rv.VAFileStart = Entry->second.Base - Entry->second.Offset;
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
@ -11,6 +11,9 @@ if (NOT TERMUX_BUILD)
|
||||
add_subdirectory(FEXRootFSFetcher/)
|
||||
endif()
|
||||
|
||||
if (ENABLE_GDB_SYMBOLS)
|
||||
add_subdirectory(FEXGDBReader/)
|
||||
endif()
|
||||
add_subdirectory(FEXGetConfig/)
|
||||
add_subdirectory(FEXServer/)
|
||||
|
||||
|
15
Source/Tools/FEXGDBReader/CMakeLists.txt
Normal file
15
Source/Tools/FEXGDBReader/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
set(NAME FEXGDBReader)
|
||||
set(SRCS FEXGDBReader.cpp)
|
||||
|
||||
add_library(${NAME} SHARED ${SRCS})
|
||||
|
||||
install(TARGETS ${NAME}
|
||||
RUNTIME
|
||||
LIBRARY DESTINATION /usr/lib/gdb
|
||||
COMPONENT LIBRARY)
|
||||
|
||||
target_include_directories(${NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/Source/)
|
||||
target_include_directories(${NAME} PRIVATE ${CMAKE_BINARY_DIR}/generated)
|
||||
|
||||
# We don't actually link, but this is a nice way to get the include dirs
|
||||
target_link_libraries(${NAME} PRIVATE Common)
|
65
Source/Tools/FEXGDBReader/FEXGDBReader.cpp
Normal file
65
Source/Tools/FEXGDBReader/FEXGDBReader.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include <FEXCore/Debug/GDBReaderInterface.h>
|
||||
|
||||
GDB_DECLARE_GPL_COMPATIBLE_READER;
|
||||
|
||||
#define debugf(...)
|
||||
|
||||
extern "C" {
|
||||
static enum gdb_status read_debug_info(struct gdb_reader_funcs *self, struct gdb_symbol_callbacks *cbs, void *memory, long memory_sz) {
|
||||
|
||||
info_t *info = (info_t *)memory;
|
||||
blocks_t *blocks = (blocks_t *)(info->blocks_ofs + (long)memory);
|
||||
gdb_line_mapping *lines = (gdb_line_mapping *)(info->lines_ofs + (long)memory);
|
||||
debugf("info: %p\n", info);
|
||||
debugf("info: s %p\n", info->filename);
|
||||
debugf("info: s %s\n", info->filename);
|
||||
debugf("info: l %d\n", info->nlines);
|
||||
debugf("info: b %d\n", info->nblocks);
|
||||
|
||||
struct gdb_object *object = cbs->object_open(cbs);
|
||||
struct gdb_symtab *symtab = cbs->symtab_open(cbs, object, info->filename);
|
||||
|
||||
for (int i = 0; i < info->nblocks; i++) {
|
||||
debugf("info: %d\n", i);
|
||||
debugf("info: %lx\n", blocks[i].start);
|
||||
debugf("info: %lx\n", blocks[i].end);
|
||||
debugf("info: %s\n", blocks[i].name);
|
||||
cbs->block_open(cbs, symtab, NULL, blocks[i].start, blocks[i].end, blocks[i].name);
|
||||
}
|
||||
|
||||
debugf("info: lines %d\n", info->nlines);
|
||||
debugf("info: lines %p\n", lines);
|
||||
|
||||
for (int i = 0; i < info->nlines; i++) {
|
||||
debugf("info: line: %d\n", i);
|
||||
debugf("info: line pc: %lx\n", lines[i].pc);
|
||||
debugf("info: line file: %d\n", lines[i].line);
|
||||
}
|
||||
cbs->line_mapping_add(cbs, symtab, info->nlines, lines);
|
||||
|
||||
// don't close here, symtab and object are cached
|
||||
cbs->symtab_close(cbs, symtab);
|
||||
cbs->object_close(cbs, object);
|
||||
return GDB_SUCCESS;
|
||||
}
|
||||
|
||||
enum gdb_status unwind_frame(struct gdb_reader_funcs *self, struct gdb_unwind_callbacks *cbs) { return GDB_SUCCESS; }
|
||||
|
||||
struct gdb_frame_id get_frame_id(struct gdb_reader_funcs *self, struct gdb_unwind_callbacks *cbs) {
|
||||
struct gdb_frame_id frame = {0x1234000, 0};
|
||||
return frame;
|
||||
}
|
||||
|
||||
void destroy_reader(struct gdb_reader_funcs *self) {}
|
||||
|
||||
extern struct gdb_reader_funcs *gdb_init_reader(void) {
|
||||
static struct gdb_reader_funcs funcs = {GDB_READER_INTERFACE_VERSION, NULL, read_debug_info, unwind_frame, get_frame_id, destroy_reader};
|
||||
return &funcs;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user