Add support to debug libtcc code
Some checks are pending
build and test / test-x86_64-linux (push) Waiting to run
build and test / test-x86_64-osx (push) Waiting to run
build and test / test-aarch64-osx (push) Waiting to run
build and test / test-x86_64-win32 (push) Waiting to run
build and test / test-i386-win32 (push) Waiting to run
build and test / test-armv7-linux (push) Waiting to run
build and test / test-aarch64-linux (push) Waiting to run
build and test / test-riscv64-linux (push) Waiting to run

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, "<file>.c")
Then call tcc_relocate().
And finaly write the object file to disk: elf_output_obj(s, "<file>.o")
Now you can start the debugger and put an breakpoint after the
elf_output_obj() code. Then use gdb command add-symbol-file <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
This commit is contained in:
herman ten brugge 2026-01-06 12:11:51 +01:00
parent 8a8388c6ff
commit 1fe3e3bff5
5 changed files with 100 additions and 12 deletions

View File

@ -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, <target>-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, "<string>", len);
tcc_open_bf(s1, filename ? filename : "<string>", 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)

View File

@ -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*);

View File

@ -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;

View File

@ -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;

58
tests/libtcc_debug.c Normal file
View File

@ -0,0 +1,58 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libtcc.h"
static const char program[] =
"#include <stdio.h>\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;
}