Linux静态库动态库的编译

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为例:

  1. 编译c文件-c生成.o文件
  2. 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。
WThe 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

制作步骤

  1. 生成与位置无关的.ogcc -fPIC -c *.c
  2. .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在自定义目录中,那么有三种方法来配置:

  1. 临时提供一个环境变量export LD_LIBRARY_PATH=lib路径,路径可以是绝对路径,也可以是相对路径;
  2. 把上一个方法的命令添加进.bashrc,路径得是绝对路径;
  3. 把动态库的路径写进动态链接器的配置文件/etc/ld.so.confsudo 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文档:

主程序源码:

#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.csdn.net/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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值