赞
踩
ELF的英文全称是The Executable and Linking Format,最初是由UNIX系统实验室开发、发布的ABI(Application Binary Interface)接口的一部分,也是Linux的主要可执行文件格式。
比如说用户空间的.out就是一个ELF的文件
一个程序的3个基本段:text段,data段,bss段。
text段:就是放程序代码的,编译时确定,只读;
data段:存放在编译阶段(而非运行时)就能确定的数据,可读可写。也就是通常所说的静态存储区,赋了初值的全局变量和赋初值的静态变量存放在这个区域,常量也存放在这个区域;
bss段:定义而没有赋初值的全局变量和静态变量,放在这个区域;比如我定义了一个全局变量int a;但是我没有给它赋值。那么就是在这个段。
dec和hex是总的大小,不同的进制。
如果你定义了一个局部变量,不会占用ELF文件的空间(也就是你size一下是看不到这个占的空间在哪),因为它是程序运行的时候储存在栈里面的,编译的时候是没有空间的。
// main.c
#include <stdio.h>
int global_var = 10;
void foo() {
printf("Hello, World!\n");
}
int main() {
foo();
printf("Global variable: %d\n", global_var);
return 0;
}
编译完成用size查看大小
jubuntu@ubuntu:~$ size test100
text data bss dec hex filename
1765 612 4 2381 94d test100
这里为什么bss为4我也不知道?GPT给出的答案是
如果通过size命令显示**.bss**段的大小为4,可能有以下几个原因:
太细节我就不深究了,目前还无法把握。那我们换一个程序看一下:
可以看到不同数据占的空间
hjubuntu@ubuntu:~/driver_linux/param$ size modulea.ko
text data bss dec hex filename
614 900 0 1514 5ea modulea.ko
如果ELF文件格式里面没有给局部变量留空间的话,那么程序运行的时候,CPU怎么能够识别到局部变量?我的意思是既然ELF文件里面没有局部变量,那么局部变量是怎么提取到栈里面去的呢?局部变量先前是作为代码指令储存在.text段里面的吗?
比如写一个简单的程序
#include <stdio.h>
#define PI 3.14
int main() {
int i=666;
printf("hello world PI=%f\n",PI);
printf("hello world i=%d\n",i);
return 0;
}
带分号的是语句,预处理指令(#开头)和注释都不会被编译和编程机器码,在编译之前把这些处理掉。
我们把这个预处理一下 gcc -E test1.c -o test1.i
查看.i预处理文件
/*前面省略几百行代码*/
# 5 "test1.c"
int main() {
int i=666;
printf("hello world PI=%f\n",3.14);
printf("hello world i=%d\n",i);
return 0;
}
可以看出头文件被展开(就是省略的几百行代码,这里没有展示),宏定义被替换。(这里面有一个局部变量,int i=666;这个声明语句最终会转化为机器指令储存在.text中,这些带;的语句最终都会转化为机器指令储存在.text中,当函数执行到局部变量的声明语句时,栈指针会被调整以分配适当的空间给局部变量 i。此时,i 的初始值 666(这个值本身) 会被存储在栈上的分配空间中。)
现在我们把它.i文件生成.s文件(汇编文件)
gcc -S test1.i -o test1.s
查看文件,一些汇编代码(下面展示的是部分)
main: .LFB0: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $666, -4(%rbp) movq .LC0(%rip), %rax movq %rax, %xmm0 leaq .LC1(%rip), %rdi movl $1, %eax call printf@PLT movl -4(%rbp), %eax movl %eax, %esi leaq .LC2(%rip), %rdi movl $0, %eax call printf@PLT movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc
可以看到,局部变量的声明也被转化为了汇编语言
最后再把test1.s生成test1.o
gcc -c test1.s -o test1.o
gedit查看二进制不方便,这里不展示
接下来是链接,我们看一下链接是咋个回事
链接静态库、动态库,多文件编译多文件编译
:
比如你在v.c里面定义了一个全局变量i
int i =100;
#include <stdio.h> #define PI 3.14 extern int i; int main() { printf("before declaring the local variation i=%d\n",i); int i=666; printf("hello world PI=%f\n",PI); printf("after declaring the local variation i=%d\n",i); return 0; }
然后运行如下shell命令,先把他们生成.o文件再链接
hjubuntu@ubuntu:~/test$ gcc -c v.c -o v.o
hjubuntu@ubuntu:~/test$ gcc -c test1.c -o test1.o
hjubuntu@ubuntu:~/test$ gcc test1.0 v.o -o test
gcc: error: test1.0: 没有那个文件或目录
hjubuntu@ubuntu:~/test$ gcc test1.o v.o -o test
hjubuntu@ubuntu:~/test$ ./test
before declaring the local variation i=100
hello world PI=3.140000
after declaring the local variation i=666
hjubuntu@ubuntu:~/test$
这里面有个小知识点,就是当全局变量和局部变量同时存在的时候(比如i有全局定义和局部定义),优先选用局部变量。链接静态库/动态库:
看下面第三节
为什么浪费空间,你想啊,假如你不同的可执行文件都需要一个同样的库,那么你在每一个可执行文件里面都需要粘贴上这个库,当然占空间
总的流程如下:
这个程序是不能直接编译运行的,因为没有main函数。只能生成一个.o文件,是不能执行的。现在需要创建成库文件。
库文件以lib开头,.a为后缀,由.o文件生成,.o文件由.c文件生成
这上面的.1我也不知道是啥,视频里面敲的时候也没有加.1
这上面这所以报错是因为不会在当前目录找这个文件(而是在/usr/lib或者/lib),有以下几个方法
推荐使用环境变量
总的流程如下:
动态库的后缀是.so
也可以用绝对路径<br />![image.png](https://img-blog.csdnimg.cn/img_convert/e42db3fa2450b0ce0798cd7f202cca6f.png#averageHue=#302e28&clientId=u9a3c3688-de81-4&from=paste&height=57&id=u8eec3117&originHeight=78&originWidth=1050&originalType=binary&ratio=2&rotation=0&showTitle=false&size=69493&status=done&style=none&taskId=ua6548f97-8d32-40c7-89bb-16bb5901126&title=&width=764)
如果你在新的窗口打开又不行了
需要重新设置环境变量,因此可以在bashrc文件末尾加上,之后再source一下(每次修改.bashrc
后,使用source就可以立刻加载修改后的设置,使之生效。)
export LD_LIBRARY_PATH=/mnt/hgfs/share/newIOP/day5
每次启动自动设置环境变量。这样子的话可以环境变量在任何一个窗口都有效了
.bashrc
是home目录下的一个shell文件,用于储存用户的个性化设置。在bash每次启动时都会加载
标记的就是上面讲的库文件,其他的可能是一些标准库文件啥的吧。
目标文件是编译器生成的中间文件,它包含了编译后的代码和相关的元数据。目标文件的具体格式取决于所使用的目标文件格式(如ELF、COFF等),不同格式的目标文件有不同的结构和特点。
对于ELF目标文件(常用于Linux系统),它通常由多个节(section)组成,每个节负责存储不同类型的信息。一些常见的节包括:
以上只是一些常见的节,目标文件的具体结构可能更加复杂。
对于未解析符号引用,它指的是在目标文件中遇到的符号引用,但没有找到对应的定义。这些引用通常是由于目标文件引用了其他模块或库中定义的符号,但在当前目标文件中无法解析。这可能是因为目标文件还没有与其他模块进行链接,链接器尚未处理符号解析。
在链接过程中,链接器会尝试解析这些未定义的符号引用。它会搜索其他目标文件、库文件和系统提供的符号表,以找到对应的符号定义。一旦找到匹配的符号定义,链接器会将未解析的符号引用与定义进行关联,并进行正确的地址重定位。
(这一块可以看一下模块参数的文档,一个模块中定义了变量,导出到符号表之后,可以给别的模块使用)
总之,目标文件是编译器生成的中间文件,它包含了编译后的代码和相关的元数据。未解析符号引用是指目标文件中遇到的符号引用,但在当前目标文件中找不到对应的定义。链接过程会尝试解析这些未定义的符号引用,并与定义进行匹配和关联。
开头图片里面的symtab就是符号表(里面有些函数名,汇编标识符啥的)
之前用size是查看能够在CPU上运行的数据(可以加载到内存给CPU运行的),可以理解成就是data,bss,text这些吧,但是实际上一个ELF程序是不止这些的。可以用ll filename查看。
nm可以列出文件的符号表
hjubuntu@ubuntu:~/driver_linux/param$ nm modulea.ko 0000000000000000 T cleanup_module 000000008ccc5fc0 A __crc_gx U __fentry__ 0000000000000000 D gx 0000000000000000 T init_module 0000000000000000 r __kstrtab_gx 0000000000000003 r __kstrtabns_gx 0000000000000000 r __ksymtab_gx 0000000000000000 T modulea_exit 0000000000000000 T modulea_init 0000000000000018 r _note_8 0000000000000000 r _note_9 U _printk 0000000000000000 D __this_module 000000000000002f r __UNIQUE_ID_depends120 0000000000000000 r __UNIQUE_ID_license118 0000000000000044 r __UNIQUE_ID_name118 0000000000000038 r __UNIQUE_ID_retpoline119 000000000000000c r __UNIQUE_ID_srcversion121 0000000000000051 r __UNIQUE_ID_vermagic117 0000000000000000 r ____versions U __x86_return_thunk
通过strip命令可以把符号表删除掉,给文件瘦身。
CPU在执行程序的时候,只有text,data,bss会被加载到内存,但是编译的时候符号表又会被保存到ELF文件的格式里面,所以会导致ELF文件比较大,所以符号表删掉对文件本身的运行时没有什么影响的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。