Update! I mention below that there’s some fork calls that appear to be missing. Here’s the solution.
Julia Evans visited Hacker School yesterday, and I got to work with her on a super neat problem. In the end, the question became How does tmux start?. Specifically:
In terminal 1:
Then in terminal 2:
Huh, looks like tmux has a parent of “init” (the one with pid
9930 here). But I started tmux in a terminal, which has the bash process running. This makes sense, because if the terminal closes tmux should still run. But how does it do it?
Here’s the short, boring answer: it calls the
daemon function in
unistd.h on startup.
But here’s the more interesting part – how did we figure this out? We guessed it had to do something with forking and exiting processies, so let’s see what’s being forked!
Let’s see what is forking.
Problem is, strace actually doesn’t record the fork system call. I’m not sure why, but I’m guessing it’s fork in particular that doesn’t work. It does tell us when a process exits though. Oddly it never mentions the
9930 pid. Hmm.
What about wrapping the fork function? Fork is in
unistd.h. If tmux is dynamically linking to that library, then we can inject our own code in and print to a file whenever fork is called! Let’s go!
Great! Now if edit the
LD_PRELOAD environment variable, all calls to fork by tmux should go to this.
dlsym call in our unfork.c requires the
libdl to be included for it to link correctly. As such, we include it in
Lets see if it worked!
Neat! We got a bunch of pids printed to the file. But, the critical missing one is the new tmux process that is the child of init. That’s the one we care about!
Attempt 3: Running the tmux source
That was neat, but we still don’t have our answer. I got the tmux source running (I suggest using vagrant and compiling it in a virtual machine). Globally searching for
fork( I found
server.c had this function:
Ah! Printing inside of here and running tmux shows that it is indeed called. Looking more into the function I see this:
Huh.. now we are getting close. The
daemon standard library calls its own special fork, and not the standard library one. Here’s proof:
Why does daemon not call our special fork?
Here’s my guess: daemon and fork are in the same library:
unistd.h. Daemon doens’t need to externally look for the fork function through dynamic linking because it already knows where it is. I haven’t yet confirmed this though.
Wait wait. How does tmux start up?
Right! So turns out the source just tells us, and all this debugging wasn’t necessary at all :p. It forks a child in
server_start, which then forks a further child and kills itself (by calling
daemon). Now that that grandchild has no parent,
init picks it up. Nice!