Search code examples
clinuxgdbentry-point

Is the real entry point a Linux program in the dynamic loader and how to show it in the callstack?


I think the start up process of a Linux program that uses shared library (not compiled with -static) is:

(1) Input the name of the executable in bash

(2) Bash read the executable file, find out it contains a ".interp" section which contains the name of the dynamic loader.

(3) Bash fork a new process, and in the sub process, exec the dynamic loader and pass in the name/path of the executable as parameter to the dynamic loader. But I'm not sure what is the entry point called, there is no "_start" in the dynammic loader which is /lib64/ld-linux-x86-64.so.2

(4) After exec return from Linux kernel to user mode, the first instruction executed in user mode should be the entry point of the dynamic loader.

(5) The dynamic loader load the executable (whose name/path is passed in as parameter) and all its dependencies, and the dynamic loader will call methods in ".init_array" section of each loaded module. Especially, the methods in ".init_array" section in the main executable should also be executed by the dynamic loader. Indeed there is no much difference between the main executable and the shared libraries from the view of the dynamic loader.

(6) The dynamic loader call the entry point of the main executable, which is _start. This means, if the entry point of the dynamic loader is also called _start, there should be two _start in the callstack from this point on.

Is there any problem in the above understanding?

But the problem is, when debugging a program by gdb and use

set backtrace past-main
start
bt

It only show a backtrace up to _start, but what about the callstacks in the dynamic loader? Also tried:

set backtrace past-entry
start
bt

Now it shows nothing before main...


Solution

  • Is there any problem in the above understanding?

    Yes: it's mostly wrong.

    bash doesn't look "inside" the binary, it simply fork()s and exec()s it. The kernel maps the binary, discovers that there is a PT_INTERP segment (nothing cares about sections at runtime) in it, maps the interpreter and passes control into it.

    After exec return from Linux kernel to user mode, the first instruction executed in user mode should be the entry point of the dynamic loader.

    Correct.

    The dynamic loader load the executable

    This is partly wrong again: the kernel has already mapped the main executable.

    The dynamic loader call the entry point of the main executable, which is _start. This means, if the entry point of the dynamic loader is also called _start, there should be two _start in the callstack from this point on.

    The dynamic loader transfers control to the main executable entry point. The control transfer doesn't have to be a CALL, it could be a JMP, in which case you shouldn't expect to find any callers of the a.out:_entry.

    On x86_64 this happens in the _dl_start_user assembly routine (in sysdeps/x86_64/dl-machine.h file), which looks something like this:

    _dl_start_user:
        # Save the user entry point address in %r12.
        movq %rax, %r12
    ...
        # Jump to the user's entry point.
        jmp *%r12