linux什么是SO⽂件
so其实就是shared object的意思。今天看了上⾯的博客,感觉好吃⼒。赶紧做个笔记记录⼀下。下⾯的内容⼤多都是连接中的,穿插我⾃⼰的笔记
牵扯到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
----------------------------------------------------------------------
:
-fPIC作⽤于编译阶段,告诉编译器产⽣与位置⽆关代码(Position-Independent Code),则产⽣的代码中,没有绝对地址,全部使⽤相对地址,故⽽代码可以被加载器加载到内存的任意 位置,都可以正确的执⾏。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
: 令 gcc ⽣成调试信息,该选项可以利⽤操作系统的“原⽣格式(native format)”⽣成调试信息。GDB 可以直接利⽤这个信息,其它调试器也可以使⽤这个调试信息
-c: 仅执⾏编译操作,不进⾏连接操作。
-o: 指定⽣成的输出⽂件名称
注意!-c,-o不是指.c⽂件和.o⽂件!!
----------------------------------------------------------------------
链接:得到输出⽂件libs.so
gcc -g -shared -Wl,-soname,libs.so -o libs.so libs.o -lc
-----------------------------------------------------------------------
上述语句中 libs.o是输⼊⽂件
-shared:
Produce a shared object which can then be linked with other objects to form an executable. Not all systems support
this option. For predictable results, you must also specify the same set of options used for compilation (-fpic, -fPIC, or model suboptions) when you specify this linker option.
-Wl: 注意第⼆个字母是⼩写的L,不是I
Pass option as an option to the linker. If option contains commas, it is split into multiple options at the commas. You can use this syntax to pass an argument to the option. For example, -Wl,-Map,output.map passes -Map output.map to the linker. When using the GNU linker, you can also get the same effect with -Wl,-Map=output.map.
:
soname的关键功能是它提供了兼容性的标准:
当要升级系统中的⼀个库时,并且新库的soname和⽼库的soname⼀样,⽤旧库链接⽣成的程序使⽤新库依然能正常运⾏。这个特性使得在Linux下,升级使得共享库的程序和定位错误变得⼗分容易。
在Linux中,应⽤程序通过使⽤soname,来指定所希望库的版本,库作者可以通过保留或改变soname来声明,哪些版本是兼容的,这使得程序员摆脱了共享库版本冲突问题的困扰。
-lc:
-l 是直接加上某库的名称,如-lc是libc库 -L 是库的路径,搜索的时候优先在-L⽬录下搜索
------------------------------------------------------------------------
⼀个头⽂件:s.h
#ifndef _MY_SO_HEADER_
#define _MY_SO_HEADER_
void out_msg(const char *m);
#endif
再来⼀个C⽂件来引⽤这个库中的函数:ts.c
#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
执⾏./ts,嗯:成功了。。。还差点
得到了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,懒得弄长了)
export LD_LIBRARY_PATH=${pwd}:${LD_LIBRARY_PATH}
./ts
执⾏:./e &
屏幕上就开始不停有信息输出了,当然TS Quit你是看不到的,前⾯是个死循环,后⾯会⽤到这句
----------------------
& 放在启动参数后⾯表⽰设置此进程为后台进程。默认情况下,进程是前台进程,这时就把Shell给占据了,我们⽆法进⾏其他操作,对于那些没有交互的进程,很多时候,我们希望将其在后台启动,可以在启动参数的时候加⼀个'&'实现这个⽬的。
----------------------
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
可以看到我们⾃⼰定义的初始化函数以及解析函数都被执⾏了,⽽且是在最前⾯以及最后⾯。
linux下的sleep函数如果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:
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⼯作了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论