9

When I try

strace ping google.com

or

strace ls 

or

even strace curl <domain>

The first two systemcalls are always,

execve("/usr/bin/curl", ["curl", "google.com"], 0x7ffecf1bc378 /* 61 vars */) = 0
brk(NULL)                               = 0x55f553c49000

Can someone please tell me if execve will always be the first systemcall when I execute anything?

I read this manual page, https://linux.die.net/man/2/execve But don't understand if execve is really a system call or executable program?

MaverickD
  • 359

2 Answers2

9

In Linux a new process is created via fork(), which makes a child process which is almost identical to the parent process. To create a new process whose program is different than the program of the original process, the new child process immediately calls execve(), which is basically the process saying "replace my current program with this other program".

brk(NULL) is the process asking where its heap memory ends. Many programs call this as their first system call (which will show up right after execve()) because they use malloc() right away (or a library call they make uses malloc() internally). If the program and its library calls don't need to call malloc() for a while then something besides brk(NULL) will be the second system call.

  • Linux's brk(0) actually just checks the current break, it doesn't change it yet. – ilkkachu Aug 27 '18 at 08:38
  • Oops, I'd glossed over that brk()'s argument was NULL/0. (And I didn't know about it special behavior on that value) – Matthew Cline Aug 27 '18 at 14:12
  • but that doesn't explain why brk(NULL) is the 2nd system call. Well I just realized it is not always the 2nd systemcall. When I run strace ping google.com, I get access("/etc/suid-debug", F_OK) = -1 ENOENT (No such file or directory) as a 2nd call after execve – MaverickD Aug 29 '18 at 00:48
  • @MaverickD added more details to my answer. – Matthew Cline Aug 29 '18 at 00:51
  • 1
    I know this is late but is the reason that strace does not register the invocation of the fork() call, that it is made by the parent process and strace only tracks what the child does ? Also in this case, the parent process would simply be the shell right ? – First User Aug 05 '22 at 14:10
1

I think all those syscalls are just the dynamic linker setting up stuff before execution. I made a little test program to check this out.

me@bar:~/foo$ cat main.c
#include <unistd.h>
void main(){
        syscall(60,0);
}

now we compile it first statically and then dynamically. the -Wl,-emain argument tells gcc to pass -emain to the linker, that means set main as the entry point. And sure enough the program exits immediately.


me@bar:~/foo$ gcc -static main.c -Wl,-emain -o main
me@bar:~/foo$ strace ./main 2>&1 | head
execve("./main", ["./main"], 0x7fff6d7c4b40 /* 50 vars */) = 0
exit(0)                                 = ?
+++ exited with 0 +++

now without the -static flag, we get what you usually see.

me@bar:~/foo$ gcc main.c -Wl,-emain -o main
me@bar:~/foo$ strace ./main 2>&1 | head
execve("./main", ["./main"], 0x7fff06b48d20 /* 50 vars */) = 0
brk(NULL)                               = 0x558c61a90000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd68e8e950) = -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=77239, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 77239, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe55ed7e000
...

Even though the code is meant to jump directly to main!

now I thought that might be specific to libc, maybe it needs to set itself up or something like that. but actually its not, I tried making a little dynamic library to link with, and then on the strace the following showed up:

$ ldd main
    linux-vdso.so.1 (0x00007fff9c8cd000)
    libfoo.so => /home/me/path/to/libfoo.so (0x00007f80ed20a000)

$ strace ./main execve("./main", ["./main"], 0x7ffcdf5b5a70 /* 51 vars */) = 0 brk(NULL) ... access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/home/me/path/to/libfoo.so", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\0\0\0\0\0\0\0"..., 832) = 832 newfstatat(3, "", {st_mode=S_IFREG|0755, st_size=13240, ...}, AT_EMPTY_PATH) = 0 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f028541c000 mmap(NULL, 16384, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f0285418000 mmap(0x7f0285419000, 4096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7f0285419000 mmap(0x7f028541a000, 4096, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f028541a000 mmap(0x7f028541b000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f028541b000 close(3) = 0 arch_prctl(ARCH_SET_FS, 0x7f028541d040) = 0 set_tid_address(0x7f028541d310) = 6310 set_robust_list(0x7f028541d320, 24) = 0 rseq(0x7f028541d960, 0x20, 0, 0x53053053) = 0 mprotect(0x7f028541b000, 4096, PROT_READ) = 0 mprotect(0x402000, 4096, PROT_READ) = 0 exit(0) = ? +++ exited with 0 +++

I'm not sure what most of the calls there are doing, but its pretty clear they are just mapping my little libfoo (actually a single function that just executes the exit syscall) in memory. So these traces are clearly from the dynamic linker, mapping the dynamic library into memory (after opening it on filedescriptor 3) and then handing over execution to the program proper (which in this case is merely exiting immediately).
Now, i guess the brk call is there because the dynamic linker needs to work out the memory layout the program will have during runtime, so it needs to do some math with that value. Linux being free software, this is probably explained somewhere in the source for the dynamic linker. Hope this helps.

Osinaga
  • 36