From bc6c0c34c166c9c3fba8c9b98c06e26103021f7e Mon Sep 17 00:00:00 2001 From: herman ten brugge Date: Sat, 23 Jan 2021 18:17:38 +0100 Subject: [PATCH] implement test coverage I have implemented the -ftest-coverage option. It works a bit different from the gcc version. It output .tcov text file which looks almost the same as a gcov file after a executable/so file is run. Add lib/tcov.c file Modify Makefiles to compile/install it Add -ftest-coverage option in tcc.c/tcc.h/tcc-doc.texi Add code to tccelf.c/tccgen.c/tccpe.c Add gen_increment_tcov to tcc.h/*gen.c unrelated changes: Add sigemptyset in tccrun.c Fix riscv64-gen.c tok_alloc label size --- Makefile | 5 +- arm-gen.c | 23 +++ arm64-gen.c | 18 +++ i386-gen.c | 13 ++ lib/Makefile | 1 + lib/tcov.c | 391 ++++++++++++++++++++++++++++++++++++++++++++++++++ libtcc.c | 1 + riscv64-gen.c | 33 ++++- tcc-doc.texi | 4 + tcc.c | 1 + tcc.h | 8 ++ tccelf.c | 65 +++++++-- tccgen.c | 159 +++++++++++++++++++- tccpe.c | 4 + tccrun.c | 1 + x86_64-gen.c | 9 ++ 16 files changed, 721 insertions(+), 15 deletions(-) create mode 100644 lib/tcov.c diff --git a/Makefile b/Makefile index 12209697..ed970c59 100644 --- a/Makefile +++ b/Makefile @@ -336,11 +336,12 @@ IR = $(IM) mkdir -p $2 && cp -r $1/. $2 IM = $(info -> $2 : $1)@ B_O = bcheck.o bt-exe.o bt-log.o bt-dll.o +T_O = tcov.o # install progs & libs install-unx: $(call IBw,$(PROGS) $(PROGS_CROSS),"$(bindir)") - $(call IFw,$(LIBTCC1) $(B_O) $(LIBTCC1_U),"$(tccdir)") + $(call IFw,$(LIBTCC1) $(B_O) $(T_O) $(LIBTCC1_U),"$(tccdir)") $(call IF,$(TOPSRC)/include/*.h $(TOPSRC)/tcclib.h,"$(tccdir)/include") $(call $(if $(findstring .so,$(LIBTCC)),IBw,IFw),$(LIBTCC),"$(libdir)") $(call IF,$(TOPSRC)/libtcc.h,"$(includedir)") @@ -365,7 +366,7 @@ uninstall-unx: install-win: $(call IBw,$(PROGS) $(PROGS_CROSS) $(subst libtcc.a,,$(LIBTCC)),"$(bindir)") $(call IF,$(TOPSRC)/win32/lib/*.def,"$(tccdir)/lib") - $(call IFw,libtcc1.a $(B_O) $(LIBTCC1_W),"$(tccdir)/lib") + $(call IFw,libtcc1.a $(B_O) $(T_O) $(LIBTCC1_W),"$(tccdir)/lib") $(call IF,$(TOPSRC)/include/*.h $(TOPSRC)/tcclib.h,"$(tccdir)/include") $(call IR,$(TOPSRC)/win32/include,"$(tccdir)/include") $(call IR,$(TOPSRC)/win32/examples,"$(tccdir)/examples") diff --git a/arm-gen.c b/arm-gen.c index 38901c34..a4dc9d28 100644 --- a/arm-gen.c +++ b/arm-gen.c @@ -2277,6 +2277,29 @@ void gen_cvt_ftof(int t) #endif } +/* increment tcov counter */ +ST_FUNC void gen_increment_tcov (SValue *sv) +{ + int r1, r2; + + vpushv(sv); + vtop->r = r1 = get_reg(RC_INT); + r2 = get_reg(RC_INT); + o(0xE59F0000 | (intr(r1)<<12)); // ldr r1,[pc] + o(0xEA000000); // b $+4 + greloc(cur_text_section, sv->sym, ind, R_ARM_REL32); + o(-12); + o(0xe080000f | (intr(r1)<<16) | (intr(r1)<<12)); // add r1,r1,pc + o(0xe5900000 | (intr(r1)<<16) | (intr(r2)<<12)); // ldr r2, [r1] + o(0xe2900001 | (intr(r2)<<16) | (intr(r2)<<12)); // adds r2, r2, #1 + o(0xe5800000 | (intr(r1)<<16) | (intr(r2)<<12)); // str r2, [r1] + o(0xe2800004 | (intr(r1)<<16) | (intr(r1)<<12)); // add r1, r1, #4 + o(0xe5900000 | (intr(r1)<<16) | (intr(r2)<<12)); // ldr r2, [r1] + o(0xe2a00000 | (intr(r2)<<16) | (intr(r2)<<12)); // adc r2, r2, #0 + o(0xe5800000 | (intr(r1)<<16) | (intr(r2)<<12)); // str r2, [r1] + vpop(); +} + /* computed goto support */ void ggoto(void) { diff --git a/arm64-gen.c b/arm64-gen.c index 2854176b..ca38b3a6 100644 --- a/arm64-gen.c +++ b/arm64-gen.c @@ -1982,6 +1982,24 @@ ST_FUNC void gen_cvt_ftof(int t) } } +/* increment tcov counter */ +ST_FUNC void gen_increment_tcov (SValue *sv) +{ + int r1, r2; + + vpushv(sv); + vtop->r = r1 = get_reg(RC_INT); + r2 = get_reg(RC_INT); + greloca(cur_text_section, sv->sym, ind, R_AARCH64_ADR_GOT_PAGE, 0); + o(0x90000000 | r1); // adrp r1, #sym + greloca(cur_text_section, sv->sym, ind, R_AARCH64_LD64_GOT_LO12_NC, 0); + o(0xf9400000 | r1 | (r1 << 5)); // ld xr,[xr, #sym] + o(0xf9400000 | (intr(r1)<<5) | intr(r2)); // ldr r2, [r1] + o(0x91000400 | (intr(r2)<<5) | intr(r2)); // add r2, r2, #1 + o(0xf9000000 | (intr(r1)<<5) | intr(r2)); // str r2, [r1] + vpop(); +} + ST_FUNC void ggoto(void) { arm64_gen_bl_or_b(1); diff --git a/i386-gen.c b/i386-gen.c index 7098a6e9..2f32944e 100644 --- a/i386-gen.c +++ b/i386-gen.c @@ -1023,6 +1023,19 @@ ST_FUNC void gen_cvt_csti(int t) ); } +/* increment tcov counter */ +ST_FUNC void gen_increment_tcov (SValue *sv) +{ + o(0x0583); /* addl $1, xxx */ + greloc(cur_text_section, sv->sym, ind, R_386_32); + gen_le32(0); + o(1); + o(0x1583); /* addcl $0, xxx */ + greloc(cur_text_section, sv->sym, ind, R_386_32); + gen_le32(4); + g(0); +} + /* computed goto support */ ST_FUNC void ggoto(void) { diff --git a/lib/Makefile b/lib/Makefile index ded721e7..458bdb62 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -64,6 +64,7 @@ OBJ-arm-wince = $(ARM_O) $(WIN_O) OBJ-riscv64 = $(RISCV64_O) $(BCHECK_O) $(DSO_O) OBJ-extra = $(filter $(B_O),$(OBJ-$T)) +OBJ-extra += tcov.o OBJ-libtcc1 = $(addprefix $(X),$(filter-out $(OBJ-extra),$(OBJ-$T))) ALL = $(addprefix $(TOP)/,$(X)libtcc1.a $(OBJ-extra)) diff --git a/lib/tcov.c b/lib/tcov.c new file mode 100644 index 00000000..aa46e14b --- /dev/null +++ b/lib/tcov.c @@ -0,0 +1,391 @@ +#include +#include +#include + +/* section layout (all little endian): + 32bit offset to executable/so file name + filename \0 + function name \0 + align to 64 bits + 64bit function start line + 64bits end_line(28bits) / start_line(28bits) / flag=0xff(8bits) + 64bits counter + \0 + \0 + \0 + executable/so file name \0 + */ + +typedef struct tcov_line { + unsigned int fline; + unsigned int lline; + unsigned long long count; +} tcov_line; + +typedef struct tcov_function { + char *function; + unsigned int first_line; + unsigned int n_line; + unsigned int m_line; + tcov_line *line; +} tcov_function; + +typedef struct tcov_file { + char *filename; + unsigned int n_func; + unsigned int m_func; + tcov_function *func; + struct tcov_file *next; +} tcov_file; + +static unsigned long long get_value(unsigned char *p, int size) +{ + unsigned long long value = 0; + + p += size; + while (size--) + value = (value << 8) | *--p; + return value; +} + +static int sort_func (const void *p, const void *q) +{ + const tcov_function *pp = (const tcov_function *) p; + const tcov_function *pq = (const tcov_function *) q; + + return pp->first_line > pq->first_line ? 1 : + pp->first_line < pq->first_line ? -1 : 0; +} + +static int sort_line (const void *p, const void *q) +{ + const tcov_line *pp = (const tcov_line *) p; + const tcov_line *pq = (const tcov_line *) q; + + return pp->fline > pq->fline ? 1 : + pp->fline < pq->fline ? -1 : + pp->count < pq->count ? 1 : + pp->count > pq->count ? -1 : 0; +} + +/* sort to let inline functions work */ +static tcov_file *sort_test_coverage (unsigned char *p) +{ + int i, j, k; + tcov_file *file = NULL; + tcov_file *nfile; + + p += 4; + while (*p) { + char *filename = (char *)p; + size_t len = strlen (filename); + + nfile = file; + while (nfile) { + if (strcmp (nfile->filename, filename) == 0) + break; + nfile = nfile->next; + } + if (nfile == NULL) { + nfile = malloc (sizeof(tcov_file)); + if (nfile == NULL) { + fprintf (stderr, "Malloc error test_coverage\n"); + return file; + } + nfile->filename = filename; + nfile->n_func = 0; + nfile->m_func = 0; + nfile->func = NULL; + nfile->next = NULL; + if (file == NULL) + file = nfile; + else { + tcov_file *lfile = file; + + while (lfile->next) + lfile = lfile->next; + lfile->next = nfile; + } + } + p += len + 1; + while (*p) { + int i; + char *function = (char *)p; + tcov_function *func; + + p += strlen (function) + 1; + p += -(size_t)p & 7; + for (i = 0; i < nfile->n_func; i++) { + func = &nfile->func[i]; + if (strcmp (func->function, function) == 0) + break; + } + if (i == nfile->n_func) { + if (nfile->n_func >= nfile->m_func) { + nfile->m_func = nfile->m_func == 0 ? 4 : nfile->m_func * 2; + nfile->func = realloc (nfile->func, + nfile->m_func * + sizeof (tcov_function)); + if (nfile->func == NULL) { + fprintf (stderr, "Realloc error test_coverage\n"); + return file; + } + } + func = &nfile->func[nfile->n_func++]; + func->function = function; + func->first_line = get_value (p, 8); + func->n_line = 0; + func->m_line = 0; + func->line = NULL; + } + p += 8; + while (*p) { + tcov_line *line; + unsigned long long val; + + if (func->n_line >= func->m_line) { + func->m_line = func->m_line == 0 ? 4 : func->m_line * 2; + func->line = realloc (func->line, + func->m_line * sizeof (tcov_line)); + if (func->line == NULL) { + fprintf (stderr, "Realloc error test_coverage\n"); + return file; + } + } + line = &func->line[func->n_line++]; + val = get_value (p, 8); + line->fline = (val >> 8) & 0xfffffffULL; + line->lline = val >> 36; + line->count = get_value (p + 8, 8); + p += 16; + } + p++; + } + p++; + } + nfile = file; + while (nfile) { + qsort (nfile->func, nfile->n_func, sizeof (tcov_function), sort_func); + for (i = 0; i < nfile->n_func; i++) { + tcov_function *func = &nfile->func[i]; + qsort (func->line, func->n_line, sizeof (tcov_line), sort_line); + } + nfile = nfile->next; + } + return file; +} + +/* merge with previous tcov file */ +static void merge_test_coverage (tcov_file *file, char *cov_filename, + unsigned int *pruns) +{ + unsigned int runs; + FILE *fp = fopen (cov_filename, "r"); + char *p; + char str[10000]; + + *pruns = 1; + if (fp == NULL) + return; + if (fgets(str, sizeof(str), fp) && + (p = strrchr (str, ':')) && + (sscanf (p + 1, "%u", &runs) == 1)) + *pruns = runs + 1; + while (file) { + int i; + size_t len = strlen (file->filename); + + while (fgets(str, sizeof(str), fp) && + (p = strstr(str, "0:File:")) == NULL); + if ((p = strstr(str, "0:File:")) == NULL || + strncmp (p + strlen("0:File:"), file->filename, len) != 0 || + p[strlen("0:File:") + len] != ' ') + break; + for (i = 0; i < file->n_func; i++) { + int j; + tcov_function *func = &file->func[i]; + unsigned int next_zero = 0; + unsigned int curline = 0; + + for (j = 0; j < func->n_line; j++) { + tcov_line *line = &func->line[j]; + unsigned int fline = line->fline; + unsigned long long count; + unsigned int tmp; + char c; + + while (curline < fline && + fgets(str, sizeof(str), fp)) + if ((p = strchr(str, ':')) && + sscanf (p + 1, "%u", &tmp) == 1) + curline = tmp; + if (sscanf (str, "%llu%c\n", &count, &c) == 2) { + if (next_zero == 0) + line->count += count; + next_zero = c == '*'; + } + } + } + file = file->next; + } + fclose (fp); +} + +/* store tcov data in file */ +void __store_test_coverage (unsigned char * p) +{ + int i, j; + unsigned int files; + unsigned int funcs; + unsigned int blocks; + unsigned int blocks_run; + unsigned int runs; + char *cov_filename = (char *)p + get_value (p, 4); + FILE *fp; + char *q; + tcov_file *file; + tcov_file *nfile; + tcov_function *func; + + file = sort_test_coverage (p); + merge_test_coverage (file, cov_filename, &runs); + fp = fopen (cov_filename, "w"); + if (fp == NULL) { + fprintf (stderr, "Cannot create coverage file: %s\n", cov_filename); + return; + } + fprintf (fp, " -: 0:Runs:%u\n", runs); + files = 0; + funcs = 0; + blocks = 0; + blocks_run = 0; + nfile = file; + while (nfile) { + files++; + for (i = 0; i < nfile->n_func; i++) { + func = &nfile->func[i]; + funcs++; + for (j = 0; j < func->n_line; j++) { + blocks++; + blocks_run += func->line[j].count != 0; + } + } + nfile = nfile->next; + } + if (blocks == 0) + blocks = 1; + fprintf (fp, " -: 0:All:%s Files:%u Functions:%u %.02f%%\n", + cov_filename, files, funcs, 100.0 * (double) blocks_run / blocks); + nfile = file; + while (nfile) { + FILE *src = fopen (nfile->filename, "r"); + unsigned int curline = 1; + char str[10000]; + + if (src == NULL) + goto next; + funcs = 0; + blocks = 0; + blocks_run = 0; + for (i = 0; i < nfile->n_func; i++) { + func = &nfile->func[i]; + funcs++; + for (j = 0; j < func->n_line; j++) { + blocks++; + blocks_run += func->line[j].count != 0; + } + } + if (blocks == 0) + blocks = 1; + fprintf (fp, " -: 0:File:%s Functions:%u %.02f%%\n", + nfile->filename, funcs, 100.0 * (double) blocks_run / blocks); + for (i = 0; i < nfile->n_func; i++) { + func = &nfile->func[i]; + + while (curline < func->first_line) + if (fgets(str, sizeof(str), src)) + fprintf (fp, " -:%5u:%s", curline++, str); + blocks = 0; + blocks_run = 0; + for (j = 0; j < func->n_line; j++) { + blocks++; + blocks_run += func->line[j].count != 0; + } + if (blocks == 0) + blocks = 1; + fprintf (fp, " -: 0:Function:%s %.02f%%\n", + func->function, 100.0 * (double) blocks_run / blocks); +#if 0 + for (j = 0; j < func->n_line; j++) { + unsigned int fline = func->line[j].fline; + unsigned int lline = func->line[j].lline; + unsigned long long count = func->line[j].count; + + fprintf (fp, "%u %u %llu\n", fline, lline, count); + } +#endif + for (j = 0; j < func->n_line;) { + unsigned int fline = func->line[j].fline; + unsigned int lline = func->line[j].lline; + unsigned long long count = func->line[j].count; + unsigned int has_zero = 0; + unsigned int same_line = fline == lline; + + j++; + while (j < func->n_line) { + unsigned int nfline = func->line[j].fline; + unsigned int nlline = func->line[j].lline; + unsigned long long ncount = func->line[j].count; + + if (fline == nfline) { + if (ncount == 0) + has_zero = 1; + else if (ncount > count) + count = ncount; + same_line = nfline == nlline; + lline = nlline; + j++; + } + else { + if (same_line) + lline++; + break; + } + } + + while (curline < fline) + if (fgets(str, sizeof(str), src)) + fprintf (fp, " -:%5u:%s", curline++, str); + while (curline < lline && + fgets(str, sizeof(str), src)) { + if (count == 0) + fprintf (fp, " #####:%5u:%s", + curline, str); + else if (has_zero) + fprintf (fp, "%8llu*:%5u:%s", + count, curline, str); + else + fprintf (fp, "%9llu:%5u:%s", + count, curline, str); + curline++; + } + } + } + while (fgets(str, sizeof(str), src)) + fprintf (fp, " -:%5u:%s", curline++, str); + fclose (src); +next: + nfile = nfile->next; + } + while (file) { + for (i = 0; i < file->n_func; i++) { + func = &file->func[i]; + free (func->line); + } + free (file->func); + nfile = file; + file = file->next; + free (nfile); + } + fclose (fp); +} diff --git a/libtcc.c b/libtcc.c index 7723b85e..84392be7 100644 --- a/libtcc.c +++ b/libtcc.c @@ -1645,6 +1645,7 @@ static const FlagDef options_f[] = { { offsetof(TCCState, leading_underscore), 0, "leading-underscore" }, { offsetof(TCCState, ms_extensions), 0, "ms-extensions" }, { offsetof(TCCState, dollars_in_identifiers), 0, "dollars-in-identifiers" }, + { offsetof(TCCState, test_coverage), 0, "test-coverage" }, { 0, 0, NULL } }; diff --git a/riscv64-gen.c b/riscv64-gen.c index 8dd67f1c..bef9d76e 100644 --- a/riscv64-gen.c +++ b/riscv64-gen.c @@ -476,7 +476,7 @@ static void gen_bounds_epilog(void) func_bound_offset, lbounds_section->data_offset); if (!label.v) { - label.v = tok_alloc(".LB0 ", 4)->tok; + label.v = tok_alloc(".LB0 ", 5)->tok; label.type.t = VT_VOID | VT_STATIC; } /* generate bound local allocation */ @@ -1348,6 +1348,37 @@ ST_FUNC void gen_cvt_ftof(int dt) } } +/* increment tcov counter */ +ST_FUNC void gen_increment_tcov (SValue *sv) +{ + int r1, r2; + static Sym label; + + if (!label.v) { + label.v = tok_alloc(".T0 ", 4)->tok; + label.type.t = VT_VOID | VT_STATIC; + } + vpushv(sv); + vtop->r = r1 = get_reg(RC_INT); + r2 = get_reg(RC_INT); + r1 = ireg(r1); + r2 = ireg(r2); + greloca(cur_text_section, sv->sym, ind, R_RISCV_PCREL_HI20, 0); + label.c = 0; /* force new local ELF symbol */ + put_extern_sym(&label, cur_text_section, ind, 0); + o(0x17 | (r1 << 7)); // auipc RR, 0 %pcrel_hi(sym) + greloca(cur_text_section, &label, ind, R_RISCV_PCREL_LO12_I, 0); + EI(0x03, 3, r2, r1, 0); // ld r2, x[r1] + EI(0x13, 0, r2, r2, 1); // addi r2, r2, #1 + greloca(cur_text_section, sv->sym, ind, R_RISCV_PCREL_HI20, 0); + label.c = 0; /* force new local ELF symbol */ + put_extern_sym(&label, cur_text_section, ind, 0); + o(0x17 | (r1 << 7)); // auipc RR, 0 %pcrel_hi(sym) + greloca(cur_text_section, &label, ind, R_RISCV_PCREL_LO12_S, 0); + ES(0x23, 3, r1, r2, 0); // sd r2, [r1] + vpop(); +} + ST_FUNC void ggoto(void) { gcall_or_jmp(0); diff --git a/tcc-doc.texi b/tcc-doc.texi index 1c66c013..97b050b6 100644 --- a/tcc-doc.texi +++ b/tcc-doc.texi @@ -238,6 +238,10 @@ behaves like an unnamed one. @item -fdollars-in-identifiers Allow dollar signs in identifiers +@item -ftest-coverage +Create code coverage code. After running the resulting code an executable.tcov +or sofile.tcov file is generated with code coverage. + @end table Warning options: diff --git a/tcc.c b/tcc.c index 564b6fb1..bd9ce2fa 100644 --- a/tcc.c +++ b/tcc.c @@ -111,6 +111,7 @@ static const char help2[] = " leading-underscore decorate extern symbols\n" " ms-extensions allow anonymous struct in struct\n" " dollars-in-identifiers allow '$' in C symbols\n" + " test-coverage create code coverage code\n" "-m... target specific options:\n" " ms-bitfields use MSVC bitfield layout\n" #ifdef TCC_TARGET_ARM diff --git a/tcc.h b/tcc.h index afe696b7..da34abfe 100644 --- a/tcc.h +++ b/tcc.h @@ -764,6 +764,7 @@ struct TCCState { unsigned char leading_underscore; unsigned char ms_extensions; /* allow nested named struct w/o identifier behave like unnamed */ unsigned char dollars_in_identifiers; /* allows '$' char in identifiers */ + unsigned char test_coverage; /* generate test coverage code */ unsigned char ms_bitfields; /* if true, emulate MS algorithm for aligning bitfields */ /* warning switches */ @@ -894,6 +895,8 @@ struct TCCState { Section *bounds_section; /* contains global data bound description */ Section *lbounds_section; /* contains local data bound description */ #endif + /* test coverage */ + Section *tcov_section; /* symbol sections */ Section *symtab_section; /* debug sections */ @@ -1700,6 +1703,7 @@ ST_FUNC void gen_le32(int c); ST_FUNC void gen_addr32(int r, Sym *sym, int c); ST_FUNC void gen_addrpc32(int r, Sym *sym, int c); ST_FUNC void gen_cvt_csti(int t); +ST_FUNC void gen_increment_tcov (SValue *sv); #endif /* ------------ x86_64-gen.c ------------ */ @@ -1719,6 +1723,7 @@ ST_FUNC void gen_cvt_csti(int t); PUB_FUNC const char *default_elfinterp(struct TCCState *s); #endif ST_FUNC void arm_init(struct TCCState *s); +ST_FUNC void gen_increment_tcov (SValue *sv); #endif /* ------------ arm64-gen.c ------------ */ @@ -1730,6 +1735,7 @@ ST_FUNC void gen_va_arg(CType *t); ST_FUNC void gen_clear_cache(void); ST_FUNC void gen_cvt_sxtw(void); ST_FUNC void gen_cvt_csti(int t); +ST_FUNC void gen_increment_tcov (SValue *sv); #endif /* ------------ riscv64-gen.c ------------ */ @@ -1739,6 +1745,7 @@ ST_FUNC void gen_opl(int op); ST_FUNC void gen_va_start(void); ST_FUNC void arch_transfer_ret_regs(int); ST_FUNC void gen_cvt_sxtw(void); +ST_FUNC void gen_increment_tcov (SValue *sv); #endif /* ------------ c67-gen.c ------------ */ @@ -1841,6 +1848,7 @@ ST_FUNC void gen_makedeps(TCCState *s, const char *target, const char *filename) #define cur_text_section TCC_STATE_VAR(cur_text_section) #define bounds_section TCC_STATE_VAR(bounds_section) #define lbounds_section TCC_STATE_VAR(lbounds_section) +#define tcov_section TCC_STATE_VAR(tcov_section) #define symtab_section TCC_STATE_VAR(symtab_section) #define stab_section TCC_STATE_VAR(stab_section) #define stabstr_section stab_section->link diff --git a/tccelf.c b/tccelf.c index 0371ae59..4c574391 100644 --- a/tccelf.c +++ b/tccelf.c @@ -1354,16 +1354,6 @@ ST_FUNC void tcc_add_bcheck(TCCState *s1) } #endif -#ifdef CONFIG_TCC_BACKTRACE -static void put_ptr(TCCState *s1, Section *s, int offs) -{ - int c; - c = set_global_sym(s1, NULL, s, offs); - s = data_section; - put_elf_reloc (s1->symtab, s, s->data_offset, R_DATA_PTR, c); - section_ptr_add(s, PTR_SIZE); -} - /* set symbol to STB_LOCAL and resolve. The point is to not export it as a dynamic symbol to allow so's to have one each with a different value. */ static void set_local_sym(TCCState *s1, const char *name, Section *s, int offset) @@ -1377,6 +1367,16 @@ static void set_local_sym(TCCState *s1, const char *name, Section *s, int offset } } +#ifdef CONFIG_TCC_BACKTRACE +static void put_ptr(TCCState *s1, Section *s, int offs) +{ + int c; + c = set_global_sym(s1, NULL, s, offs); + s = data_section; + put_elf_reloc (s1->symtab, s, s->data_offset, R_DATA_PTR, c); + section_ptr_add(s, PTR_SIZE); +} + ST_FUNC void tcc_add_btstub(TCCState *s1) { Section *s; @@ -1427,6 +1427,45 @@ ST_FUNC void tcc_add_btstub(TCCState *s1) } #endif +static void tcc_tcov_add_file(TCCState *s1, const char *filename) +{ + CString cstr; + void *ptr; + char wd[1024]; + + if (tcov_section == NULL) + return; + section_ptr_add(tcov_section, 1); + write32le (tcov_section->data, tcov_section->data_offset); + + getcwd (wd, sizeof(wd)); + cstr_new (&cstr); + cstr_printf (&cstr, "%s/%s.tcov", wd, filename); + ptr = section_ptr_add(tcov_section, cstr.size + 1); + strncpy((char *)ptr, cstr.data, cstr.size); + unlink((char *)ptr); +#ifdef _WIN32 + normalize_slashes((char *)ptr); +#endif + cstr_free (&cstr); +} + +static void tcc_add_tcov(TCCState *s1) +{ + CString cstr; + + cstr_new(&cstr); + cstr_printf(&cstr, + "extern char *__tcov_data[];" + "extern void __store_test_coverage ();" + "__attribute__((destructor)) static void __tcov_exit() {" + "__store_test_coverage(__tcov_data);" + "}"); + tcc_compile_string(s1, cstr.data); + cstr_free(&cstr); + set_local_sym(s1, &"___tcov_data"[!s1->leading_underscore], tcov_section, 0); +} + #ifndef TCC_TARGET_PE /* add tcc runtime libraries */ ST_FUNC void tcc_add_runtime(TCCState *s1) @@ -1440,6 +1479,8 @@ ST_FUNC void tcc_add_runtime(TCCState *s1) if (!s1->nostdlib) { if (s1->option_pthread) tcc_add_library_err(s1, "pthread"); + if (s1->test_coverage) + tcc_add_support(s1, "tcov.o"); tcc_add_library_err(s1, "c"); #ifdef TCC_LIBGCC if (!s1->static_link) { @@ -1473,6 +1514,8 @@ ST_FUNC void tcc_add_runtime(TCCState *s1) tcc_add_btstub(s1); } #endif + if (s1->test_coverage) + tcc_add_tcov(s1); if (strlen(TCC_LIBTCC1) > 0) tcc_add_support(s1, TCC_LIBTCC1); #if TARGETOS_OpenBSD || TARGETOS_FreeBSD || TARGETOS_NetBSD @@ -2772,6 +2815,8 @@ static int elf_output_obj(TCCState *s1, const char *filename) LIBTCCAPI int tcc_output_file(TCCState *s, const char *filename) { + if (s->test_coverage) + tcc_tcov_add_file(s, filename); if (s->output_type == TCC_OUTPUT_OBJ) return elf_output_obj(s, filename); #ifdef TCC_TARGET_PE diff --git a/tccgen.c b/tccgen.c index 5f83bdc3..29e21669 100644 --- a/tccgen.c +++ b/tccgen.c @@ -51,6 +51,10 @@ ST_DATA SValue *vtop; static SValue _vstack[1 + VSTACK_SIZE]; #define vstack (_vstack + 1) +static void tcc_tcov_block_begin(void); +static void tcc_tcov_block_end(int line); +static void tcc_tcov_check_line(int start); + ST_DATA int const_wanted; /* true if constant wanted */ ST_DATA int nocode_wanted; /* no code generation wanted */ #define unevalmask 0xffff /* unevaluated subexpression */ @@ -63,7 +67,7 @@ ST_DATA int nocode_wanted; /* no code generation wanted */ /* Clear 'nocode_wanted' at label if it was used */ ST_FUNC void gsym(int t) { if (t) { gsym_addr(t, ind); CODE_ON(); }} -static int gind(void) { CODE_ON(); return ind; } +static int gind(void) { int t; CODE_ON(); t = ind; tcc_tcov_block_begin(); return t; } /* Set 'nocode_wanted' after unconditional jumps */ static void gjmp_addr_acs(int t) { gjmp_addr(t); CODE_OFF(); } @@ -203,6 +207,16 @@ static struct debug_info { struct debug_info *child, *next, *last, *parent; } *debug_info, *debug_info_root; +static struct { + unsigned long offset; + unsigned long last_offset; + unsigned long last_file_name; + unsigned long last_func_name; + int ind; + int line; + Sym label; +} tcov_data; + /********************************************************/ #if 1 #define precedence_parser @@ -700,6 +714,136 @@ ST_FUNC void tcc_debug_end(TCCState *s1) tcc_free(debug_hash); } +/* for section layout see lib/tcov.c */ +static void tcc_tcov_block_begin(void) +{ + SValue sv; + void *ptr; + + tcc_tcov_block_end (0); + if (tcc_state->test_coverage == 0 || nocode_wanted) + return; + + if (tcov_data.last_file_name == 0 || + strcmp ((const char *)(tcov_section->data + tcov_data.last_file_name), + file->true_filename) != 0) { + char wd[1024]; + CString cstr; + + if (tcov_data.last_func_name) + section_ptr_add(tcov_section, 1); + if (tcov_data.last_file_name) + section_ptr_add(tcov_section, 1); + getcwd (wd, sizeof(wd)); + tcov_data.last_file_name = tcov_section->data_offset + strlen(wd) + 1; + tcov_data.last_func_name = 0; + cstr_new (&cstr); + cstr_printf (&cstr, "%s/%s", wd, file->true_filename); + ptr = section_ptr_add(tcov_section, cstr.size + 1); + strncpy((char *)ptr, cstr.data, cstr.size); +#ifdef _WIN32 + normalize_slashes((char *)ptr); +#endif + cstr_free (&cstr); + } + if (tcov_data.last_func_name == 0 || + strcmp ((const char *)(tcov_section->data + tcov_data.last_func_name), + funcname) != 0) { + size_t len; + + if (tcov_data.last_func_name) + section_ptr_add(tcov_section, 1); + tcov_data.last_func_name = tcov_section->data_offset; + len = strlen (funcname); + ptr = section_ptr_add(tcov_section, len + 1); + strncpy((char *)ptr, funcname, len); + section_ptr_add(tcov_section, -tcov_section->data_offset & 7); + ptr = section_ptr_add(tcov_section, 8); + write64le (ptr, file->line_num); + } + if (ind == tcov_data.ind && tcov_data.line == file->line_num) + tcov_data.offset = tcov_data.last_offset; + else { + if (!tcov_data.label.v) { + tcov_data.label.v = tok_alloc(".TCOV ", 6)->tok; + tcov_data.label.type.t = VT_LLONG | VT_STATIC; + } + tcov_data.label.c = 0; /* force new local ELF symbol */ + ptr = section_ptr_add(tcov_section, 16); + tcov_data.line = file->line_num; + write64le (ptr, (tcov_data.line << 8) | 0xff); + put_extern_sym(&tcov_data.label, tcov_section, + ((unsigned char *)ptr - tcov_section->data) + 8, 0); + sv.type = tcov_data.label.type; + sv.r = VT_SYM | VT_LVAL | VT_CONST; + sv.r2 = VT_CONST; + sv.c.i = 0; + sv.sym = &tcov_data.label; +#if defined TCC_TARGET_I386 || defined TCC_TARGET_X86_64 || \ + defined TCC_TARGET_ARM || defined TCC_TARGET_ARM64 || \ + defined TCC_TARGET_RISCV64 + gen_increment_tcov (&sv); +#else + vpushv(&sv); + inc(0, TOK_INC); + vpop(); +#endif + tcov_data.offset = (unsigned char *)ptr - tcov_section->data; + tcov_data.ind = ind; + } +} + +static void tcc_tcov_block_end(int line) +{ + if (tcc_state->test_coverage == 0) + return; + if (tcov_data.offset) { + void *ptr = tcov_section->data + tcov_data.offset; + unsigned long long nline = line ? line : file->line_num; + + write64le (ptr, (read64le (ptr) & 0xfffffffffull) | (nline << 36)); + tcov_data.last_offset = tcov_data.offset; + tcov_data.offset = 0; + } +} + +static void tcc_tcov_check_line(int start) +{ + if (tcc_state->test_coverage == 0) + return; + if (tcov_data.line != file->line_num) { + if ((tcov_data.line + 1) != file->line_num) { + tcc_tcov_block_end (tcov_data.line); + if (start) + tcc_tcov_block_begin (); + } + else + tcov_data.line = file->line_num; + } +} + +static void tcc_tcov_start(void) +{ + if (tcc_state->test_coverage == 0) + return; + memset (&tcov_data, 0, sizeof (tcov_data)); + if (tcov_section == NULL) { + tcov_section = new_section(tcc_state, ".tcov", SHT_PROGBITS, + SHF_ALLOC | SHF_WRITE); + section_ptr_add(tcov_section, 4); // pointer to executable name + } +} + +static void tcc_tcov_end(void) +{ + if (tcc_state->test_coverage == 0) + return; + if (tcov_data.last_func_name) + section_ptr_add(tcov_section, 1); + if (tcov_data.last_file_name) + section_ptr_add(tcov_section, 1); +} + static BufferedFile* put_new_file(TCCState *s1) { BufferedFile *f = file; @@ -825,6 +969,7 @@ ST_FUNC int tccgen_compile(TCCState *s1) local_scope = 0; tcc_debug_start(s1); + tcc_tcov_start (); #ifdef TCC_TARGET_ARM arm_init(s1); #endif @@ -838,6 +983,7 @@ ST_FUNC int tccgen_compile(TCCState *s1) check_vstack(); /* end of translation unit info */ tcc_debug_end(s1); + tcc_tcov_end (); return 0; } @@ -5548,6 +5694,7 @@ ST_FUNC void unary(void) /* generate line number info */ if (tcc_state->do_debug) tcc_debug_line(tcc_state); + tcc_tcov_check_line (1); sizeof_caller = in_sizeof; in_sizeof = 0; @@ -6267,8 +6414,10 @@ special_math_val: #endif } } - if (s->f.func_noreturn) + if (s->f.func_noreturn) { + tcc_tcov_block_end (tcov_data.line); CODE_OFF(); + } } else { break; } @@ -7029,6 +7178,8 @@ again: goto expr; next(); + tcc_tcov_check_line (0); + tcc_tcov_block_begin (); if (t == TOK_IF) { skip('('); gexpr(); @@ -7109,6 +7260,7 @@ again: /* jump unless last stmt in top-level block */ if (tok != '}' || local_scope != 1) rsym = gjmp(rsym); + tcc_tcov_block_end (tcov_data.line); CODE_OFF(); } else if (t == TOK_BREAK) { @@ -7342,6 +7494,8 @@ again: } } } + tcc_tcov_check_line (0); + tcc_tcov_block_end (0); } /* This skips over a stream of tokens containing balanced {} and () @@ -7819,6 +7973,7 @@ static void decl_initializer(init_params *p, CType *type, unsigned long c, int f /* generate line number info */ if (!p->sec && tcc_state->do_debug) tcc_debug_line(tcc_state); + tcc_tcov_check_line (1); if (!(flags & DIF_HAVE_ELEM) && tok != '{' && /* In case of strings we have special handling for arrays, so diff --git a/tccpe.c b/tccpe.c index 3f5965fd..0df09919 100644 --- a/tccpe.c +++ b/tccpe.c @@ -1915,6 +1915,10 @@ static void pe_add_runtime(TCCState *s1, struct pe_info *pe) tcc_add_btstub(s1); } #endif + if (s1->test_coverage) { + tcc_add_support(s1, "tcov.o"); + tcc_add_tcov(s1); + } /* grab the startup code from libtcc1.a */ #ifdef TCC_IS_NATIVE diff --git a/tccrun.c b/tccrun.c index 55b1cc46..954f312f 100644 --- a/tccrun.c +++ b/tccrun.c @@ -737,6 +737,7 @@ static void set_exception_handler(void) struct sigaction sigact; /* install TCC signal handlers to print debug info on fatal runtime errors */ + sigemptyset (&sigact.sa_mask); sigact.sa_flags = SA_SIGINFO | SA_RESETHAND; #if 0//def SIGSTKSZ // this causes signals not to work at all on some (older) linuxes sigact.sa_flags |= SA_ONSTACK; diff --git a/x86_64-gen.c b/x86_64-gen.c index a8eef52a..271f3d23 100644 --- a/x86_64-gen.c +++ b/x86_64-gen.c @@ -2200,6 +2200,15 @@ ST_FUNC void gen_cvt_csti(int t) ); } +/* increment tcov counter */ +ST_FUNC void gen_increment_tcov (SValue *sv) +{ + o(0x058348); /* addq $1, xxx(%rip) */ + greloca(cur_text_section, sv->sym, ind, R_X86_64_PC32, -5); + gen_le32(0); + o(1); +} + /* computed goto support */ void ggoto(void) {