2019-2020-1 20199314 《Linux内核原理与分析》 第八周作业

可执行程序工作原理、

1、ELF(Executable and Linkable Format)可执行和可链接文件,其包含了以下三类:

  1. 可重定位文件(Relocatable File):保存着代码和适当的数据,用来和其它的目标文件一起来创建一个可执行文件、静态库文件或者是一个共享目标文件(主要是.o文件)
  2. 可执行文件(Executable File):保存着一个用来执行的程序,一般由多个可重定位文件结合生成,是完成了所有重定位工作和符号解析(除了运行时解析的共享库符号)的文件。
  3. 共享目标文件(Shared Object File):保存着代码和合适的数据,用来被两个链接器链接。第一个是链接编辑器(静态链接),可以和其它的可重定位和共享目标文件来创建其它的object。第二个是动态链接器,联合一个可执行文件和其它的共享目标文件来创建一个进程映象。

2、ELF文件格式:

ELF文件的主体是各种节,以及描述这些节属性的信息(Program header table和 Section header table),以及ELF文件的整体性信息(ELF header),如下图。

3、程序编译

程序从源代码到可执行文件的步骤:预处理、编译、汇编、衔接--以hello.c为例。

预处理: gcc -E hello.c -o hello.i  -m32
编译:gcc -S hello.i  -o hello.s -m32
汇编:gcc -c hello.s -o hello.o -m32
默认衔接(动态库):gcc hello.o -o hello -m32 
衔接静态库:gcc hello.o -o hello.static -m32 -static

最后得到的hello和hello-static文件就是可执行文件。可执行文件中的内容包括有编译后的机器指令代码、数据还包括了链接时所须要的一些信息,比如符号表、调试信息、字符串等。
其文件格式,如下图。

4、静态衔接和动态衔接

静态链接:在编译链接时直接将需要的执行代码复制到最终可执行文件中,优点是代码的装载速度快,执行速度也比较快,对外部环境依赖度低。缺点是如果多个应用程序使用同一库函数,会被装载多次,浪费内存。
动态链接:编译时不直接复制可执行代码,而是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统。操作系统负责将需要的动态库加载到内存中,在程序运行到指定的代码时,去共享执行内存中已经加载的动态库去执行代码,最终达到运行时链接的目的。优点是多个程序可以共享同一段代码,而不需要在磁盘上存储多个复制。缺点是在运行时加载可能会影响程序的前期执行性能,而且对使用的库依赖性较高。(分为装载时动态链接和运行时动态链接)

使用gcc hello.o -o hello.static -static进行静态链接,发现得到的可执行程序文件大小可达到动态链接的100倍,如图。

5、动态衔接

动态衔接分为以下两种;

  1. 可执行程序装载时动态衔接
    2.运行时动态衔接。

装载时动态衔接意味着在程序一开始启动的时候其所调用的库需要在一开始就提供,而运行时动态衔接只有程序运行到相关语句才会访问dllibexample。

6、fork和execve区别与联系

对于fork():
1、子进程复制父进程的所有进程内存到其内存地址空间中。父、子进程的
“数据段”,“堆栈段”和“代码段”完全相同,即子进程中的每一个字节都
和父进程一样。
2、子进程的当前工作目录、umask掩码值和父进程相同,fork()之前父进程
打开的文件描述符,在子进程中同样打开,并且都指向相同的文件表项。
3、子进程拥有自己的进程ID。

对于exec():
1、进程调用exec()后,将在同一块进程内存里用一个新程序来代替调用
exec()的那个进程,新程序代替当前进程映像,当前进程的“数据段”,
“堆栈段”和“代码段”背新程序改写。
2、新程序会保持调用exec()进程的ID不变。
3、调用exec()之前打开打开的描述字继续打开(好像有什么参数可以令打开
的描述字在新程序中关闭)

实验1:

编程使用exec* 库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接,编程练习动态链接库的这两种使用方式。

1、实验楼上说要从某个网站上下载下面两个源码:

shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example
但是后来查到是网易云付费课的附件,为了两个代码没必要花这么多钱,再说书上已经有源码了,可以自己写一遍,但是书上的代码有一个大坑,直接影响了我后面做实验的进度,之后来我会说明。

shlibexample.h
#ifndef  _SH_LTB_EXAMPLE_H_
#define  _SH_LTB_EXAMPLE_H_
#define SUCCESS 0
#define FAILURE (-1)
#ifdef __cplusplus
extern "C" {
#endif
int SharedLibApi();
#ifdef __cplusplus
}
#endif
#endif

dllibexample.h
#ifndef  _DL_LTB_EXAMPLE_H_
#define  _DL_LTB_EXAMPLE_H_
#ifdef _cplusplus
extern "C"{
#endif
int DynamicalLoadingLibApi();
#ifdef _cplusplus
}
#endif
#endif

shlibexample.c
#include <stdio.h>
#include "shlibexample.h"
int SharedLibApi()
{
printf("This is a shared libary!\n");
return SUCCESS;
}

dllibexample.c
#include <stdio.h>
#include "dllibexample.h"
#define SUCCESS 0
#define FAILURE(-1)
int DynamicalLoadingLibApi()
{
printf(“This is a Dynamical Loading library”!\n”);
return SUCCESS;
}

2、编译成libshlibexample.so文件

$ gcc -shared shlibexample.c -o libshlibexample.so -m32
$ gcc -shared dllibexample.c -o libdllibexample.so -m32

错误到这里就显现出来了,为什么会报错我在网上查了很多答案,什么需要更新gcc啊,什么C文件头名字冲突啊,反正各种答案,耗费了很久时间就是依旧会报错,于是我重新打了好几遍代码,直到发现有一个贼细小的地方,书上很容易误导。

注意看!这分明看着是就是LIB对吧,我在vim编辑器中敲的时候也和书上的样子差不多,然而我仔细看了一下I的上横和下横还是有点长短不一的,这货该不会是T吧!!,结果事实验证了我的判断,这货就是T,LTB。终于找到原因了

3、mian函数中分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件

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

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;
}

4、动态衔接运行测试

因为shilibexample在衔接时就需要提供路径,对应的头文件shilibexample.h也需要在编译器能找到位置。使用参数-L表明文件路径,-l表示库文件名。
dllibexample只有在程序运行到相关语句才会访问,在编译时不需要任何的相关信息,使用-ldl指明其所需要的共享库dlopen,同时修改LD_LIBRARY_PATH确保dllibexample.so可以查到。

gcc main.c -o main -L./ -l shlibexample -ldl -m32
export LD_LIBRARY_PATH=$PWD
./main

实验2:

使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve ,验证您对Linux系统加载可执行程序所需处理过程的理解。

实验二就只能在实验楼环境做了,毕竟本地虚拟机没有配好的环境。

1、首先从github上下载包含test_exec.c文件的文件夹,进行编译。

ls
cd ~/LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu 
mv test_exec.c test.c
make rootfs

2、在qemu界面使用help和exec命令查看是否编译成功。

3、随后重新启动qemu。

4、加载内核和端口。

5、设置三个断点。

6、调试运行程序。

猜你喜欢

转载自www.cnblogs.com/morvalhe/p/11815584.html
今日推荐