2023-2024-1 20232802《Linux内核原理与设计》第八周作业

预处理、编译、链接和目标文件的格式

可执行程序是怎么来的

首先使用chatGPT给我进行了简单介绍

以C语言为例,经过编译器预处理、编译成汇编代码、汇编器编译成目标代码,然后链接成可执行文件,再将可执行程序加载到内存中执行,过程可以通过下图展示(其中预处理已省略):

可执行文件的创建--预处理、编译和链接过程:

cd Code
vi hello.c
gcc –E –o hello.cpp hello.c –m32             //预处理,把include的文件包含进来及宏替换等工作
vi hello.cpp                                 //cpp为预处理的中间文件
gcc -x cpp-output -S -o hello.s hello.cpp -m32  //编译成汇编代码
vi hello.s
gcc -x assembler -c hello.s -o hello.o -m32     //编译成目标代码
vi hello.o                                      //得到二进制.o文件,ELF格式
gcc -o hello hello.o -m32                    //链接成可执行文件hello
vi hello                                     //也是二进制文件,ELF格式
gcc -o hello.static hello.o -m32 -static     //静态编译,占用内存较大
ls -l

常见的目标文件格式,最古老的目标文件格式是A.out,然后发展成coff,现在我们常用的pe(windows系统运用较多)、elf(linux系统中应用较多)。ELF全称为EXECUTABLE AND LINKABLE FORMAT,即可执行和可链接模式,是一个文件格式的标准。目标文件我们一般也叫它ABI(应用程序二进制接口),实际上在目标文件里面它已经是二进制兼容的格式了,也就是说它这个目标文件已经是适应到某一种cpu体系结构上的二进制指令。比如说我们在一个32位x86编译出来的目标文件链接成ARM平台上的可执行文件肯定是不可以的。

elf文件格式中的三种主要目标文件:

  1. 一个可重定位(relocatable)文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。(主要是.o文件)
  2. 一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。
  3. 一个共享object文件保存着代码和合适的数据,用来被下面的两个链接器链接。第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。(主要是.so文件)

当创建或增加一个进程映像的时候,系统在理论上将拷贝一个文件的段到一个虚拟的内存段。如图可执行文件的格式和进程地址空间有一个映射关系:

静态链接的ELF可执行文件与进程的地址空间的联系:

当elf文件加载到内存的时候,他把代码的数据加载到一块内存中来,其中有很多段代码。加载进来之后默认从0x8048000开始加载,前面是elf头部的一些信息,一般头部的大小会有不同,加载的入口点的位置可能是0x8048300,即程序的实际入口。当启动一个刚加载过可执行文件的进程的时候,开始执行的入口点。文件是一个elf的静态连接文件,链接的时候已经链接好了。从这(0x8048300)开始执行,压栈出栈,从main函数到结束,所有的链接在静态链接时候已经设定好了。正常需要用到共享库或动态链接的时候,情况会更复杂一点。

装载可执行程序之前,先了解一下可执行程序的执行环境。一般我们执行一个程序的shell环境,它本身不限制命令行参数的个数,命令行参数的个数受限于命令自身,比如 int main(int argc,char *argv[]) ,shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数。命令行参数和环境串都放在用户态的堆栈中。Shell程序->execve -> sys_execve,然后在初始化新程序堆栈时拷贝进去。

可执行程序、共享库和动态加载

动态链接有可执行装载时的动态链接和运行时的动态链接,下面演示了两种动态链接:

共享库shilibexample.c实现SharedLibApi()函数:

#include <stdio.h>
#include "shlibexample.h"

/*
 * Shared Lib API Example
 * input	: none
 * output	: none
 * return	: SUCCESS(0)/FAILURE(-1)
 *
 */
int SharedLibApi()
{
printf("This is a shared libary!\n");
return SUCCESS;
}

shilibexample.h:

#ifndef _SH_LIB_EXAMPLE_H_
#define _SH_LIB_EXAMPLE_H_

#define SUCCESS 0
#define FAILURE (-1)

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Shared Lib API Example
 * input	: none
 * output	: none
 * return	: SUCCESS(0)/FAILURE(-1)
 *
 */
int SharedLibApi();


#ifdef __cplusplus
}
#endif
#endif /* _SH_LIB_EXAMPLE_H_ */

