0. 源码
目录:
$ tree CTest/
CTest/
├── include
│ └── foo.h
├── lib
├── main.c
├── Makefile
└── src
├── foo.c
└── Makefile
库
foo.h:
#include <stdio.h>
int g_nShared;
int g_nTmp;
void funcExchange(int *a, int *b);
typedef void (*PFuncExchange)(int *a, int *b);
foo.c:
#include "foo.h"
int g_nShared = 1;
int g_nTmp = 0;
void funcExchange(int *a, int *b)
{
if(!a || !b) return;
g_nTmp = *a;
*a = *b;
*b = g_nTmp;
printf("I'm funcExchange from libFoo\n");
}
主程序
main.c:
#include "foo.h"
int main(void) {
int n = 2;
funcExchange(&n, &g_nShared);
printf("n==%d\n", n);
printf("g_nShared==%d\n", g_nShared);
printf("g_nTmp==%d\n", g_nTmp);
return 0;
}
1. 静态库
命名规则:lib
+库的名字+.a
发布时除了.a
,还要有头文件以提供接口查询。
制作步骤
以库test为例:
- 编译c文件
-c
生成.o
文件 - src目录下,
ar rcs libtest.a *.o
手工编译:
gcc -g -c ./src/foo.c -I./include -o ./src/foo.o
ar src libFoo.a ./src/*.o
gcc -g -static main.c -I./include/ -L./ -lFoo -o s_main
然后学一下nm
(names)命令,nm litest.a
可以查看有哪些.o
文件;也可以跟可执行文件nm myapp
。
$ nm libFoo.a
foo.o:
0000000000000000 T funcExchange
U _GLOBAL_OFFSET_TABLE_
0000000000000000 D g_nShared
0000000000000000 B g_nTmp
U puts
$ ar -t libFoo.a
foo.o
$ nm s_main
0000000000201014 B __bss_start
0000000000201014 b completed.7698
w __cxa_finalize@@GLIBC_2.2.5
0000000000201000 D __data_start
0000000000201000 W data_start
0000000000000620 t deregister_tm_clones
00000000000006b0 t __do_global_dtors_aux
0000000000200db0 t __do_global_dtors_aux_fini_array_entry
0000000000201008 D __dso_handle
0000000000200db8 d _DYNAMIC
0000000000201014 D _edata
0000000000201020 B _end
0000000000000864 T _fini
00000000000006f0 t frame_dummy
0000000000200da8 t __frame_dummy_init_array_entry
0000000000000a1c r __FRAME_END__
000000000000078e T funcExchange
0000000000200fa8 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000201010 D g_nShared
0000000000201018 B g_nTmp
00000000000008b4 r __GNU_EH_FRAME_HDR
0000000000000580 T _init
0000000000200db0 t __init_array_end
0000000000200da8 t __init_array_start
0000000000000870 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
0000000000000860 T __libc_csu_fini
00000000000007f0 T __libc_csu_init
U __libc_start_main@@GLIBC_2.2.5
00000000000006fa T main
U printf@@GLIBC_2.2.5
U puts@@GLIBC_2.2.5
0000000000000660 t register_tm_clones
U __stack_chk_fail@@GLIBC_2.4
00000000000005f0 T _start
0000000000201018 D __TMC_END__
中间这一列,符号类型解释如下(可以man nm查询):
符号类型 | 说明 |
---|---|
A | 该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。 |
B | 该符号的值出现在非初始化数据段(bss)中。例如,在一个文件中定义全局static int test。则该符号test的类型为b,位于bss section中。其值表示该符号在bss段中的偏移。一般而言,bss段分配于RAM中 |
C | 该符号为common。common symbol是未初始话数据段。该符号没有包含于一个普通section中。只有在链接过程中才进行分配。符号的值表示该符号需要的字节数。例如在一个c文件中,定义int test,并且该符号在别的地方会被引用,则该符号类型即为C。否则其类型为B。 |
D | 该符号位于初始话数据段中。一般来说,分配到data section中。例如定义全局int baud_table[5] = {9600, 19200, 38400, 57600, 115200},则会分配于初始化数据段中。 |
G | 该符号也位于初始化数据段中。主要用于small object提高访问small data object的一种方式。 |
I | 该符号是对另一个符号的间接引用。 |
N | 该符号是一个debugging符号。 |
R | 该符号位于只读数据区。例如定义全局const int test[] = {123, 123};则test就是一个只读数据区的符号。注意在cygwin下如果使用gcc直接编译成MZ格式时,源文件中的test对应_test,并且其符号类型为D,即初始化数据段中。但是如果使用m6812-elf-gcc这样的交叉编译工具,源文件中的test对应目标文件的test,即没有添加下划线,并且其符号类型为R。一般而言,位于rodata section。值得注意的是,如果在一个函数中定义const char *test = “abc”, const char test_int = 3。使用nm都不会得到符号信息,但是字符串“abc”分配于只读存储器中,test在rodata section中,大小为4。 |
S | 符号位于非初始化数据区,用于small object。 |
T | 该符号位于代码区text section。 |
U | 该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。 |
V | 该符号是一个weak object。 |
W | The symbol is a weak symbol that has not been specifically tagged as a weak object symbol. |
- | 该符号是a.out格式文件中的stabs symbol。 |
? | 该符号类型没有定义 |
Makefile
src/Makefile:
TARGET = libFoo.a
CC = gcc
CCFLAGS = -c -g
INCLUDEPATH = -I../include
AR = ar
ARFLAG = rcs
LIBPATH = ../lib/
OBJ = foo.o
all : $(TARGET)
$(TARGET): $(OBJ)
$(AR) $(ARFLAG) $(LIBPATH)$(TARGET) *.o
$(OBJ): foo.c
$(CC) $(CCFLAGS) $(INCLUDEPATH) foo.c -o $(OBJ)
.phony: clean
clean:
rm $(OBJ)
rm $(LIBPATH)$(TARGET)
根目录makefile:
CC = gcc
TARGET = s_main
CFLAGS = -static -g
INCLUDE = -I./include/
LDFLAGS = -L./lib/
LIBS = -lFoo
LIB = libFoo.a
SUBDIR = ./src
main: main.c $(LIB)
$(CC) $(CFLAGS) $(INCLUDE) $(LDFLAGS) main.c $(LIBS) -o $(TARGET)
$(LIB):
cd $(SUBDIR) && $(MAKE)
.phony: clean
clean:
rm $(TARGET)
cd $(SUBDIR) && $(MAKE) clean
优缺点
优点:发布程序时不需提供库;加载库快。
缺点:程序大;升级时要重新编译。
所以,制作.a
要选择好该加入哪些.o文件。
2. 动态库
命名规则:lib
+库的名字+.so
制作步骤
- 生成与位置无关的
.o
,gcc -fPIC -c *.c
- 将
.o
打包成共享库,gcc -shared -o libFoo.so *.o [-I ../include]
可以合成一句:gcc -shared -fpic foo.c -o libFoo.so
同样移到库目录,把头文件和动态库发布给用户。
使用so:gcc main.c -I include libFoo.so -o main
手工编译:
gcc -g -shared -fpic src/foo.c -I./include -o libFoo.so
gcc main.c ./libFoo.so -I./include -o main # 错误方法
关于PIC和GOT,参考另一篇笔记:ELF链接原理
可以用ldd
查看so
;windows中可以用depends.exe
查看dll
。
$ ldd libFoo.so
linux-vdso.so.1 (0x00007ffe11bf4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a6b172000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8a6b765000)
$ ldd main
linux-vdso.so.1 (0x00007ffc5c7e9000)
./libFoo.so (0x00007f8a43c6c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a4387b000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8a44070000)
上面这样是在编译时指定了so的绝对路径,这样不太讲究,通常应该用-l链接:
$ gcc main.c -I./include/ -L./lib/ -lFoo -o main # 正确方法
$ ldd main
linux-vdso.so.1 (0x00007ffffb98f000)
libFoo.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb890cbf000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb8912b2000)
我们的自定义库没有被找到,因为它不在/usr/lib, /usr/local/lib , /usr/lib64
中。
可以看到动态链接器ld-linux-x86-64.so
,就是它根据$PATH
调用其它动态库。
再看看其它的依赖库。linux-vdso.so是驻留在内存中的,vdso即虚拟动态共享库,没有实际文件。
如果so在自定义目录中,那么有三种方法来配置:
- 临时提供一个环境变量
export LD_LIBRARY_PATH=lib路径
,路径可以是绝对路径,也可以是相对路径; - 把上一个方法的命令添加进
.bashrc
,路径得是绝对路径; - 把动态库的路径写进动态链接器的配置文件
/etc/ld.so.conf
,sudo ldconfig [-v]
更新。
通常使用第3种方法。这里用第一种测试即可:
$ export LD_LIBRARY_PATH=./lib/
$ ldd main
linux-vdso.so.1 (0x00007ffd33bf3000)
libFoo.so => ./lib/libFoo.so (0x00007fcc7c32a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fcc7bf39000)
/lib64/ld-linux-x86-64.so.2 (0x00007fcc7c72e000)
现在,就不会显示not found
了。
也不要把库直接复制进用户的lib目录,可能覆盖重名库。
另外还有一个环境变量叫LIBRARY_PATH
, 用于在编译期间指定查找共享库的路径,是给gcc等编译器用的,而LD_LIBRARY_PATH
是运行期间,给ld.so动态链接器用的。
Makefile
src/makefile:
TARGET = libFoo.so
CC = gcc
CCFLAGS = -g -shared -fpic
INCLUDEPATH = -I../include
LIBPATH = ../lib/
all : $(TARGET)
$(TARGET): $(OBJ)
$(CC) $(CCFLAGS) $(INCLUDEPATH) foo.c -o $(LIBPATH)$(TARGET)
.phony: clean
clean:
rm $(LIBPATH)$(TARGET)
根目录下makefile:
CC = gcc
TARGET = main
CFLAGS = -g
INCLUDE = -I./include/
LDFLAGS = -L./lib/
LIBS = -lFoo
LIB = libFoo.so
SUBDIR = ./src
main: main.c $(LIB)
$(CC) $(CFLAGS) $(INCLUDE) $(LDFLAGS) main.c $(LIBS) -o $(TARGET)
$(LIB):
cd $(SUBDIR) && $(MAKE)
.phony: clean
clean:
rm $(TARGET)
cd $(SUBDIR) && $(MAKE) clean
动态加载
相关api文档:
- dlopen(3) - Linux manual page (man7.org),这里还有一个Demo供参考,并且注意依赖库
Link with -ldl.
; - dlsym(3) - Linux manual page (man7.org)
主程序源码:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "foo.h"
int main(void)
{
void *handle = NULL;
PFuncExchange pFuncExchange = NULL;
char *error = NULL;
int n = 2;
handle = dlopen("libFoo.so", RTLD_LAZY); // https://blog.youkuaiyun.com/Ga4ra/article/details/123515542
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror(); /* Clear any existing error */
pFuncExchange = (PFuncExchange) dlsym(handle, "funcExchange");
error = dlerror();
if (error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
pFuncExchange(&n, &g_nShared);
printf("n==%d\n", n);
printf("g_nShared==%d\n", g_nShared);
printf("g_nTmp==%d\n", g_nTmp);
dlclose(handle);
return EXIT_SUCCESS;
}
手动编译:
$ gcc -g main.c -I./include/ -L./lib/ -lFoo -ldl -o main
$ ldd main
linux-vdso.so.1 (0x00007ffff4b5f000)
libFoo.so => ./lib/libFoo.so (0x00007f4b888bf000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4b886bb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4b882ca000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4b88cc4000)
$ ./main
I'm funcExchange from libFoo
n==1
g_nShared==2
g_nTmp==2
makefile加上对libdl.so的引用:
CC = gcc
TARGET = main
CFLAGS = -g
INCLUDE = -I./include/
LDFLAGS = -L./lib/
LIBS = -lFoo -ldl
LIB = libFoo.so
SUBDIR = ./src
main: main.c $(LIB)
$(CC) $(CFLAGS) $(INCLUDE) $(LDFLAGS) main.c $(LIBS) -o $(TARGET)
$(LIB):
cd $(SUBDIR) && $(MAKE)
.phony: clean
clean:
rm $(TARGET)
cd $(SUBDIR) && $(MAKE) clean