about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorYang Liu <liuyang22@iscas.ac.cn>2024-12-18 05:01:33 +0800
committerGitHub <noreply@github.com>2024-12-17 22:01:33 +0100
commit6e0f32f4ca4b35bda036caa6ad3abcae4ae64cdc (patch)
tree39a144624b528deba4421d7b71098db69d99eb80
parent62eca11beef3f211a93235b18d556332fafc74de (diff)
downloadbox64-6e0f32f4ca4b35bda036caa6ad3abcae4ae64cdc.tar.gz
box64-6e0f32f4ca4b35bda036caa6ad3abcae4ae64cdc.zip
Added initial GDBJIT support (#2162)
* Added GDBJIT support

* fix

* fix
-rw-r--r--CMakeLists.txt19
-rw-r--r--gdbjit/reader.c41
-rw-r--r--src/core.c1
-rw-r--r--src/dynarec/arm64/dynarec_arm64_functions.c6
-rw-r--r--src/dynarec/arm64/dynarec_arm64_private.h1
-rw-r--r--src/dynarec/dynablock_private.h2
-rw-r--r--src/dynarec/dynarec_native.c11
-rw-r--r--src/dynarec/la64/dynarec_la64_functions.c4
-rw-r--r--src/dynarec/la64/dynarec_la64_private.h1
-rw-r--r--src/dynarec/rv64/dynarec_rv64_functions.c4
-rw-r--r--src/dynarec/rv64/dynarec_rv64_private.h1
-rw-r--r--src/include/debug.h1
-rw-r--r--src/include/gdbjit.h32
-rw-r--r--src/tools/gdbjit.c134
14 files changed, 257 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6903beb8..766f531c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -42,6 +42,7 @@ option(WITH_MOLD "Set to ON to use with mold" ${WITH_MOLD})
 option(BOX32 "Set to ON to add Linux 32bits support (experimental, do not use)" ${BOX32})
 option(BOX32_BINFMT "Also setup binfmt integration for box32" ${BOX32_BINFMT})
 option(LARCH64_ABI_1 "Set to ON if building for Loongarch64 ABI 1.0 system" ${LARCH64_ABI_1})
+option(GDBJIT "Set to ON to enable GDB JIT support" ${GDBJIT})
 
 if(TERMUX)
     set(TERMUX_PATH "/data/data/com.termux/files")
@@ -95,6 +96,11 @@ if(ANDROID)
     set(BAD_SIGNAL ON CACHE BOOL "")
 endif()
 
+if(GDBJIT)
+    message(STATUS "Build with GDBJIT support")
+    add_definitions(-DGDBJIT)
+endif()
+
 option(LD80BITS "Set to ON if host device have 80bits long double (i.e. i386)" ${LD80BITS})
 option(NOALIGN "Set to ON if host device doesn't need re-align (i.e. i386)" ${NOALIGN})
 option(ARM_DYNAREC "Set to ON to use ARM Dynamic Recompilation" ${ARM_DYNAREC})
@@ -386,6 +392,7 @@ set(ELFLOADER_SRC
     "${BOX64_ROOT}/src/tools/box64stack.c"
     "${BOX64_ROOT}/src/tools/bridge.c"
     "${BOX64_ROOT}/src/tools/callback.c"
+    "${BOX64_ROOT}/src/tools/gdbjit.c"
     "${BOX64_ROOT}/src/tools/my_cpuid.c"
     "${BOX64_ROOT}/src/tools/fileutils.c"
     "${BOX64_ROOT}/src/tools/pathcoll.c"
@@ -1039,6 +1046,11 @@ if(LARCH64_DYNAREC)
     )
 endif()
 
+
+if(GDBJIT)
+set(GDBJITREADER "${BOX64_ROOT}/gdbjit/reader.c")
+endif()
+
 if(DYNAREC)
     set(DYNAREC_PASS
         "${BOX64_ROOT}/src/wrapped/generated/wrapper.h"
@@ -1082,6 +1094,10 @@ if(DYNAREC)
         $<TARGET_OBJECTS:native_pass3>
         $<TARGET_OBJECTS:test_interpreter>
     )
+
+    if(GDBJIT)
+        add_library(box64gdbjitreader SHARED ${GDBJITREADER})
+    endif()
 endif()
 
 # creates git_head.h
@@ -1171,6 +1187,9 @@ if(NOT _x86 AND NOT _x86_64)
     install(PROGRAMS ${CMAKE_SOURCE_DIR}/tests/box64-bash DESTINATION ${TERMUX_PATH}/usr/bin)
    endif()
   endif()
+  if (GDBJIT)
+    install(TARGETS box64gdbjitreader RUNTIME DESTINATION lib)
+  endif()
   if(NOT NO_CONF_INSTALL)
     configure_file(system/box64.conf.cmake system/box64.conf)
 	  if(NOT TERMUX)
diff --git a/gdbjit/reader.c b/gdbjit/reader.c
new file mode 100644
index 00000000..024e88ea
--- /dev/null
+++ b/gdbjit/reader.c
@@ -0,0 +1,41 @@
+#include "gdbjit.h"
+
+static enum gdb_status read_debug_info(struct gdb_reader_funcs* self, struct gdb_symbol_callbacks* cbs, void* memory, long memory_sz)
+{
+    gdbjit_block_t* block = (gdbjit_block_t*)memory;
+
+    struct gdb_object* object = cbs->object_open(cbs);
+    struct gdb_symtab* symtab = cbs->symtab_open(cbs, object, block->filename);
+    cbs->block_open(cbs, symtab, NULL, block->start, block->end, "(block)");
+
+    cbs->line_mapping_add(cbs, symtab, block->nlines, block->lines);
+
+    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 = {0xdeadbeef, 0};
+  return frame;
+}
+
+void destroy_reader(struct gdb_reader_funcs* self) {}
+
+__attribute__((visibility("default"))) 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;
+}
+
+__attribute__((visibility("default"))) int plugin_is_GPL_compatible (void)
+{
+    return 0;
+}
\ No newline at end of file
diff --git a/src/core.c b/src/core.c
index 3cc5caec..ddb83ce2 100644
--- a/src/core.c
+++ b/src/core.c
@@ -72,6 +72,7 @@ int box64_rdtsc_1ghz = 0;
 uint8_t box64_rdtsc_shift = 0;
 char* box64_insert_args = NULL;
 char* box64_new_args = NULL;
