Linux应用程序的启动流程

目录

代码

用strace查看调用流程

分析

总结


看到一篇好文:摘抄记录--Linux应用程序 启动流程-BugMan-ChinaUnix博客

  • 代码

#include <stdio.h>

int main (int argc, char *argv[])
{
    printf ("Hello World\n");

    return 0;
}
  • 用strace查看调用流程

pc123@ubuntu:~/Public$ strace ./main
execve("./main", ["./main"], 0x7fff3906f3d0 /* 56 vars */) = 0
brk(NULL)                               = 0x55d0fc755000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
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
fstat(3, {st_mode=S_IFREG|0644, st_size=81045, ...}) = 0
mmap(NULL, 81045, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe65b878000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/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\240\35\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe65b876000
mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe65b272000
mprotect(0x7fe65b459000, 2097152, PROT_NONE) = 0
mmap(0x7fe65b659000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fe65b659000
mmap(0x7fe65b65f000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe65b65f000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7fe65b8774c0) = 0
mprotect(0x7fe65b659000, 16384, PROT_READ) = 0
mprotect(0x55d0fa83f000, 4096, PROT_READ) = 0
mprotect(0x7fe65b88c000, 4096, PROT_READ) = 0
munmap(0x7fe65b878000, 81045)           = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 14), ...}) = 0
brk(NULL)                               = 0x55d0fc755000
brk(0x55d0fc776000)                     = 0x55d0fc776000
write(1, "hello world", 11hello world)             = 11
exit_group(0)                           = ?
+++ exited with 0 +++
pc123@ubuntu:~/Public$
  • 分析

  1. 毫无疑问,在shell内执行一个程序main,本质上是shell去调用execve函数执行main程序
  2. execve是一个系统调用,Linux内核会在这个系统调用里面为main程序映射必要的内存
  3. 动态解释器【动态解释器】:当execve将控制权给/lib64/ld-linux-x86-64.so.2的时候,这个文件进行动态库代码重定向等功能
    $ readelf -l hello | grep interpreter
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  4. 通过读取elf头部信息中的entry部分,即可得到程序入口地址为0x540
    pc123@ubuntu:~/Public$ readelf -h main
    ELF Header:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      Class:                             ELF64
      Data:                              2's complement, little endian
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI Version:                       0
      Type:                              DYN (Shared object file)
      Machine:                           Advanced Micro Devices X86-64
      Version:                           0x1
      Entry point address:               0x540
      Start of program headers:          64 (bytes into file)
      Start of section headers:          6448 (bytes into file)
      Flags:                             0x0
      Size of this header:               64 (bytes)
      Size of program headers:           56 (bytes)
      Number of program headers:         9
      Size of section headers:           64 (bytes)
      Number of section headers:         29
      Section header string table index: 28
    pc123@ubuntu:~/Public$ 
    
  5. 通过objdump找到程序的入口地址:
    pc123@ubuntu:~/Public$ objdump -axd main > main.s

    在main.s文件找到地址为0x540的代码

    Disassembly of section .text:       段名:.text,表示代码段
    
    0000000000000540 <_start>:        函数名_start,存放地址0x540
     540:	31 ed                	xor    %ebp,%ebp
     542:	49 89 d1             	mov    %rdx,%r9
     545:	5e                   	pop    %rsi
     546:	48 89 e2             	mov    %rsp,%rdx
     549:	48 83 e4 f0          	and    $0xfffffffffffffff0,%rsp
     54d:	50                   	push   %rax
     54e:	54                   	push   %rsp
     54f:	4c 8d 05 8a 01 00 00 	lea    0x18a(%rip),%r8        # 6e0 <__libc_csu_fini>
     556:	48 8d 0d 13 01 00 00 	lea    0x113(%rip),%rcx        # 670 <__libc_csu_init>
     55d:	48 8d 3d e6 00 00 00 	lea    0xe6(%rip),%rdi        # 64a <main>
     564:	ff 15 76 0a 20 00    	callq  *0x200a76(%rip)        # 200fe0 <__libc_start_main@GLIBC_2.2.5>
     56a:	f4                   	hlt    
     56b:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1)
    

    可见在shell执行main程序,程序的入口是_start函数,这个_start函数实际上是由C库实现的,

  6. _start函数最后调用了__libc_start_main函数【汇编的callq *0x200a76这一行】,而调用__libc_start_main函数传入了0x54f, 0x556, 0x55d对应的这三个参数,其中对应的就是__libc_csu_fini, __libc_csu_initial, main函数【也就是我们写的代码】 

  7. 【__libc_start_main@plt表示这是一个延迟加载函数,什么是延迟加载函数呢?延迟加载函数就是指在动态解释阶段不进行代码重定位,只有在真正使用该函数的时候,才去定位该函数的地址, 这样做的目的是加快程序启动】

  8. __libc_start_main函数的运行的顺序为:__libc_csu_init->main->__libc_csu_fini,__libc_csu_init叫构造函数,__libc_csu_fini叫析构函数,在代码中可通过__attribute__ ((constructor))标记构造函数,__attribute__ ((destructor))来标记析构函数                      有这样的例子

    #include <stdio.h>
    
    static void hello_after() __attribute__ ((destructor));
    static void hello_before() __attribute__ ((constructor));
    
    static void hello_before(void)
    {
        printf("Before main\n");
    }
    
    static void hello_after(void)
    {
        printf("After main\n");
    }
    
    int main (int argc, char *argv[])
    {
        printf ("Hello World\n");
    
        return 0;
    }
    pc123@ubuntu:~/Public$ ./main 
    Before main
    Hello World
    After main
    pc123@ubuntu:~/Public$ 
  • 总结

shell通过execve启动main程序------->main函数被C 库作为一个函数----------->整个程序的入口地址是_start【通过objdump对应的0x540确定】----->start函数调用了__libc_start_main,传入了构造,main, 析构函数。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值