可执行程序的装载

曹朋辉
原创作品转载请注明出处
《Linux内核分析》MOOC课程

可执行文件的创建——预处理、编译和链接

shiyanlou:~/ $ cd Code                                                [9:27:05]
shiyanlou:Code/ $ vi hello.c                                          [9:27:14]
//预处理 -E
shiyanlou:Code/ $ gcc -E -o hello.cpp hello.c -m32                    [9:34:55]
shiyanlou:Code/ $ vi hello.cpp                                        [9:35:04]
//-x 指定文件类型
//将预处理的文件编译为汇编文件 -S
shiyanlou:Code/ $ gcc -x cpp-output -S -o hello.s hello.cpp -m32      [9:35:21]
shiyanlou:Code/ $ vi hello.s                                          [9:35:28]
//将汇编文件编译为目标文件(二进制的)
shiyanlou:Code/ $ gcc -x assembler -c hello.s -o hello.o -m32         [9:35:58]
shiyanlou:Code/ $ vi hello.o                                          [9:38:44]
//链接  hello为ELF格式的文件 以上语句调用共享库链接
shiyanlou:Code/ $ gcc -o hello hello.o -m32                           [9:39:37]
shiyanlou:Code/ $ vi hello                                            [9:39:44]
//调用静态库链接
shiyanlou:Code/ $ gcc -o hello.static hello.o -m32 -static            [9:40:21]
shiyanlou:Code/ $ ls -l                                               [9:41:13]
-rwxrwxr-x 1 shiyanlou shiyanlou   7292  3\u6708 23 09:39 hello
-rw-rw-r-- 1 shiyanlou shiyanlou     64  3\u6708 23 09:30 hello.c
-rw-rw-r-- 1 shiyanlou shiyanlou  17302  3\u6708 23 09:35 hello.cpp
-rw-rw-r-- 1 shiyanlou shiyanlou   1020  3\u6708 23 09:38 hello.o
-rw-rw-r-- 1 shiyanlou shiyanlou    470  3\u6708 23 09:35 hello.s
-rwxrwxr-x 1 shiyanlou shiyanlou 733254  3\u6708 23 09:41 hello.static

静态链接和动态链接是怎么回事?
可执行文件的内部是怎样的?
ABI和目标文件什么关系?
命令行参数和环境变量是如何保存和传递的?
命令行参数和环境变量是如何进入新程序的堆栈的?

参数传递

![Upload Paste_Image.png failed. Please try again.]

目标文件及链接

ELF目标文件格式
ELF文件格式 -- (中文翻译版)

可执行程序的装载_第1张图片
目标文件的格式

PE格式大多用在wondows上
ELF格式大多用在Linux上
ELF (Excutable and Linkble Format)

.o 可重定位

.o

a.out

a.out

.so

可执行程序的装载_第2张图片
.so

查看ELF文件的头部

shiyanlou:Code/ $ readelf -h hello

可执行程序的装载_第3张图片
readelf

查看该ELF文件依赖的共享库

shiyanlou:sharelib/ $ ldd main                                       [21:25:56]
    linux-gate.so.1 =>  (0xf774e000) # 这个是vdso - virtual DSO:dynamically shared object,并不存在这个共享库文件,它是内核的一部分,为了解决libc与新版本内核的系统调用不同步的问题,linux-gate.so.1里封装的系统调用与内核支持的系统调用完全匹配,因为它就是内核的一部分嘛。而libc里封装的系统调用与内核并不完全一致,因为它们各自都在版本更新。
    libshlibexample.so => /home/shiyanlou/LinuxKernel/sharelib/libshlibexample.so (0xf7749000)
    libdl.so.2 => /lib32/libdl.so.2 (0xf7734000)
    libc.so.6 => /lib32/libc.so.6 (0xf7588000)
    /lib/ld-linux.so.2 (0xf774f000)
shiyanlou:sharelib/ $ ldd /lib32/libc.so.6                         [21:37:00]
    /lib/ld-linux.so.2 (0xf779e000)
    linux-gate.so.1 =>  (0xf779d000)
# readelf -d 也可以看依赖的so文件
shiyanlou:sharelib/ $ readelf -d main                              [21:28:04]
Dynamic section at offset 0xf04 contains 26 entries:
 0x00000001 (NEEDED)                     共享库:[libshlibexample.so]
 0x00000001 (NEEDED)                     共享库:[libdl.so.2]
 0x00000001 (NEEDED)                     共享库:[libc.so.6]
 0x0000000c (INIT)                       0x80484f0
 0x0000000d (FINI)                       0x8048804
 0x00000019 (INIT_ARRAY)                 0x8049ef8

命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。
$ ls -l /usr/bin 列出/usr/bin下的目录信息
Shell本身不限制命令行参数的个数,�命令行参数的个数受限于命令自身
例如,int main(int argc, char *argv[])
又如, int main(int argc, char *argv[], char envp[])
Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
库函数exec
都是execve的封装例程

#include 
#include 
#include 
int main(int argc, char * argv[])
{
    int pid;
    /* fork another process */
    pid = fork();
    if (pid<0) 
    { 
        /* error occurred */
        fprintf(stderr,"Fork Failed!");
        exit(-1);
    } 
    else if (pid==0) 
    {
        /*   child process   */
        execlp("/bin/ls","ls",NULL);
    } 
    else 
    {  
        /*     parent process  */
        /* parent will wait for the child to complete*/
        wait(NULL);
        printf("Child Complete!");
        exit(0);
    }
}

命令行参数和环境串都放在用户态堆栈中

可执行程序的装载_第4张图片
用户态堆栈

在linux中一个程序是如何加载运行的

在shell中输入一个程序的名称时,shell会先fork一个子进程,然后在子进程中调用execlp函数来拉起我们执行的程序,

exec系统调用

首先,exec会调用sys_execve,然后调用do_execve,再调用do_execve_common,这个函数会把函数参数和系统环境传进来进行相应的处理。然后调用exec_binprm来执行相应的程序。而exec_binprm又会调用search_binary_handler,这个函数会调用各种不同的格式来识别相应的文件,直到识别为止,比如linux中可执行文件为ELF,它就会识别出elf文件。

在sys_execve处设置断点

可执行程序的装载_第5张图片
sys_execve

retval = fmt->load_binary(bprm)把对应的文件以对应的格式加载到内存里面。由于Linux是elf格式,故会执行对应的load_elf_binary,然后在这个函数里面有一个函数start_thread,这个函数会复制内核堆栈,同时会设置新的进程的执行位置,即会设置新的eip,使eip指向新程序的入口位置。

可执行程序的装载_第6张图片
retval

你可能感兴趣的:(可执行程序的装载)