glibc源码分析之进程启动(start.S)

来源:互联网 发布:最赚钱的网络投资 编辑:程序博客网 时间:2024/06/05 10:57

启动代码

顾名思义,启动代码就是使进程启动的代码。它包含了进程运行的第一条指令。
每个程序都有一段启动代码。启动代码初始化进程环境,调用main函数,使程序正式开始运行。
glibc为我们提供了进程的启动代码,本文将详细讲解glibc提供的启动代码。(基于glibc2.7)

启动代码的实现

接下来,我们将详细讲解启动代码的实现。
让我们来看一下glibc源代码中启动代码的实现。
glibc的启动代码包含在一个汇编文件和几个c语言文件中。这些文件将在本文中详细讲解。

start.S
首先是汇编文件start.S文件。该文件位于sysdeps/i386/elf/start.S。

    .text    .globl _start    .type _start,@function_start:    xorl %ebp, %ebp    popl %esi           movl %esp, %ecx    andl $0xfffffff0, %esp    pushl %eax    pushl %esp    pushl %edx    pushl $__libc_csu_fini    pushl $__libc_csu_init    pushl %ecx          pushl %esi          pushl $BP_SYM (main)    call BP_SYM (__libc_start_main)    hlt             .section .rodata    .globl _fp_hw_fp_hw: .long 3    .size _fp_hw, 4    .type _fp_hw,@object    .data    .globl __data_start__data_start:    .long 0    .weak data_start    data_start = __data_start

整个代码不长,也就几十行代码。代码使用汇编语言编写。接下来分段解析代码实现。

xorl %ebp, %ebp

清理栈帧。由于在c语言函数调用中需要大量使用ebp寄存器,将其作为栈帧。此处将其设置为0,这样最外层的栈帧也就为0。

popl %esi       movl %esp, %ecx

在明白这两句汇编代码时,需要先了解进程运行前栈的分布情况。

进程运行前栈的分布情况
这里写图片描述

栈顶存放了命令行参数的个数argc,所以popl %esi将命令行参数的个数放入了esi寄存器中。
argc下是命令行参数字符指针数组(argv[0]……argv[n])。movl %esp, %ecx 将esp寄存器的值赋值给ecx寄存器。此时ecx寄存器指向命令行参数指针数组(argv)。

andl $0xfffffff0, %esp

调整esp寄存器的值,使其是16的倍数。便于后面的入栈操作。

pushl %eax

将eax寄存器的值压入栈中。后面会压入28个字节的数据,此处压入eax使压入的数据为32字节。

pushl %esp

将esp寄存器的值压入栈中。作为用户空间最高的栈地址。

pushl %edx

压入共享库终止函数的地址。

pushl $__libc_csu_finipushl $__libc_csu_init

如果__libc_csu_fini函数和__libc_csu_init函数的地址。

pushl %ecx

压入命令行参数的个数。

pushl %esi

压入命令行参数指针数组的地址。

pushl $BP_SYM (main)

压入main函数的地址,为后面调用main函数做准备。
BP_SYM 宏的定义

#define BP_SYM(name) _BP_SYM (name)# define _BP_SYM(name) name
call BP_SYM (__libc_start_main)

执行__libc_start_main函数

hlt 

停机指令,一般不会执行到此处,在__libc_start_main函数中会调用exit,结束进程。

0 0