牵扯到ELF格式,gcc编译选项待补,简单实用的说明一下,对Linux下的so文件有个实际性的认识。
1.so文件是什么?
2.怎么生成以及使用一个so动态库文件?
3.地址空间,以及线程安全.
4.库的初始化,解析:
5.使用我们自己库里的函数替换系统函数:
//-------------------------------------------------------------------------------
1.so文件是什么?
也是ELF格式文件,共享库(动态库),类似于DLL。节约资源,加快速度,代码升级简化。
知道这么多就够了,实用主义。等有了印象再研究原理。
2.怎么生成以及使用一个so动态库文件?
先写一个C文件:s.c
- #include <stdio.h>
- int count;
- void out_msg(const char *m)
- {//2秒钟输出1次信息,并计数
- for(;;) {printf("%s %d\n", m, ++count); sleep(2);}
- }
编译:得到输出文件libs.o
gcc -fPIC -g -c s.c -o libs.o
链接:得到输出文件libs.so
gcc -g -shared -Wl,-soname,libs.so -o libs.so libs.o -lc
(注释:-lc选项,表示使用c语言库,一般都要用到)
-l参数和-L参数
-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文件名有什么关系呢?
就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的头lib和尾.so去掉就是库名
了。
如:
gcc xxx.c -lm( 动态数学库)
-lpthread
一个头文件:s.h
- #ifndef _MY_SO_HEADER_
- #define _MY_SO_HEADER_
- void out_msg(const char *m);
- #endif
再来一个C文件来引用这个库中的函数:ts.c
PS:在你的应用程序中直接调用你库中的函数,当然你 得把库的头文件包含到你的应用程序中。
- #include <stdio.h>
- #include "s.h"
- int main(int argc, char** argv)
- {
- printf("TS Main\n");
- out_msg("TS ");
- sleep(5); //这句话可以注释掉,在第4节的时候打开就可以。
- printf("TS Quit\n");
- }
编译链接这个文件:得到输出文件ts
gcc -g ts.c -o ts -L. -ls
PS: 此处-ls ,因为生成的库是libs.so ,除去前缀lib,和后缀so,故而就是-ls了。
得到了ts:error while loading shared libraries: libs.so: cannot open shared object file: No such file or directory
系统不能找到我们自己定义的libs.so,那么告诉他,修改变量LD_LIBRARY_PATH,为了方便,写个脚本:e(文件名就叫e,懒得弄长了)
#!/bin/sh
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
执行:./e &
屏幕上就开始不停有信息输出了,当然TS Quit你是看不到的,前面是个死循环,后面会用到这句
3.地址空间,以及线程安全:
如果这样:
./e &开始执行后,稍微等待一下然后再 ./e&,
这个时候屏幕信息会怎么样呢?全局变量count会怎么变化?
会是两个进程交叉输出信息,并且各自的count互不干扰,虽然他们引用了同一个so文件。
也就是说只有代码是否线程安全一说,没有代码是否是进程安全这一说法。
4.库的初始化,解析:
windows下的动态库加载,卸载都会有初始化函数以及卸载函数来完成库的初始化以及资源回收,linux当然也可以实现。
ELF文件本身执行时就会执行一个_init()函数以及_fini()函数来完成这个,我们只要把自己的函数能让系统在这个时候执行
就可以了。
修改我们前面的s.c文件:
- #include <stdio.h>
- void my_init(void) __attribute__((constructor)); //告诉gcc把这个函数扔到init section
- void my_fini(void) __attribute__((destructor)); //告诉gcc把这个函数扔到fini section
- void out_msg(const char *m)
- {
- printf(" Ok!\n");
- }
- int i; //仍然是个计数器
- void my_init(void)
- {
- printf("Init ... ... %d\n", ++i);
- }
- void my_fini(void)
- {
- printf("Fini ... ... %d\n", ++i);
- }
重新制作 libs.so,ts本是不用重新编译了,代码维护升级方便很多。
然后执行: ./e &
可以看到屏幕输出:(不完整信息,只是顺序一样)
Init
Main
OK
Quit
Fini
可以看到我们自己定义的初始化函数以及解析函数都被执行了,而且是在最前面以及最后面。
如果s.c中的sleep(5)没有注释掉,那么有机会:
./e&
./e&连续执行两次,那么初始化函数和解析函数也会执行两次,虽然系统只加载了一次libs.so。
如果sleep时候kill 掉后台进程,那么解析函数不会被执行。
5.使用我们自己库里的函数替换系统函数:
创建一个新的文件b.c:我们要替换系统函数malloc以及free(可以自己写个内存泄露检测工具了)
- #include <stdio.h>
- void* malloc(int size)
- {
- printf("My malloc\n");
- return NULL;
- }
- void free(void* ad)
- {
- printf("My free\n");
- }
老规矩,编译链接成一个so文件:得到libb.so
gcc -fPIC -g -c b.c -o libb.o
gcc -g -shared -Wl,-soname,libb.so -o libb.so -lc
修改s.c:重新生成libs.so
- void out_msg()
- {
- int *p;
- p = (int*)malloc(100);
- free(p);
- printf("Stop Ok!\n");
- }
修改脚本文件e:
#!/bin/sh
export LD_PRELOAD=${pwd}libb.so:${LD_PRELOAD}
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
关键就在LD_PRELOAD上了,这个路径指定的so将在所有的so之前加载,并且符号会覆盖后面加载的so文件中的符号。如果可执行文件的权限不合适(SID),这个变量会被忽略。
执行:./e &
嗯,可以看到我们的malloc,free工作了。
暂时就想到这么多了。
编译阶段gcc
正文:编译流程分析:
编译分为几个过程:
以下分析,如何处理各个阶段:
Gcc指令的一般格式为 Gcc[选项] 要编译的文件 [选项][目标文件]
其中,目标文件可以缺省 Gcc默认生成可执行文件 命名编译文件.out
Gcc -E hello.c -o hello.i
2.编译阶段 Gcc -S hello.i -o hello.s
3.汇编阶段就是生成二进制代码的时候 Gcc -c hello.s -o hello.o
4.链接阶段
这个程序之中并没有定义printf的函数实现、在预处理阶段包含了stdio.h也只是函数的生命。而没有定义函数的实现。系统吧函数的实现都被做到名为libc.so.6的函数文件之中去了。在没有特定的指出是。Gcc就到系统默认的搜索路径/usr/lib下查找
链接过程分为动态链接还有静态链接两种形式。静态链接的文件是.a的形式。动态链接的形式是.so的形式
libc.so.6就是动态链接库,GCC一般都是默认使用的是动态链接库
后面还要用到
gcc hello.o -o hello
./hello
总共有的步骤
Gcc -E hello.c -o hello.i
Gcc -S hello.i -o hello.s
Gcc -c hello.s -o hello.o
gcc hello.o -o hello
./hello
条件讲解
-c之编译不链接 生成的是目标代码 .o文件
-S之编译不汇编生成的是汇编代码
-E只进行预编译。不做其他处理
-g在可执行程序中包含便准调试信息
-o file 把输出文件输出到fill里面
-v 打印出编译器内部编译过程的命令行信息和编译器的版本
-I dir 在头文件的搜索路径列表中添加dir目录
-L dir 在库文件的搜索路径列表中添加dir目录
-static 链接静态库
-llibrary 连接名为library的库文件
Linux之中头文件默认放在/usr/include/目录下。因此当用户希望添加放置在其他位置的头文件时候。就需要透过 -I dir选项来指定 这样
eg Gcc hello.c -I/root/workplace/Gcc/ -o hello1
这样Gcc就可以执行正确结果了
注意事项include之中<>是表示在标准路径之中搜索文件。而""是表示在本目录之中搜索文件。所以也可以用"my.h" 从而就不用在写-I的问题了
哈哈哈 以前C语言老师讲过哈 没有在意 。。没想到 还挺有用的 我觉得他好强大啊
L-dir指定的是动态链接库的路径。需要注意的是
-I dir 还有 -L dir 都是指定路径 没有指定文件。因此不能再路径之中包含文件名
没有看的:
Gcc警告的问题
GCC优化选项
GCC体系结构相关选项
[root@localhost Desktop]# gcc -E hello.c -o hello.i
gcc: hello.c:没有那个文件或目录
gcc: 没有输入文件
[root@localhost Desktop]# gcc -E helllo.c -o helllo.i
[root@localhost Desktop]# gcc -S helllo.i -o helllo.s
[root@localhost Desktop]# gcc -c helllo.s -o helllo.o
[root@localhost Desktop]# gcc helllo.o -o helllo
[root@localhost Desktop]# helllo
bash: helllo: command not found
[root@localhost Desktop]# ./helllo//默认的寻找目录是usr/bin 所以要入./
i like china[root@localhost Desktop]#
[root@localhost Desktop]# sl
bash: sl: command not found
[root@localhost Desktop]# ls
gnome-terminal.desktop
helllo
#helllo.c#
helllo.c
helllo.c~
[root@localhost Desktop]# ls
gnome-terminal.desktop
helllo
#helllo.c#
helllo.c
helllo.c~
[root@localhost Desktop]# helllo
bash: helllo: command not found
[root@localhost Desktop]# ./helllo
i like china[root@localhost Desktop]# cd