+int box64_dynarec_gdbjit = 1;
 #ifdef DYNAREC
 int box64_dynarec = 1;
 int box64_dynarec_dump = 0;
diff --git a/src/dynarec/arm64/dynarec_arm64_functions.c b/src/dynarec/arm64/dynarec_arm64_functions.c
index 590000bf..e473e6ca 100644
--- a/src/dynarec/arm64/dynarec_arm64_functions.c
+++ b/src/dynarec/arm64/dynarec_arm64_functions.c
@@ -25,6 +25,7 @@
 #include "dynarec_arm64_functions.h"
 #include "custommem.h"
 #include "bridge.h"
+#include "gdbjit.h"
 
 // Get a FPU scratch reg
 int fpu_get_scratch(dynarec_arm_t* dyn, int ninst)
@@ -748,6 +749,11 @@ void inst_name_pass3(dynarec_native_t* dyn, int ninst, const char* name, rex_t r
             dynarec_log(LOG_NONE, " %s:%d/%d", dyn->insts[ninst].n.swapped?"SWP":"CMB", dyn->insts[ninst].n.combined1, dyn->insts[ninst].n.combined2);
         dynarec_log(LOG_NONE, "%s\n", (box64_dynarec_dump>1)?"\e[m":"");
     }
+    if (box64_dynarec_gdbjit) {
+        zydis_dec_t* dec = rex.is32bits?my_context->dec32:my_context->dec;
+        const char *inst_name = dec ? DecodeX64Trace(dec, dyn->insts[ninst].x64.addr) : name;
+        dyn->gdbjit_block = GdbJITBlockAddLine(dyn->gdbjit_block, (dyn->native_start + dyn->insts[ninst].address), inst_name);
+    }
 }
 
 void print_opcode(dynarec_native_t* dyn, int ninst, uint32_t opcode)
diff --git a/src/dynarec/arm64/dynarec_arm64_private.h b/src/dynarec/arm64/dynarec_arm64_private.h
index 113e4341..8f10b0f5 100644
--- a/src/dynarec/arm64/dynarec_arm64_private.h
+++ b/src/dynarec/arm64/dynarec_arm64_private.h
@@ -164,6 +164,7 @@ typedef struct dynarec_arm_s {
     uint8_t             doublepop;
     uint8_t             always_test;
     uint8_t             abort;      // abort the creation of the block
+    void*               gdbjit_block;
 } dynarec_arm_t;
 
 void add_next(dynarec_arm_t *dyn, uintptr_t addr);