通过gcc -shared shlibexaple.c -o libshlibexample.so -m32 编译成一个共享库文件,
下面是同样使用 gcc -shared dllibexample.c -o libdllibexample.so -m32 得到动态加载共享库。

其中dellibexample.c实现了DynamicalLoadingLibApi()函数:

#include <stdio.h>
#include "dllibexample.h"

#define SUCCESS 0
#define FAILURE (-1)

/*
 * Dynamical Loading Lib API Example
 * input	: none
 * output	: none
 * return	: SUCCESS(0)/FAILURE(-1)
 *
 */
int DynamicalLoadingLibApi()
{
printf("This is a Dynamical Loading libary!\n");
return SUCCESS;
}

dellibexample.h:

#ifndef _DL_LIB_EXAMPLE_H_
#define _DL_LIB_EXAMPLE_H_



#ifdef __cplusplus
extern "C" {
#endif
/*
 * Dynamical Loading Lib API Example
 * input	: none
 * output	: none
 * return	: SUCCESS(0)/FAILURE(-1)
 *
 */
int DynamicalLoadingLibApi();


#ifdef __cplusplus
}
#endif
#endif /* _DL_LIB_EXAMPLE_H_ */

main.c()函数:

#include <stdio.h>
#include "shlibexample.h"   
#include <dlfcn.h>

/*
 * Main program
 * input	: none
 * output	: none
 * return	: SUCCESS(0)/FAILURE(-1)
 *
 */
int main()
{
printf("This is a Main program!\n");
/* Use Shared Lib */
printf("Calling SharedLibApi() function of libshlibexample.so!\n");
SharedLibApi();
/* Use Dynamical Loading Lib */
void * handle = dlopen("libdllibexample.so",RTLD_NOW);
if(handle == NULL)
{
printf("Open Lib libdllibexample.so Error:%s\n",dlerror());
return   FAILURE;
}
int (*func)(void);
char * error;
func = dlsym(handle,"DynamicalLoadingLibApi");
if((error = dlerror()) != NULL)
{
printf("DynamicalLoadingLibApi not found:%s\n",error);
return   FAILURE;
}
printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n");
func();  
dlclose(handle);   
return SUCCESS;
}   

我们发现main函数中含有include "shlibexample.h" 以及include dlfcn,而没有include dllibexample(动态加载共享库)。当需要调用动态加载共享库时,使用定义在dlfcn.h中的dlopen。
最后通过  gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
接下来编译main()函数,注意这里只提供shlibexample的-L(库对应的接口头文件所在目录)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl,然后我们继续执行:

$ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。
$ ./main 

使用gdb跟踪sys_execve内核函数的处理过程

打开test.c文件:

发现增加了一句,MenuConfig("exec","Execute a program",Exec)

和fork()函数类似,增加了一个fork,子进程增加了一个execlp("/hello","hello",NULL); 启动hello,看一下hello.c:

看一下Makefile文件,静态的方式编译了hello.c,并在生成根文件系统时把init 和hello都放在rootfs里面:

输入命令 make rootfs  ,在qemu窗口中输入help 执行一下exec:

先cd .. 返回到上一级,qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S启动,水平分割,gdb

然后把符号表文件加载进来,gdb服务器使用默认端口号1234来连接,并通过gdb跟踪调试,设置断点,可以先停在sys_execve然后再设置其他断点:

输入命令c继续执行:

输入命令c连续执行三次,在menuos窗口输入exec,发现执行到 This is child process! 停下:

进入sys_execve系统调用,list列出来,跟踪:

接下来通过s进入sys_execve内部

输入c继续执行到load_elf_binary,list查看;再按c执行,执行到start_thread,想知道new_ip到底指向哪里,new_ip是返回到用户态的第一条指令的地址。再水平分割一个控制台出来,使用命令readelf -h hello,可以看到入口点地址和上面po new_ip所显示的地址一样:

我们继续执行s步骤,可以看到在进行修改内核堆栈的位置,发现原来压栈的ip和sp都被改成了新的ip(程序hello的入口点地址)和新的sp,这样在返回到用户态的时候程序就有一个新的可执行上下文环境。最后按一下c,exec的执行结束:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值