zoukankan      html  css  js  c++  java
  • 2019-2020-1 20199324《Linux内核原理与分析》第八周作业

    第七章 可执行程序工作原理

    一.ELF目标文件格式

    • 目标文件:ABI,应用程序二进制接口,是编译器生成的文件。
    • ELF:可执行的和可链接的格式,是一个目标文件格式的标准。三种类型是:
      • 可重定位文件:Linux中每个内核源代码.c文件都会生成一个同名的.o文件,该文件即为可重定位目标文件。
      • 可执行文件:由多个可重定位文件结合生成。
      • 共享目标文件:共享库,指可以被可执行文件或者其他库文件使用的目标文件。Linux下共享库后缀为.so的文件。
    • ELF文件的作用:参与程序的连接(建立一个程序)和程序的执行(运行一个程序)。
    • ELF格式简介

    可以使用入下命令将hello.c编译为一个32位静态链接ELF可执行文件 hello.m32.static 。

    ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。

    二.程序编译

    预处理:.i 文件任然是文本文件,可用任意编辑工具打开查看。

    gcc -E hello.c -o hello.i
    

    编译-S 表示只进行编译而不进行汇编(仅生成汇编代码,不进一步翻译为机器指令),-m32生成32位平台格式文件,其与64位使用不同的寄存器名及指令集。

    gcc -S hello.i -o hello.s -m32
    

    汇编:汇编后形成的.o文件已经是ELF格式文件了。

    gcc -c hello.s -o hello.o.m32 -m32
    

    链接:将各种代码和数据部分收集起来并且组合成为一个单一文件的过程,这个文件被加载到内存中并执行。将输编译输出的.o文件与libc库文件进行链接,生成最终的可执行文件。

    gcc hello.o.m32 -o hello.m32.static -m32 -static
    

    三.链接与库

    • 链接从过程上分为:符号解析重定位
    • 符号解析:编译器需要到其他的共享库中找到printf的“定义(机器指令片段)”,找到后把该机器指令与hello.o拼接到一起,生成了执行文件hello。hello中printf就存在了(有定义即有了明确的地址)。
    • 重定位:把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程。即在装入时对目标程序中指令和数据的修改过程。
    • 根据链接的时机不同,又分为静态链接动态链接
    • 静态链接:在编译链接时直接将需要的执行代码复制到最终的可执行文件中。
    • 动态链接:在编译时不直接复制可执行代码,是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统。
      • 在编译时候不加“ -static ”选项,编辑器会默认使用动态链接。静态链接在编译时会把需要的所有代码都链接进去所以应用程序相对比较大。如下图所示:
    • 动态链接分为可执行程序装载时动态链接运行时动态链接

    装载时动态链接

    使用如下命令:

    gcc -shared shlibexample.c -o libshlibexample.so -m32 
    

    可以将一个动态库源码编译成libshlibexample.so文件。

    运行时动态链接

    运行时动态链接库的源文件为 dllibexample.h 和 dllibexample.c。

    编译成 libdllibexample.so 文件的指令如下:

    gcc -shared dllibexample.c -o libdllibexample.so -m32 
    

    动态链接实例

    如下代码分别以装载时动态链接和运行时动态链接调用了两个动态链接库。

    #include<stdio.h>
    #include "shlibexample.h"
    #include<dlfcn.h>
    
    int main()
    {
    	printf("This is a Main program!
    ");
    	
    	/*装载时动态链接*/
    	printf("Calling SharedLibApi()  function of libshlibexample.so!
    ");
    	SharedLibApi();
    	
    	/*运行时动态链接*/
    	void *handle=dlopen("libdllibexample.so",RTLD_NOW);
    	if(handle == NULL)
    	{
    		printf("Open Lib libdllipexample.so Error:%s
    ",dlerror());
    		return FAILURE;
    	}
    	int (*func)(void);
    	char *error;
    	func=dlsym(handle,"DynamicalLoadingLibApi");
    	if((error=dlerror())!=NULL)
    	{
    		printf("DynamicalLoadingLibApi not found:%s
    ",error);
    		return FAILURE;
    	}
    	printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!
    ");
    	func();
    	dlclose(handle);
    	return SUCCESS;
    }
    

    编译运行:

    $ sudo gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
    $ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件复制到默认路径下。
    $ ./main
    

    其中,参数-L指明头文件所在的目录,参数-l指明库文件名,如libshlibexample.so去掉lib和.so部分。参数-ldl指明其所需要使用共享库dlopen等函数。

    最终的编译运行效果如下:

    遇到的问题1:

    解决办法:
    libshlibexample.so是一个第三方库,并不存在于系统的默认路径中,搜索不到。因此一个简答的方法是把libshlibexample.so放到/usr/local/lib目录下,就可以解决这个问题。
    进入libshlibexample.so所在的目录,打开终端,运行以下命令即可:

    sudo cp libshlibexample.so /usr/local/lib/
    

    遇到的问题2:

    解决办法:
    运行时动态链接的操作不在该目录下,因此找不到libdllibexample.so文件。在该目录下重新编译得到libdllibexample.so文件即可。

    四.程序装载

    • shell本身不限制命令行参数的个数,命令行参数个数受限于命令本身。
    • shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
    • execve的函数原型:
    int execve(const char *filename,char *const argv[],char * const envp[]);
    
    • 调用关系:sys_execve() -> do_execve() -> do_execve_common() -> exec_binprm() -> search_binary_handler() -> load_elf_binary() -> start_thread()

    • fork与execve的区别和联系:

      • 都是比较特殊的系统调用;
      • fork在陷入内核态后有两次返回,第一次返回到原来父进程的位置继续向下执行(这和其他的系统调用是一样的),第二次是在子进程中fork也返回了一次,这次会返回到ret_from_fork,之后正常返回用户态;
      • execve在执行时陷入内核态,在内核中调用execve加载的可执行文件把当前进程的可执行程序给覆盖了,当execve的系统调用返回时,返回的已经不是原来的那个可执行程序了,返回的是新的可执行程序执行的起点,即main函数的大致位置(一般地址为0x8048xxx,由编译器设定)。
    • 执行 readelf -h 可以查看 ELF 可执行文件首部信息。

    五.实验

    使用gdb跟踪分析execve系统调用内核处理函数sys_execve

    将menu目录删除,利用git命令克隆一个新的menu目录。

    用test_exec.c将test.c覆盖。查看test.c文件可以看到新增加了exec系统调用。


    启动内核,使用help命令可以发现增加了exec指令,执行exec指令发现比fork指令增加了一行输出“hello word!”。实际上是新加载了一个可执行程序来输出一行语句。

    返回到LinuxKernel目录下,启动内核。在进行gdb调试之前先启动gdb,把3.18.6的内核加载进来,之后连接到target remote 1234。

    设置断点:


    执行到 start_thread 处的断点,使用 po new_ip 指令打印其指向的地址:

    退出调试状态,输入readelf -h hello可以查看hello的EIF头部

    参考资料连接:https://www.cnblogs.com/feifanrensheng/p/10039959.html

  • 相关阅读:
    2020年12月-第02阶段-前端基础-品优购项目规范
    2020年12月-第02阶段-前端基础-CSS Day07
    2020年12月-第02阶段-前端基础-CSS Day06
    2020年12月-第02阶段-前端基础-CSS Day05
    2020年12月-第02阶段-前端基础-CSS Day04
    2020年12月-第02阶段-前端基础-CSS Day03
    2020年12月-第02阶段-前端基础-CSS Day02
    2020年12月-第02阶段-前端基础-CSS字体样式
    2020年12月-第02阶段-前端基础-CSS基础选择器
    2020年12月-第02阶段-前端基础-CSS初识
  • 原文地址:https://www.cnblogs.com/yangdd/p/11813969.html
Copyright © 2011-2022 走看看