From 1fe3e3bff560b3e6148933687e4a892b695f3a88 Mon Sep 17 00:00:00 2001 From: herman ten brugge Date: Tue, 6 Jan 2026 12:11:51 +0100 Subject: [PATCH] Add support to debug libtcc code I tried several gdb/lldb options to debug libtcc generated code. But gdb/lldb complained that they could not load symbols or that module does not exist. There was also another problem. The code itself was in memory (string) and gdb/lldb do not have functions to acces it. So I came up with a new api for debugging libtcc code. First you enable debugging with: tcc_set_options(s, "-g") Then compile the code with: tcc_compile_string_file(s, program, ".c") Then call tcc_relocate(). And finaly write the object file to disk: elf_output_obj(s, ".o") Now you can start the debugger and put an breakpoint after the elf_output_obj() code. Then use gdb command add-symbol-file .o and from there on you can set breakpoints in the libtcc generated code. You can also step/print variables/... I could not find a simular function in lldb yet. When debugging is done you remove the tcc_set_options(s, "-g"). All other code can remain because tcc_compile_string_file and elf_output_obj do not output any file any more is debug is not set. See also tests/libtcc_debug.c --- libtcc.c | 21 +++++++++++++--- libtcc.h | 12 +++++++++ tccelf.c | 5 +++- tccrun.c | 16 ++++++------ tests/libtcc_debug.c | 58 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 tests/libtcc_debug.c diff --git a/libtcc.c b/libtcc.c index c430d1a2..f8a749db 100644 --- a/libtcc.c +++ b/libtcc.c @@ -791,7 +791,7 @@ ST_FUNC int tcc_open(TCCState *s1, const char *filename) } /* compile the file opened in 'file'. Return non zero if errors. */ -static int tcc_compile(TCCState *s1, int filetype, const char *str, int fd) +static int tcc_compile(TCCState *s1, int filetype, const char *str, int fd, const char *filename) { /* Here we enter the code section where we use the global variables for parsing and code generation (tccpp.c, tccgen.c, -gen.c). @@ -807,8 +807,16 @@ static int tcc_compile(TCCState *s1, int filetype, const char *str, int fd) if (fd == -1) { int len = strlen(str); - tcc_open_bf(s1, "", len); + tcc_open_bf(s1, filename ? filename : "", len); memcpy(file->buffer, str, len); + if (s1->do_debug && filename) { + FILE *fp = fopen(filename, "w"); + + if (fp) { + fputs(str, fp); + fclose(fp); + } + } } else { tcc_open_bf(s1, str, 0); file->fd = fd; @@ -838,7 +846,12 @@ static int tcc_compile(TCCState *s1, int filetype, const char *str, int fd) LIBTCCAPI int tcc_compile_string(TCCState *s, const char *str) { - return tcc_compile(s, s->filetype, str, -1); + return tcc_compile(s, s->filetype, str, -1, NULL); +} + +LIBTCCAPI int tcc_compile_string_file(TCCState *s, const char *str, const char *filename) +{ + return tcc_compile(s, s->filetype, str, -1, filename); } /* define a preprocessor symbol. value can be NULL, sym can be "sym=val" */ @@ -1233,7 +1246,7 @@ ST_FUNC int tcc_add_file_internal(TCCState *s1, const char *filename, int flags) return tcc_add_binary(s1, flags, filename, fd); dynarray_add(&s1->target_deps, &s1->nb_target_deps, tcc_strdup(filename)); - return tcc_compile(s1, flags, filename, fd); + return tcc_compile(s1, flags, filename, fd, NULL); } LIBTCCAPI int tcc_add_file(TCCState *s, const char *filename) diff --git a/libtcc.h b/libtcc.h index 5949c807..11cd96cd 100644 --- a/libtcc.h +++ b/libtcc.h @@ -105,6 +105,18 @@ 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 */ + +/* compile a string containing a C source. Return -1 if error. + Write the string to file filename if debug is set. */ +LIBTCCAPI int tcc_compile_string_file(TCCState *s, const char *buf, const char *filename); + +/* Output object file. This must be done after tcc_relocate. + It only generates the file if debug is set. + The filename can be loaded with gdb command add-symbol-file */ +LIBTCCAPI int elf_output_obj(TCCState *s1, const char *filename); + /* 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 e078b917..4069d349 100644 --- a/tccelf.c +++ b/tccelf.c @@ -3095,10 +3095,13 @@ 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 -2; /* debugging and TCC_OUTPUT_MEMORY and do_debug = 0 */ /* Allocate strings for section names */ alloc_sec_names(s1, 1); file_offset = (sizeof (ElfW(Ehdr)) + 3) & -4; diff --git a/tccrun.c b/tccrun.c index 1fbbf6d2..2a888a64 100644 --- a/tccrun.c +++ b/tccrun.c @@ -120,7 +120,7 @@ static int rt_mem(TCCState *s1, int size) unlink(tmpfname); ftruncate(fd, size); - ptr = mmap(NULL, size * 2, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0); + ptr = mmap(NULL, size * 2, PROT_READ|PROT_EXEC|(s1->do_debug ? PROT_WRITE : 0), MAP_SHARED, fd, 0); /* mmap RW memory at fixed distance */ prw = mmap((char*)ptr + size, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, fd, 0); close(fd); @@ -299,7 +299,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 +311,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 +356,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; diff --git a/tests/libtcc_debug.c b/tests/libtcc_debug.c new file mode 100644 index 00000000..eff6d2fe --- /dev/null +++ b/tests/libtcc_debug.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include "libtcc.h" + +static const char program[] = +"#include \n" +"int fib(int n)\n" +"{\n" +" if (n <= 2)\n" +" return 1;\n" +" else\n" +" return fib(n-1) + fib(n-2);\n" +"}\n" +"int tst(void)\n" +"{\n" +" int i;\n" +" for (i = 2; i < 20; i++)\n" +" printf(\"%d \", fib(i));\n" +" printf(\"\\n\");\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; + } +#if 1 + /* If -g option is not set the debugging files tst.c en tst.o will + not be created. */ + tcc_set_options(s, "-g"); +#endif + tcc_set_error_func(s, stdout, handle_error); + tcc_set_output_type(s, TCC_OUTPUT_MEMORY); + if (tcc_compile_string_file(s, program, "tst.c") == -1) + return 1; + if (tcc_relocate(s) < 0) + return 1; + elf_output_obj(s, "tst.o"); + /* set breakpoint on next line. and load symbol file with + gdb command add-symbol-file. + Then set breakpoint on tst and continue. */ + if ((func = tcc_get_symbol(s, "tst"))) + func(); + tcc_delete(s); + return 0; +}