When I use strace to examine a program, I often have a hard time finding where the syscalls from the dynamic loader end and the syscalls from the program begin.

The output from strace ./hello where hello a simple hello world C program is 36 lines. Here's a sample:

execve("./hello", ["./hello"], 0x7fffb38f4a30 /* 73 vars */) = 0
brk(NULL)                               = 0x1304000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffe6715fe60) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=92340, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 92340, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f78d9fbd000
close(3)                                = 0
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260|\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
pread64(3, "\4\0\0\0 \0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0"..., 48, 848) = 48

Is there a way to ignore the dynamic loader syscalls?

  • openat returns a file descriptor, which the linker calls use. You could filter that (with some work...) using a script. Commented Sep 12, 2021 at 17:43

1 Answer 1


On x86_64 the main program starts just after arch_prctl(ARCH_SET_FS) and a couple of mprotect()s, so you can sed 1,/ARCH_SET_FS/d on the strace's output.

A trick you can use on all platforms is to LD_PRELOAD a small library which overides __libc_start_main() and does a pointless system call like write(-1, "IT_STARTS_HERE", 14) before calling the original __libc_start_main().

cat >hack.c <<'EOT'
#define _GNU_SOURCE
#include <dlfcn.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
int __libc_start_main(
        int (*main)(int,char**,char**), int ac, char **av,
        int (*init)(int,char**,char**), void (*fini)(void),
        void (*rtld_fini)(void), void *stack_end)
        typeof(__libc_start_main) *next = dlsym(RTLD_NEXT, "__libc_start_main");
        write(-1, "IT_STARTS_HERE", 14); errno = 0;
        return next(main, ac, av, init, fini, rtld_fini, stack_end);
cc -shared -ldl hack.c -o hack.so

hack_strace(){ strace -E LD_PRELOAD=./hack.so "$@" 2>&1 >&3 3>&- | sed 1,/IT_STARTS_HERE/d >&2; } 3>&1

# usage
hack_strace sh -c 'echo LOL'
getuid()                                = 2000
getgid()                                = 2000
getpid()                                = 11443
rt_sigaction(SIGCHLD, {sa_handler=0x55eba5c19380, sa_mask=~[RTMIN RT_1], sa_flags=SA_RESTORER, sa_restorer=0x7fae5c55f840}, NULL, 8) = 0
geteuid()                               = 2000
  • 2
    It is remotely possible this could break a program. If it does, add errno=0 after the write call. If you think that's absurd, I saw a weirder dependency. I have a program pair within my archive that depends on esi and edi surviving across an execve() call.
    – Joshua
    Commented Sep 13, 2021 at 1:35
  • @Joshua: The latter is not valid, but the former is. C specifies that errno is 0 on entry to main. FWIW it may not be safe to call libc (even write, syscall, or dlsym to get the real address) before __libc_start_main so this approach seems dubious. Commented Sep 13, 2021 at 14:09
  • @R..GitHubSTOPHELPINGICE You're right that overriding __libc_start_main is theoretically risky, although it tends to work in practice (at least with glibc on the most common platforms). The safe solution would likely be to preload a library that provides a global constructor which would do the dummy syscall, but then other user code in global constructors could execute before that mark. (In general, global ctors might be a mix of library and user code, so it's a bit of a grey area, perhaps you want them in, perhaps you don't.)
    – TooTea
    Commented Sep 13, 2021 at 14:33
  • @zevzek: I'm going to hazard a guess and say that anything that's async signal safe is safe in __libc_start_main().
    – Joshua
    Commented Sep 14, 2021 at 20:05
  • @zevzek: An async-signal-safe function won't use thread local storage or heap, and therefore will won't find out. It's malloc I'd worry about. Heap might not be initialized yet.
    – Joshua
    Commented Sep 15, 2021 at 1:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .