Linux X86软件启动流程

序言

在我刚学C语言的时候, 都知道程序是从main()函数开始执行的, 实际上呢, 程序运行以后第一个执行的函数是_start()函数, 然后会去执行__libc_start_main, 具体流程如下:

本文将围绕这张图进行展开, 主要是根据参考文章进行一波学习, 程序使用动态链接, 所以如果你用的是静态链接的话, 可能会跟我的调试结果不太一样

具体流程

接下来通过调试从汇编层面来看一下具体的程序启动流程

我们这里写一个简单的c程序, 并编译:

// prog1.c
// gcc -ggdb -o prog1 prog1.c
int
main()
{
}

然后用objdump看一眼objdump -d prog1 > prog1.dump

启动程序

首先, 操作系统如何启动一个新的程序呢? 在我们执行某个程序以后, shell或者gui会调用execve()函数, 通过系统调用来启动程序一个软件. 然后会为你设置一个栈, 将argc argv以及envp放入栈里, 并设置文件描述符0, 1, 2(stdin, stdout, stderr), 之后加载器会设置各种重定位, 然后会调用我们的预初始化器, 当一切准备就绪后, 调用_start()函数:

(objdump的汇编代码跟ida的相反, mov %rsp, %rdx在objdump中是将rsp放入rdx, 而IDA中是rdx放入rsp, 所以他们对同一段汇编的反汇编显示不太一样)

```asm

00000000004003e0 <_start>:
4003e0: 31 ed xor %ebp,%ebp
4003e2: 49 89 d1 mov %rdx,%r9
4003e5: 5e pop %rsi
4003e6: 48 89 e2 mov %rsp,%rdx
4003e9: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
4003ed: 50 push %rax
4003ee: 54 push %rsp
4003ef: 49 c7 c0 60 05 40 00 mov $0x400560,%r8
4003f6: 48 c7 c1 f0 04 40 00 mov $0x4004f0,%rcx
4003fd: 48 c7 c7 d6 04 40 00 mov $0x4004d6,%rdi
400404: e8 b7 ff ff ff callq 4003c0 __libc_start_main@plt
400409: f4 hlt
40040a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
``

首先清空ebp, 用来作为第一个栈的标记, 此时栈里保存了argc, argv和envp, 所以会pop argc到rsi中, 此时rsp指向了argv, 使用mov把地址传给rdx, 之后用and来清空最低四位的栈顶指针rsp, 使其重新指向栈顶

Reference