如何使用makefile生成.so文件

如何使用makefile生成.so文件

在大型的C/C++工程中,为了节省编译时间,一般都会将一些不会改动的文件生成为动态库(如.so文件),这样可以让链接过程放在运行的时候。
在工程量较少的时候可以自己写语句来生成动态库。但是当工程量很大的时候,包含的文件非常多,自己写语句就会非常麻烦,因此需要一种工具来自动化生成.so文件。

make就是一种可以实现自动化编译的工具,只要你写好了makefile,然后在终端上输入make指令,就可以实现自动编译(make命令会自己去寻找里面的makefile文件)

要谈如何生成so文件,先了解下什么是so动态库文件,和静态lib有什么区别。

1、静态连接和动态连接

静态链接在链接的时候,就把所依赖的第三方库函数都打包到了一起,导致最终的可执行文件非常大。

而动态链接在链接的时候并不将那些库文件直接拿过来,而是在运行时,发现用到某些库中的某些函数时,再从这些第三方库中读取自己所需的方法。

-------这也能说明为什么代码编译是OK的,但是运行时会报未定义(只有函数声明,却缺少函数定义,编译时不报错,运行时才暴露)。

------查找发现makefile编译动态库时,找不到链接符号也是允许编译通过的,那最好是加上限制条件,在makefile加上 -Xlinker –unresolved-symbols=ignore-in-shared-libs ,让其报错,把未定义的符号给报错出来。

ldd命令查看动态链接库依赖

root@10:/home/code/exec/4.makefile# gcc -shared -fPIC -fsanitize=address main.c -o libmain.so
root@10:/home/code/exec/4.makefile# ldd libmain.so
        linux-gate.so.1 (0xb7f1c000)
        libasan.so.6 => /lib/i386-linux-gnu/libasan.so.6 (0xb795d000)
        libdl.so.2 => /lib/i386-linux-gnu/libdl.so.2 (0xb7957000)
        libpthread.so.0 => /lib/i386-linux-gnu/libpthread.so.0 (0xb7935000)
        libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb7831000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7649000)
        /lib/ld-linux.so.2 (0xb7f1e000)
        libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb762a000)

2、编写makefile文件,利用make生成so

先创建个工程,目录结构如下

── project
    ├── build
    │   └── Makefile
    ├── include
    │   ├── data.h
    │   ├── print_class.h
    │   └── set_class.h
    ├── main.c
    └── src
        ├── print_class.c
        ├── print_class.o
        ├── set_class.c
        └── set_class.o

main依赖data.h\print_class.h\set_class.h

Makefile

