ELF文件的主体是各类节,以及描述这些节属性的信息(Program header table和 Section header table),以及ELF文件的总体性信息(ELF header),以下图。git
程序从源代码到可执行文件的步骤:预处理、编译、汇编、衔接--以hello.c为例。github
预处理: 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文件就是可执行文件。可执行文件中的内容包括有编译后的机器指令代码、数据还包括了连接时所需要的一些信息,好比符号表、调试信息、字符串等。
其文件格式,以下图。编程
静态连接:在编译连接时直接将须要的执行代码复制到最终可执行文件中,优势是代码的装载速度快,执行速度也比较快,对外部环境依赖度低。缺点是若是多个应用程序使用同一库函数,会被装载屡次,浪费内存。
动态连接:编译时不直接复制可执行代码,而是经过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操做系统。操做系统负责将须要的动态库加载到内存中,在程序运行到指定的代码时,去共享执行内存中已经加载的动态库去执行代码,最终达到运行时连接的目的。优势是多个程序能够共享同一段代码,而不须要在磁盘上存储多个复制。缺点是在运行时加载可能会影响程序的前期执行性能,并且对使用的库依赖性较高。(分为装载时动态连接和运行时动态连接)vim
使用gcc hello.o -o hello.static -static进行静态连接,发现获得的可执行程序文件大小可达到动态连接的100倍,如图。编辑器
动态衔接分为如下两种;函数
装载时动态衔接意味着在程序一开始启动的时候其所调用的库须要在一开始就提供,而运行时动态衔接只有程序运行到相关语句才会访问dllibexample。性能
对于fork():
一、子进程复制父进程的全部进程内存到其内存地址空间中。父、子进程的
“数据段”,“堆栈段”和“代码段”彻底相同,即子进程中的每个字节都
和父进程同样。
二、子进程的当前工做目录、umask掩码值和父进程相同,fork()以前父进程
打开的文件描述符,在子进程中一样打开,而且都指向相同的文件表项。
三、子进程拥有本身的进程ID。测试
对于exec():
一、进程调用exec()后,将在同一块进程内存里用一个新程序来代替调用
exec()的那个进程,新程序代替当前进程映像,当前进程的“数据段”,
“堆栈段”和“代码段”背新程序改写。
二、新程序会保持调用exec()进程的ID不变。
三、调用exec()以前打开打开的描述字继续打开(好像有什么参数能够令打开
的描述字在新程序中关闭)网站
编程使用exec* 库函数加载一个可执行文件,动态连接分为可执行程序装载时动态连接和运行时动态连接,编程练习动态连接库的这两种使用方式。操作系统
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; }
$ gcc -shared shlibexample.c -o libshlibexample.so -m32 $ gcc -shared dllibexample.c -o libdllibexample.so -m32
错误到这里就显现出来了,为何会报错我在网上查了不少答案,什么须要更新gcc啊,什么C文件头名字冲突啊,反正各类答案,耗费了好久时间就是依旧会报错,因而我从新打了好几遍代码,直到发现有一个贼细小的地方,书上很容易误导。
注意看!这分明看着是就是LIB对吧,我在vim编辑器中敲的时候也和书上的样子差很少,然而我仔细看了一下I的上横和下横仍是有点长短不一的,这货该不会是T吧!!,结果事实验证了个人判断,这货就是T,LTB。终于找到缘由了
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; }
由于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
使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve ,验证您对Linux系统加载可执行程序所需处理过程的理解。
实验二就只能在实验楼环境作了,毕竟本地虚拟机没有配好的环境。
ls cd ~/LinuxKernel rm menu -rf git clone https://github.com/mengning/menu.git cd menu mv test_exec.c test.c make rootfs