On 12/29/25 22:02, [email protected] wrote:

i've been building a C package manager where the packages are written in C and jitted as opposed to some crazy dsl or external scripting language. hence my interest in tcc. it does pretty much everything i need stock, but i was experimenting with how to debug the jitted code with gdb.

it looks like this boils down to using gdb's builtin jit stuff (__jit_debug_register_code, etc). you just give it a pointer to the elf data and everything works nicely. and tcc obviously has a ton of nice code for outputting to elf.

but the problem is twofold. (1) tcc only outputs elf objects to files. nothing in the api for outputting to a buffer/realloc'ing a buffer and returning. (2) i REALLY want the resulting object to be relocated, but when tcc outputs an object it's obviously not relocated, since it's expected to be linked in to some host program later.

i made a proof of concept where i do the relocation myself. but it's very hairy and moreover feels wrong -- i just jitted the code. i know where it is in memory. i know the offsets. why go through this dance of generating an unrelocated object just to relocate it later?

i believe the fundamental mismatch is that tcc operates in either/or mode. either you want an object file or you want to run it directly. there's no idea of "i want to run directly, but also i want debug symbols". this is understandable...

my question is just whether there's any chance of this work being accepted as a patch to tcc, or if i should just fork. i have everything working; it's not a lot of code, just another entry in libtcc.h that outputs the elf to mem, relocated. i am happy to clean it up and submit it...but if you dont think it will be useful/you just dont want it then i'll just keep my fork.

in any case, thanks for the great library and thanks for reading

I tried a lot of debugging jit code for gdb/lldb but none of it really worked.

So I did the next thing and output the object file and a text file to disk.
Then loaded the symbol table with gdb add-symbol-file. Gdb then finds
the text file from the object file. See tst_run.c how this works.

See attachments.
Do not apply this. It is just a first working version.

    Herman
diff --git a/libtcc.h b/libtcc.h
index 5949c80..738c7d5 100644
--- a/libtcc.h
+++ b/libtcc.h
@@ -105,6 +105,16 @@ LIBTCCAPI void tcc_list_symbols(TCCState *s, void *ctx,
 LIBTCCAPI void *_tcc_setjmp(TCCState *s1, void *jmp_buf, void *top_func, void 
*longjmp);
 #define tcc_setjmp(s1,jb,f) setjmp(_tcc_setjmp(s1, jb, f, longjmp))
 
+/* debugging */
+/* For debugging to work you have to enable it with tcc_set_options */
+
+/* Output object file. This must be done after tcc_relocate.
+   The filename can be loaded with gdb command add-symbol-file */
+LIBTCCAPI int elf_output_obj(TCCState *s1, const char *filename);
+
+/* Write source file. If filename is NULL '<string>' will be used */
+LIBTCCAPI int tcc_write_source(const char *filename, const char *text);
+
 /* custom error printer for runtime exceptions. Returning 0 stops backtrace */
 typedef int TCCBtFunc(void *udata, void *pc, const char *file, int line, const 
char* func, const char *msg);
 LIBTCCAPI void tcc_set_backtrace_func(TCCState *s1, void* userdata, 
TCCBtFunc*);
diff --git a/tccelf.c b/tccelf.c
index e078b91..70a2084 100644
--- a/tccelf.c
+++ b/tccelf.c
@@ -3095,10 +3095,14 @@ static void alloc_sec_names(TCCState *s1, int is_obj)
 }
 
 /* Output an elf .o file */
