For debug purposes, when a program is getting so big that it is becoming out of control, it is useful to catch the stack backtrace of a call to a given service.
This paper presents a method for a C language program in GNU/GLIBC environment.
Hereunder is a source file (write_ovl.c) to overload a Linux system call wrapped into the C library (not all system calls are wrapped on all achitectures). Here we overload write(). The actual symbol address is retrieved thanks to dlsym() service, the function displays the call stack with backtrace() and backtrace_symbols() before calling the actual service.
#define _GNU_SOURCE #include <sys/types.h> #include <unistd.h> #include <dlfcn.h> #include <stdio.h> #include <execinfo.h> #include <stdlib.h> typedef ssize_t (* orig_write_t)(int fd, const void *buf, size_t count); static orig_write_t orig_write; ssize_t write(int fd, const void *buf, size_t count) { void *stack_entries[50]; int n, i; char **syms; // 1st call ? if (!orig_write) { orig_write = dlsym(RTLD_NEXT, "write"); printf("Syscall 'write@%p' is overloaded\n", orig_write); } printf("\n=====> Write called with %d, %p, %zu:\n", fd, buf, count); // Get the callstack n = backtrace(stack_entries, 50); syms = backtrace_symbols(stack_entries, n); if (syms) { for (i = 0; i < n; i ++) { printf("%s\n", syms[i]); } free(syms); } printf("========================\n"); // Call the actual system service return (*orig_write)(fd, buf, count); } |
The example main program (try.c) into which we want to spy the system call is:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <string.h> #define STR "string\n" #define STR2 "string2\n" void service(int fd) { write(fd, STR2, strlen(STR2)); } int main(void) { int fd; fd = open("/tmp/file", O_RDWR|O_CREAT, 0777); write(fd, STR, strlen(STR)); write(fd, STR2, strlen(STR2)); service(fd); close(fd); return 0; } |
The overloading object is built as a shared library. The -rdynamic options instructs the linker to add all symbols, not only used ones, to the dynamic symbol table. This option is needed to obtain backtraces from within a program.
$ gcc try.c -o try -g -rdynamic $ gcc write_ovl.c --shared -fPIC -o write_ovl.so -ldl |
Execution without symbol overloading:
$ ./try $ cat /tmp/file string string2 |
Execution with write overload (we use the LD_PRELOAD environment variable):
$ LD_PRELOAD=./write_ovl.so ./try Syscall 'write@0x7fc582acc210' is overloaded =====> Write called with 3, 0x557683b7b9e7, 7: ./write_ovl.so(write+0xaa) [0x7fc582dad884] ./try(main+0x3c) [0x557683b7b91a] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7fc5829ddbf7] ./try(_start+0x2a) [0x557683b7b7da] ======================== =====> Write called with 3, 0x557683b7b9d4, 8: ./write_ovl.so(write+0xaa) [0x7fc582dad884] ./try(main+0x52) [0x557683b7b930] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7fc5829ddbf7] ./try(_start+0x2a) [0x557683b7b7da] ======================== =====> Write called with 3, 0x557683b7b9d4, 8: ./write_ovl.so(write+0xaa) [0x7fc582dad884] ./try(service+0x21) [0x557683b7b8db] ./try(main+0x5c) [0x557683b7b93a] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7fc5829ddbf7] ./try(_start+0x2a) [0x557683b7b7da] ======================== |
The author is an engineer in computer sciences located in France. He can be contacted here.