target = libproj.so
cc = gcc
srcs = $(wildcard ./../src/*.c ./../*.c)
includes = -I./../include/
OBJS    =$(patsubst %c, %o, $(srcs))

CFLAGS  = -fsanitize=address -g -shared -fPIC
all: $(target)
$(target): $(OBJS)
        $(cc) $(CFLAGS) $(OBJS) -o $@

%.o:%.c
        $(cc) -c -lpthread $(includes) $(CFLAGS) $^ -o $@

语法规则

$(target): $(OBJS)
	$(cc) $(CFLAGS) $(OBJS) -o $@

意思如下

目标 ... : 依赖 ...
	命令1
	命令2
	. . .
	
实际执行,是将.o文件编译生成目标so文件
gcc -fsanitize=address -g -shared -fPIC  ./../src/print_class.o  ./../src/set_class.o  ./../main.o -o libproj.so
%.o:%.c
        $(cc) -c -pthread $(includes) $(CFLAGS) $^ -o $@
        
实际执行,是将.c文件编译生成.o文件
gcc -c -lpthread -I./../include/ -fsanitize=address -g  -shared -fPIC ../main.c -o ../main.o

这里先将所有.c 文件编译为 .o 文件,这样后面更改某个 .c 文件时,其他的 .c 文件将不在编译,而只是编译有更改的 .c 文件,可以大大提高大项目中的编译速度。

Makefile参数解释

target:我要生成的目标文件
cc:我使用的编译指令
srcs:我需要编译的对象 (c文件)
OBJS: 编译时需要的中间对象(一般来说是.o文件),这里是将src中所有的c文件替换成.o文件
includes:头文件目录
CFLAGS:编译选项,里面的内容就是生成.so文件需要的编译选项
	-fsanitize=address: asan选项
    -g:在编译的时候,产生调试信息
    -shared:生成动态连接库(so)
    -fPIC: 位置无关选项,用于生成动态连接库so.
    		用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code);这样一来,产生的代码中就没有绝对地址了,全部使用相对地址,所以代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
all:编译需要生成哪些目标文件,因为有些情况会生成多个目标文件,all后面可以指定自己要生成哪些目标文件


------------- 变量 ------------------
%:为通配符,%.c为对应.c文件, %.o为对应.o文件
$:表示取变量的值,当变量名多于一个字符时,使用"$(xxx)"
$@:表示目标文件
$^:表示所有的依赖文件
$<:表示第一个依赖文件
$?:表示比目标还要新的依赖文件列表

-I:用来指定头文件目录
-L:用来指定依赖的库所在的目录
-l:用来指定依赖的具体动态库

-------------- 函数 ----------------------
wildcard:通配符,这里表示./src下所有的c文件
patsubst:模式字符串替换函数
	比如:OBJS=$(patsubst %c, %o, $(srcs)), 意思是取出SRC中的所有值,然后将.c 替换为.o 最后赋值给OBJS变量
notdir : 剥离文件的绝对路径,只保留文件名。
	比如: src/set_class.c, 变为set_class.c

make命令

make VERBOSE=1 或 make V=1
显示详细编译过程

执行make

root@10:/home/code/exec/4.makefile/project/build# make V=1

gcc -c -lpthread -I./../include/ -fsanitize=address -g  -shared -fPIC ../main.c -o ../main.o

gcc -fsanitize=address -g  -shared -fPIC  ./../src/print_class.o  ./../src/set_class.o  ./../main.o -o libproj.so

最终会生成动态可执行文件,libproj.so

查看so中的符号

nm -A -D libproj.so

root@10:/home/code/exec/4.makefile/project/build# nm -A -D libproj.so
libproj.so:         U __asan_init
libproj.so:         U __asan_option_detect_stack_use_after_return
libproj.so:         U __asan_register_globals
libproj.so:         U __asan_report_load4
libproj.so:         U __asan_report_store16
libproj.so:         U __asan_report_store4
libproj.so:         U __asan_stack_malloc_0
libproj.so:         U __asan_unregister_globals
libproj.so:         U __asan_version_mismatch_check_v8
libproj.so:         w __cxa_finalize
libproj.so:         w __gmon_start__
libproj.so:         w _ITM_deregisterTMCloneTable
libproj.so:         w _ITM_registerTMCloneTable
libproj.so:000013a2 T main
libproj.so:         U memcpy
libproj.so:000011f9 T print_class
libproj.so:         U printf
libproj.so:00001355 T set_class

参考:

利用Makefile给多文件、多目录C源码建立工程 - 知乎 (zhihu.com)

使用makefile生成.so文件_makefile生成so文件-CSDN博客

Linux 中 dlopen、dlsym、dlclose、dlerror函数-CSDN博客

3、编写程序调用so

上述生成了so文件,然后我们通过dlopen调用so,来看下如何使用so

test1/test1.c 依赖外部动态连接库:project/build/libproj.so

test1.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include "data.h"
#include "set_class.h"
#include "print_class.h"


int  main(int argc, char* argv[])
{
        printf("ok.\n");
        Class tmp = {
                .score[0] = 1,
                .score[1] = 2,
                .score[2] = 3,
        };

        Class a = {0};
        set_class(&a, &tmp);
        print_class(&a);

        return 0;
}


方法一:直接gcc编译

gcc -fsanitize=address -g    ./../test1.o -o test1  -I../project/include -L../../lib -Wl,-rpath=../../lib  -lproj -lpthread

-I:用来指定依赖的头文件目录
-L:用来指定依赖的库文件目录
-ldl:依赖dl动态库(dlopen\dlsym\dlclose)

方法二:利用make编译

编写Makefile 依赖动态so

其中test1依赖libproj.so,将libproj.so放到…/…/lib目录下,增加-L目录依赖,增加-lproj 的so依赖,解决编译问题。增加-Wl,rpath指定so目录,是为了解决运行时连接器找不到libproj.so的问题。

target = test1
cc = gcc
srcs = $(wildcard ./../*.c)
includes = -I../../project/include
LIBS = -lproj -lpthread
CFLAGS = -fsanitize=address -g
LDFLAGS = -L../../lib -Wl,-rpath=../../lib
OBJS = $(patsubst %c, %o, $(srcs))

all: $(target)

# link
$(target): $(OBJS)
        $(cc) $(CFLAGS)  $(OBJS) -o $@  $(LDFLAGS)  $(LIBS)
#
# compile
%.o:%.c
        $(cc) -c $(includes) $(CFLAGS) $^ -o $@

# -L:用来指定依赖的库所在的目录
clear:
        rm -rf $(target)
        rm -rf $(OBJS)

执行make,生成目标文件test1

查看可执行文件的依赖库 ldd test1
在这里插入图片描述

在这里插入图片描述

遇到的问题1:

编译ok,但执行时,还未进入main,就报segment fault,段错误。

原因是依赖的so,报未定义。。

解决方法:

链接时,未增加-L 路径依赖,-l 库依赖

遇到的问题2:

编译OK,但是运行时报libproj.so找不到

在这里插入图片描述

解决方法:

有时候LDFLAGS指定-L虽然能让链接器找到库进行链接,但是运行时链接器却找不到这个库,如果要让软件运行时库文件的路径也得到扩展,那么我们需要增加这两个库给"-Wl,-rpath"。run time的so路径一般都在环境变量LD_LIBRARY_PATH中

参考如下:

Makefile选项CFLAGS,LDFLAGS,LIBS - Taskiller - 博客园 (cnblogs.com)

[GCC/G++ 编译时-L -l为什么运行时还是找不到? - 知乎 (zhihu.com)](

4、如何不依赖外部头文件,通过so文件使用其接口?

通过dlopen加载so,返回handle;

然后通过dlsym读取handle里的符号“xxx_func”地址,就可以了

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <dlfcn.h>
#include "data.h"

#define LIB_SO "../lib/libproj.so"

typedef void *(*my_func)(Class *a, Class *b);
my_func set_class_stub = NULL;

int  main(int argc, char* argv[])
{
        // 加载so
        void *handle = dlopen(LIB_SO, RTLD_NOW);
        if (handle == NULL) {
                printf("dlopen fail.\n");
                printf("%s.\n", dlerror());
                return -1;
        }


        // 获取符号地址
        set_class_stub = dlsym(handle, "set_class");
        if (set_class_stub == NULL) {
                printf("dlsym fail.\n");
                return -1;
        }


                Class tmp = {
                .score[0] = 1,
                .score[1] = 2,
                .score[2] = 3,
        };
        Class a = {0};
        set_class_stub(&a, &tmp);
        printf("score0:%u, score1:%u, score2:%u.\n",
                a.score[0], a.score[1], a.score[2]);


        return 0;
}

gcc编译

gcc -fsanitize=address -g  test.c -o test -I../project/include -L../lib -Wl,-rpath=../lib -lproj -lpthread -ldl

执行

 ./test
score0:1, score1:2, score2:3.

说明执行了 libproj.so的set_class

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值