3
nptrs = backtrace(buffer, SIZE);

Standard backtrace function does not work under Tiny C, (it only returns a single address). How do you get a current stack trace in a Tiny C compiled program?

Update:

I tried manual stack walk like this found on git hub, again it only works on GCC but not under Tiny:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#define CALL_OFFSET 5
#define RELATIVE_ADDR_OFFSET 4
#define CALL_CMD 0xe8

extern uint8_t _start;
extern uint8_t _etext;
extern void *__libc_stack_end;

typedef uint8_t * pointer;

void stack_show() {
    uint8_t shift = 1;
    uint8_t stack_top = 0;
    pointer ptr = &stack_top;
    while((ptr + 3) <= (pointer)__libc_stack_end) {
        uint32_t * lbs = (uint32_t *)ptr;
        uint32_t * mbs = (uint32_t *)(ptr+3);
        uint64_t addr = ((*(mbs))<<16) | *lbs;
        if(addr > CALL_OFFSET &&
            (addr - CALL_OFFSET) >= (uint64_t)&_start &&
                addr < (uint64_t)&_etext) {
            if(*(pointer)(addr - CALL_OFFSET) == CALL_CMD) {
                uint64_t fun_addr = *(int*)(addr - RELATIVE_ADDR_OFFSET) + *(int*)ptr;
                if(fun_addr >= (uint64_t)&_start && fun_addr < (uint64_t)&_etext)
                    printf("%016llx\n", fun_addr);
            }
        }
        ptr += shift;
    }
    return;
}
exebook
  • 32,014
  • 33
  • 141
  • 226

1 Answers1

3

Only few hours of mindwrapping and you get the backtrace() that is compatible with TinyC (and surprisingly it also works with GCC for some reason, so even that #define __TINYC__ is not necessary).

The tricks here are to use inline assembly to get the base pointer and extern __libc_stack_end gives you the beginning of the stack (despite tha name, remember the stack grows downwards).

Notice that to get symbol names you need -rdynamic from GCC (ut not needed for tcc for some reason) and also symbols are not available if you use -run option of TinyC or run it embedded from RAM.

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <stdint.h>

#ifdef __TINYC__
int backtrace(void **buffer, int size) {
    extern uint64_t *__libc_stack_end;
    uint64_t **p, *bp, *frame;
    asm ("mov %%rbp, %0;" : "=r" (bp));
    p = (uint64_t**) bp;
    int i = 0;
    while (i < size) {
        frame = p[0];
        if (frame < bp || frame > __libc_stack_end) {
            return i;
        }
        buffer[i++] = p[1];
        p = (uint64_t**) frame;
    }
    return i;
}
#endif

    // Below is a demonstration of use, note that backtrace_symbols() is compatible 
    // with our backtrace replacement.

void show() {
    void *buffer[10];
    int size = backtrace(buffer, 10);
    char **strings = backtrace_symbols(buffer, size);
    if (strings == NULL) {
        perror("backtrace_symbols");
        exit(EXIT_FAILURE);
    }
    for (int j = 0; j < size; j++) {
        printf("%s\n", strings[j]);
    }
    free(strings);
}

void d() {
    show(); // show current back trace here
}
int c(uint64_t a, uint64_t b) {
    d();
    return a + b;
}
void b(int x, int y, int z, int zz) {
    c(100, 200);
}
void a() {
    b(1,2,3,4);
}

int main(){
    a();
    return 0;
}
exebook
  • 32,014
  • 33
  • 141
  • 226
  • 1
    You are a king and a saint. I think we are the only two people who have ever had this problem. Thank you dearly for your answer. One caveat. This code seems to lose the function you're on if you get a signal. Which is quite annoying because it means you don't _really_ know where the problem occurred. You can solve this by examining both p[1] and p[2] in the above code. – Charles Lohr Sep 04 '19 at 09:00
  • After digging around a while, it seems that part of the stack gets damaged, but if you are writing a crash handler, you can read the oldmask field of the context and get the actual line of code where the crash was. – Charles Lohr Sep 04 '19 at 09:36