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. Sep 12 at 17:43

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
    Sep 13 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. Sep 13 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
    Sep 13 at 14:33
  • @R..G Why isn't safe to call any libc function from __libc_start_main()? Could you give an example program which breaks because of that? If the __libc_start_main() wrapper is so broken and useless in the first place, then better get rid of it (for instance, BSDs don't have anything similar). FWIW, I had already tested this hack with your musl lib too before posting it, and it worked.
    – zevzek
    Sep 14 at 14:53
  • @zevzek: I'm going to hazard a guess and say that anything that's async signal safe is safe in __libc_start_main().
    – Joshua
    Sep 14 at 20:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.