diff --git a/src/dynarec/dynablock_private.h b/src/dynarec/dynablock_private.h
index 4bf7dd7a..a152bd4e 100644
--- a/src/dynarec/dynablock_private.h
+++ b/src/dynarec/dynablock_private.h
@@ -24,4 +24,4 @@ typedef struct dynablock_s {
     void*           jmpnext;    // a branch jmpnext code when block is marked
 } dynablock_t;
 
-#endif //__DYNABLOCK_PRIVATE_H_
\ No newline at end of file
+#endif //__DYNABLOCK_PRIVATE_H_
diff --git a/src/dynarec/dynarec_native.c b/src/dynarec/dynarec_native.c
index ece7baa0..011815bc 100644
--- a/src/dynarec/dynarec_native.c
+++ b/src/dynarec/dynarec_native.c
@@ -1,3 +1,4 @@
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
@@ -22,6 +23,7 @@
 #include "dynarec_native.h"
 #include "dynarec_arch.h"
 #include "dynarec_next.h"
+#include "gdbjit.h"
 
 void printf_x64_instruction(zydis_dec_t* dec, instruction_x64_t* inst, const char* name) {
     uint8_t *ip = (uint8_t*)inst->addr;
@@ -574,6 +576,9 @@ void* FillBlock64(dynablock_t* block, uintptr_t addr, int alternate, int is32bit
     protectDB(addr, 1);
     // init the helper
     dynarec_native_t helper = {0};
+#ifdef GDBJIT
+    helper.gdbjit_block = box_calloc(1, sizeof(gdbjit_block_t));
+#endif
     current_helper = &helper;
     helper.dynablock = block;
     helper.start = addr;
@@ -748,6 +753,9 @@ void* FillBlock64(dynablock_t* block, uintptr_t addr, int alternate, int is32bit
         printFunctionAddr(helper.start, " => ");
         dynarec_log(LOG_NONE, "%s\n", (box64_dynarec_dump>1)?"\e[m":"");
     }
+    if (box64_dynarec_gdbjit) {
+        GdbJITNewBlock(helper.gdbjit_block, (GDB_CORE_ADDR)block->actual_block, (GDB_CORE_ADDR)block->actual_block + native_size);
+    }
     int oldtable64size = helper.table64size;
     size_t oldnativesize = helper.native_size;
     size_t oldinstsize = helper.insts_size;
@@ -782,6 +790,9 @@ void* FillBlock64(dynablock_t* block, uintptr_t addr, int alternate, int is32bit
     //block->x64_addr = (void*)start;
     block->x64_size = end-start;
     // all done...
+    if (box64_dynarec_gdbjit) {
+        GdbJITBlockReady(helper.gdbjit_block);
+    }
     __clear_cache(actual_p, actual_p+sz);   // need to clear the cache before execution...
     block->hash = X31_hash_code(block->x64_addr, block->x64_size);
     // Check if something changed, to abort if it is
diff --git a/src/dynarec/la64/dynarec_la64_functions.c b/src/dynarec/la64/dynarec_la64_functions.c
index 925113f0..bdb60094 100644
--- a/src/dynarec/la64/dynarec_la64_functions.c
+++ b/src/dynarec/la64/dynarec_la64_functions.c
@@ -25,6 +25,7 @@
 #include "dynarec_la64_functions.h"
 #include "custommem.h"
 #include "bridge.h"
+#include "gdbjit.h"
 
 #define XMM0 0
 #define XMM8 16
@@ -306,6 +307,9 @@ void inst_name_pass3(dynarec_native_t* dyn, int ninst, const char* name, rex_t r
             dynarec_log(LOG_NONE, " %s:%d/%d", dyn->insts[ninst].lsx.swapped ? "SWP" : "CMB", dyn->insts[ninst].lsx.combined1, dyn->insts[ninst].lsx.combined2);
         dynarec_log(LOG_NONE, "%s\n", (box64_dynarec_dump > 1) ? "\e[m" : "");
     }
+    if (box64_dynarec_gdbjit) {
+        dyn->gdbjit_block = GdbJITBlockAddLine(dyn->gdbjit_block, dyn->insts[ninst].address, name);
+    }
 }
 
 // will go badly if address is unaligned
diff --git a/src/dynarec/la64/dynarec_la64_private.h b/src/dynarec/la64/dynarec_la64_private.h
index a9578fc6..4f57d366 100644
--- a/src/dynarec/la64/dynarec_la64_private.h
+++ b/src/dynarec/la64/dynarec_la64_private.h
@@ -133,6 +133,7 @@ typedef struct dynarec_la64_s {
     uint8_t              smwrite;    // for strongmem model emulation
     uint8_t              always_test;
     uint8_t              abort;
+    void*               gdbjit_block;
 } dynarec_la64_t;
 
 void add_next(dynarec_la64_t *dyn, uintptr_t addr);
diff --git a/src/dynarec/rv64/dynarec_rv64_functions.c b/src/dynarec/rv64/dynarec_rv64_functions.c
index 3930d6ea..e14be4f2 100644
--- a/src/dynarec/rv64/dynarec_rv64_functions.c
+++ b/src/dynarec/rv64/dynarec_rv64_functions.c
@@ -27,6 +27,7 @@
 #include "custommem.h"
 #include "bridge.h"
 #include "rv64_lock.h"
+#include "gdbjit.h"
 
 #define XMM0 0
 #define X870 XMM0 + 16
@@ -722,6 +723,9 @@ void inst_name_pass3(dynarec_native_t* dyn, int ninst, const char* name, rex_t r
             dynarec_log(LOG_NONE, " %s:%d/%d", dyn->insts[ninst].e.swapped ? "SWP" : "CMB", dyn->insts[ninst].e.combined1, dyn->insts[ninst].e.combined2);
         dynarec_log(LOG_NONE, "%s\n", (box64_dynarec_dump > 1) ? "\e[m" : "");
     }
+    if (box64_dynarec_gdbjit) {
+        dyn->gdbjit_block = GdbJITBlockAddLine(dyn->gdbjit_block, dyn->insts[ninst].address, name);
+    }
 }
 
 void print_opcode(dynarec_native_t* dyn, int ninst, uint32_t opcode)
diff --git a/src/dynarec/rv64/dynarec_rv64_private.h b/src/dynarec/rv64/dynarec_rv64_private.h
index 9e876fd3..aecea9e9 100644
--- a/src/dynarec/rv64/dynarec_rv64_private.h
+++ b/src/dynarec/rv64/dynarec_rv64_private.h
@@ -180,6 +180,7 @@ typedef struct dynarec_rv64_s {
     uint8_t             inst_sew;       // sew inside current instruction, for vsetvli elimination
     uint8_t             inst_vl;        // vl inside current instruction, for vsetvli elimination
     uint8_t             inst_vlmul;     // vlmul inside current instruction
+    void*               gdbjit_block;
 } dynarec_rv64_t;
 
 // v0 is hardware wired to vector mask register, which should be always reserved
diff --git a/src/include/debug.h b/src/include/debug.h
index cbaab9db..0a8d4058 100644
--- a/src/include/debug.h
+++ b/src/include/debug.h
@@ -18,6 +18,7 @@ extern int box64_rdtsc_1ghz;
 extern uint8_t box64_rdtsc_shift;
 extern int box64_is32bits;
 extern int box64_x11sync;
+extern int box64_dynarec_gdbjit;
 #ifdef DYNAREC
 extern int box64_dynarec_dump;
 extern int box64_dynarec_trace;
diff --git a/src/include/gdbjit.h b/src/include/gdbjit.h
new file mode 100644
index 00000000..70405d3e
--- /dev/null
+++ b/src/include/gdbjit.h
@@ -0,0 +1,32 @@
+#ifndef __GDBJIT_H__
+#define __GDBJIT_H__
+
+#if defined(DYNAREC) && defined(GDBJIT)
+#include <gdb/jit-reader.h>
+#include <stdio.h>
+#include <stdint.h>
+
+typedef struct gdbjit_block_s {
+    char filename[32];
+    FILE* file;
+    GDB_CORE_ADDR start;
+    GDB_CORE_ADDR end;
+    uintptr_t alloced;
+    size_t nlines;
+    struct gdb_line_mapping lines[0];
+} gdbjit_block_t;
+
+
+void GdbJITNewBlock(gdbjit_block_t* block, GDB_CORE_ADDR start, GDB_CORE_ADDR end);
+gdbjit_block_t* GdbJITBlockAddLine(gdbjit_block_t* block, GDB_CORE_ADDR addr, const char* line);
+void GdbJITBlockReady(gdbjit_block_t* block);
+
+#else
+
+#define GdbJITNewBlock(a, b, c)
+#define GdbJITBlockAddLine(a, b, c) NULL
+#define GdbJITBlockReady(a)
+
+#endif
+
+#endif // __GDBJIT_H__
diff --git a/src/tools/gdbjit.c b/src/tools/gdbjit.c
new file mode 100644
index 00000000..d91c4c05
--- /dev/null
+++ b/src/tools/gdbjit.c
@@ -0,0 +1,134 @@
+#ifdef GDBJIT
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <elf.h>
+#include <errno.h>
+#include "gdbjit.h"
+#include "dynablock.h"
+#include "debug.h"
+
+
+/* GDB JIT Compilation Interface -----------------------------------------------
+ * https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html
+ */
+
+enum {
+    GDBJIT_NOACTION = 0,
+    GDBJIT_REGISTER,
+    GDBJIT_UNREGISTER
+};
+
+typedef struct gdbjit_code_entry_s {
+    struct gdbjit_code_entry_s* next_entry;
+    struct gdbjit_code_entry_s* prev_entry;
+    const char* symfile_addr;
+    uint64_t symfile_size;
+} gdbjit_code_entry_t;
+
+typedef struct gdbjit_descriptor_s {
+    uint32_t version;
+    uint32_t action_flag;
+    gdbjit_code_entry_t* relevant_entry;
+    gdbjit_code_entry_t* first_entry;
+} gdbjit_descriptor_t;
+
+/* GDB puts a breakpoint in this function. This can't be optimized out. */
+void __attribute__((noinline)) __jit_debug_register_code()
+{
+    asm volatile("" ::: "memory");
+};
+
+/* Make sure to specify the version statically, because the debugger may check
+ * the version before we can set it.
+ */
+gdbjit_descriptor_t __jit_debug_descriptor = { 1, GDBJIT_NOACTION, NULL, NULL };
+
+/* --------------------------------------------------------------------------- */
+
+void GdbJITNewBlock(gdbjit_block_t* block, GDB_CORE_ADDR start, GDB_CORE_ADDR end)
+{
+    if (!block)
+        return;
+
+    memset(block, 0, sizeof(gdbjit_block_t));
+
+    strcpy(block->filename, "/tmp/box64gdbjit-XXXXXX.S");
+    int fd = mkstemps(block->filename, 2);
+    block->file = fdopen(fd, "w");
+
+    block->start = start;
+    block->end = end;
+    block->alloced = block->nlines = 0;
+}
+
+static size_t GdbJITLinesAvailable(gdbjit_block_t* block)
+{
+    if (!block)
+        return 0;
+
+    return block->alloced > block->nlines;
+}
+
+#define GDBJIT_LINES_MAX_PREALLOC 64
+
+static gdbjit_block_t* GdbJITMakeRoom(gdbjit_block_t* block)
+{
+    if (!block) return NULL;
+
+    if (!GdbJITLinesAvailable(block)) {
+        size_t new_size = block->alloced + GDBJIT_LINES_MAX_PREALLOC;
+        block = box_realloc(block, sizeof(gdbjit_block_t) + new_size * sizeof(struct gdb_line_mapping));
+        if (!block) return NULL;
+        block->alloced = new_size;
+    }
+    return block;
+}
+
+gdbjit_block_t* GdbJITBlockAddLine(gdbjit_block_t* block, GDB_CORE_ADDR addr, const char* line)
+{
+    block->nlines++;
+    block = GdbJITMakeRoom(block);
+    if (!block) return NULL;
+
+    block->lines[block->nlines-1].pc = addr;
+    block->lines[block->nlines-1].line = block->nlines;
+    fprintf(block->file, "%s\n", line);
+    fflush(block->file);
+    return block;
+}
+
+void GdbJITBlockReady(gdbjit_block_t* block)
+{
+    if (!block) return;
+
+    if (block->nlines == 0) {
+        fclose(block->file);
+        box_free(block);
+        return;
+    }
+
+    gdbjit_code_entry_t* entry = (gdbjit_code_entry_t*)box_malloc(sizeof(gdbjit_code_entry_t));
+    if (!entry) {
+        fclose(block->file);
+        box_free(block);
+        return;
+    }
+
+    fclose(block->file);
+
+    entry->symfile_addr = (const char*)block;
+    entry->symfile_size = sizeof(gdbjit_block_t) + block->nlines * sizeof(struct gdb_line_mapping);
+
+    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 = GDBJIT_REGISTER;
+    __jit_debug_register_code();
+}
+#endif