-static int elf_output_obj(TCCState *s1, const char *filename)
+LIBTCCAPI int elf_output_obj(TCCState *s1, const char *filename)
 {
     Section *s;
     int i, ret, file_offset;
+    for(i = 1; i < s1->nb_sections; i++) {
+       if (s1->sections[i] == NULL)
+           return tcc_error_noabort("Probably forget to set debug option with 
tccapi");
+    }
     /* Allocate strings for section names */
     alloc_sec_names(s1, 1);
     file_offset = (sizeof (ElfW(Ehdr)) + 3) & -4;
@@ -3115,6 +3119,17 @@ static int elf_output_obj(TCCState *s1, const char 
*filename)
     return ret;
 }
 
+LIBTCCAPI int tcc_write_source(const char *filename, const char *text)
+{
+    FILE *fp = fopen(filename ? filename : "<string>", "w");
+
+    if (fp) {
+        fputs(text, fp);
+        fclose(fp);
+    }
+    return fp ? 0 : -1;
+}
+
 LIBTCCAPI int tcc_output_file(TCCState *s, const char *filename)
 {
     s->nb_errors = 0;
diff --git a/tccrun.c b/tccrun.c
index 1fbbf6d..7fe06c1 100644
--- a/tccrun.c
+++ b/tccrun.c
@@ -160,6 +160,8 @@ LIBTCCAPI int tcc_relocate(TCCState *s1)
     ret = tcc_relocate_ex(s1, s1->run_ptr, ptr_diff);
     if (ret == 0)
         st_link(s1);
+    if (s1->do_debug)
+        protect_pages((void*)PAGEALIGN(s1->run_ptr), s1->run_size - PAGESIZE, 
3 /*rwx*/);
     return ret;
 }
 
@@ -299,7 +301,7 @@ static void cleanup_sections(TCCState *s1)
     do {
         for (i = --f; i < p->nb_secs; i++) {
             Section *s = p->secs[i];
-            if (s == s1->symtab || s == s1->symtab->link || s == 
s1->symtab->hash) {
+            if (s1->do_debug || s == s1->symtab || s == s1->symtab->link || s 
== s1->symtab->hash) {
                 s->data = tcc_realloc(s->data, s->data_allocated = 
s->data_offset);
             } else {
                 free_section(s), tcc_free(s), p->secs[i] = NULL;
@@ -311,15 +313,16 @@ static void cleanup_sections(TCCState *s1)
 /* ------------------------------------------------------------- */
 /* 0 = .text rwx  other rw (memory >= 2 pages a 4096 bytes) */
 /* 1 = .text rx   other rw (memory >= 3 pages) */
-/* 2 = .text rx  .rdata ro  .data/.bss rw (memory >= 4 pages) */
+/* 2 = .debug    .debug ro (optional) */
+/* 3 = .text rx  .rdata ro  .data/.bss rw (memory >= 4 pages) */
 
 /* Some targets implement secutiry options that do not allow write in
-   executable code. These targets need CONFIG_RUNMEM_RO=1.
+   executable code. These targets need CONFIG_RUNMEM_RO=2.
    The disadvantage of this is that it requires a little bit more memory. */
 
 #ifndef CONFIG_RUNMEM_RO
 # ifdef __APPLE__
-#   define CONFIG_RUNMEM_RO 1
+#   define CONFIG_RUNMEM_RO 2
 # else
 #   define CONFIG_RUNMEM_RO 0
 #  endif
@@ -355,12 +358,13 @@ redo:
     if (copy == 3)
         return 0;
 
-    for (k = 0; k < 3; ++k) { /* 0:rx, 1:ro, 2:rw sections */
+    for (k = 0; k < 4; ++k) { /* 0:rx, 1:ro, 2:ro debug , 3:rw sections */
         n = 0; addr = 0;
         for(i = 1; i < s1->nb_sections; i++) {
             static const char shf[] = {
-                SHF_ALLOC|SHF_EXECINSTR, SHF_ALLOC, SHF_ALLOC|SHF_WRITE
+                SHF_ALLOC|SHF_EXECINSTR, SHF_ALLOC, 0, SHF_ALLOC|SHF_WRITE
                 };
+           if (k == 2 && s1->do_debug == 0) continue;
             s = s1->sections[i];
             if (shf[k] != (s->sh_flags & (SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR)))
                 continue;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libtcc.h"

static const char program[] =
"#include <stdio.h>\n"
"int value;\n"
"int tst(void)\n"
"{\n"
"   fprintf(stderr, \"Hello World\\n\");\n"
"   value = 10;\n"
"   return 0;\n"
"}\n";

void handle_error(void *opaque, const char *msg)
{
    fprintf(opaque, "%s\n", msg);
}

int
main(void)
{
    int (*func)(void);
    TCCState *s = tcc_new();

    if (!s) {
        fprintf(stderr, __FILE__ ": could not create tcc state\n");
        return 1;
    }
    tcc_set_options(s, "-g");
    tcc_set_error_func(s, stdout, handle_error);
    tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
    if (tcc_compile_string(s, program) == -1)
	return 1;
    if (tcc_relocate(s) < 0)
        return 1;
    elf_output_obj(s, "tst.o");
    tcc_write_source(NULL, program);
    /* set breakpoint on next line. and load symbol file with
       gdb command add-symbol-file.
       The set breakpoint on tst and continue. */
    func = tcc_get_symbol(s, "tst");
    if (!func)
        return 1;
    func();
    tcc_delete(s);
    return 0;
}
_______________________________________________
Tinycc-devel mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/tinycc-devel

Reply via email to