• Linux应用程序的启动流程


    目录

    代码

    用strace查看调用流程

    分析

    总结


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

    • 代码

    1. #include
    2. int main (int argc, char *argv[])
    3. {
    4. printf ("Hello World\n");
    5. return 0;
    6. }
    1. pc123@ubuntu:~/Public$ strace ./main
    2. execve("./main", ["./main"], 0x7fff3906f3d0 /* 56 vars */) = 0
    3. brk(NULL) = 0x55d0fc755000
    4. access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
    5. access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
    6. openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    7. fstat(3, {st_mode=S_IFREG|0644, st_size=81045, ...}) = 0
    8. mmap(NULL, 81045, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fe65b878000
    9. close(3) = 0
    10. access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
    11. openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
    12. 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
    13. fstat(3, {st_mode=S_IFREG|0755, st_size=2030928, ...}) = 0
    14. mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe65b876000
    15. mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fe65b272000
    16. mprotect(0x7fe65b459000, 2097152, PROT_NONE) = 0
    17. mmap(0x7fe65b659000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7fe65b659000
    18. mmap(0x7fe65b65f000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fe65b65f000
    19. close(3) = 0
    20. arch_prctl(ARCH_SET_FS, 0x7fe65b8774c0) = 0
    21. mprotect(0x7fe65b659000, 16384, PROT_READ) = 0
    22. mprotect(0x55d0fa83f000, 4096, PROT_READ) = 0
    23. mprotect(0x7fe65b88c000, 4096, PROT_READ) = 0
    24. munmap(0x7fe65b878000, 81045) = 0
    25. fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 14), ...}) = 0
    26. brk(NULL) = 0x55d0fc755000
    27. brk(0x55d0fc776000) = 0x55d0fc776000
    28. write(1, "hello world", 11hello world) = 11
    29. exit_group(0) = ?
    30. +++ exited with 0 +++
    31. pc123@ubuntu:~/Public$
    • 分析

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

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

      1. Disassembly of section .text: 段名:.text,表示代码段
      2. 0000000000000540 <_start>: 函数名_start,存放地址0x540
      3. 540: 31 ed xor %ebp,%ebp
      4. 542: 49 89 d1 mov %rdx,%r9
      5. 545: 5e pop %rsi
      6. 546: 48 89 e2 mov %rsp,%rdx
      7. 549: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
      8. 54d: 50 push %rax
      9. 54e: 54 push %rsp
      10. 54f: 4c 8d 05 8a 01 00 00 lea 0x18a(%rip),%r8 # 6e0 <__libc_csu_fini>
      11. 556: 48 8d 0d 13 01 00 00 lea 0x113(%rip),%rcx # 670 <__libc_csu_init>
      12. 55d: 48 8d 3d e6 00 00 00 lea 0xe6(%rip),%rdi # 64a
      13. 564: ff 15 76 0a 20 00 callq *0x200a76(%rip) # 200fe0 <__libc_start_main@GLIBC_2.2.5>
      14. 56a: f4 hlt
      15. 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))来标记析构函数                      有这样的例子

      1. #include
      2. static void hello_after() __attribute__ ((destructor));
      3. static void hello_before() __attribute__ ((constructor));
      4. static void hello_before(void)
      5. {
      6. printf("Before main\n");
      7. }
      8. static void hello_after(void)
      9. {
      10. printf("After main\n");
      11. }
      12. int main (int argc, char *argv[])
      13. {
      14. printf ("Hello World\n");
      15. return 0;
      16. }
      1. pc123@ubuntu:~/Public$ ./main
      2. Before main
      3. Hello World
      4. After main
      5. pc123@ubuntu:~/Public$
    • 总结

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

  • 相关阅读:
    “如何应用数据分析提升软件开发流程效率?”
    C const
    【玩转C语言】第四讲--->操作符与循环语句
    [MTK6771] android13系统启用OMAPI 支持esim.me
    ​书籍实拍《乡村振兴战略下传统村落文化旅游设计》许少辉八一专著
    Chapter7:非线性控制系统分析
    可信执行环境(Tee)入门综述
    centos7中sshd -t没内容输出日志也没内容但sshd服务重启一直失败解决方法、strace命令的使用以及使用场景说明
    OpenResty
    前端如何实现隐藏滚动条,并且页面还可以滚动
  • 原文地址:https://blog.csdn.net/m0_37844072/article/details/126848036