在一些大型项目的开发中,代码复用是很常见的情况,各个模块经常会使用一些通用功能。为了提高代码质量,减少代码冗余,一般会引入公共的模块库,将通用函数写入其中。动态链接库就是这样的一个公共的模块库,它不仅可以降低模块之间的耦合,还可以将其发布出去,供他人使用。

0x10 动态链接库的生成和使用

动态链接库只是一些函数的实现,供第三方调用。从宏观角度上来说,需要以下两个文件

  • 头文件,主要是函数的一些声明(类似于 Java 中的接口)
  • 函数实现代码

0x11 生成动态链接库

头文件需要提供给使用者,因为我们需要知道动态链接库里有哪写函数可供使用。#ifndef 都是一种宏定义判断,作用是防止多重定义,在头文件中也经常使用

/* random.h */
#ifndef _RANDOM_H_
#define _RANDOM_H_

int obtain_random(int x);

#endif

以下是函数实现代码

/* random.c */
#include <time.h>
#include <stdlib.h>

int obtain_random(int max)
{
	srand((int)time(0));
	return rand() % max;
}

如果你对随机数比较熟悉,可忽略下面的这段话:

标准C库中函数 rand() 可以生成 0~RAND_MAX 之间的一个随机数,其中 RAND_MAXstdlib.h 中定义的一个整数,它与系统有关。如果直接使用 rand() 函数,你会发现每次生成的值都是一样的,因为 rand() 函数默认会有一个种子值(默认为1),其根据种子值来生成随机数,种子一样,生成的随机数也一样。为了让程序在每次执行时都能生成一个新序列的随机值,我们通常通过为随机数生成器提供一粒新的随机种子。函数 srand() (来自stdlib.h)可以为随机数生成器播散种子。只要种子不同 rand() 函数就会产生不同的随机数序列。srand() 称为随机数生成器的初始化器。

编译生成动态链接库

gcc -shared -fPIC random.c -o librandom.so
  • shared 参数,顾名思义就是用来生成动态链接库的
  • fPIC 参数,告诉编译器产生与位置无关的代码

这样,就完成了 librandom.so 文件的制作。

0x12 使用动态链接库

编写测试代码,调用动态链接库中的函数

/* test.c */
#include <stdio.h>
#include "random.h"

int main(int argc, char const *argv[])
{
	/* generate random number within 100*/
	printf("%d\n", obtain_random(100));
	return 0;
}

编译和链接

gcc test.c -L ./ -l random -o test
  • L 库文件路径
  • l 要链接的库,库的名称去头尾

运行结果
在这里插入图片描述
这里为什么要加 LD_LIBRARY_PATH? 刚刚编译的时候不是已经指定动态链接库的路径了吗?不指定文件路径 ,可能会报错 cannot open shared object file: No such file or directory,解决方案详见 Q&A

0x20 使用特定函数加载动态链接库

除了从引用头文件的方式,引入库函数,我们还可以使用特定的函数,来加载动态链接库中的函数。

0x21 使用 dlopen 函数加载动态链接库

Linux 提供了 dlopendlsymdlerrordlcolose 函数(在头文件 dlfcn.h 中声明)用来获取动态链接库中包含的函数。可用 man 命令查看相应的手册,获得的函数声明如下

#include <dlfcn.h>

void *dlopen(const char *filename, int flag);
char *dlerror(void);
void *dlsym(void *handle, const char *symbol);
int dlclose(void *handle);

dlopen 是一个功能强大的库函数,可以打开一个库,并装入内存。dlopen 用来加载库中的符号,而这些符号在编译的时候,编译器无法知道。dlopen 以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程;dlerror 返回错误状态;dlsym 通过句柄和连接符名称获取函数名或者变量名;dlclose来卸载打开的库。

flags 参数必须包括以下两个值中的一个:

  • RTLD_LAZY 执行延迟绑定。仅在执行引用它们的代码时解析符号。如果从未引用该符号,则永远不会解析它(只对函数引用执行延迟绑定;在加载共享对象时,对变量的引用总是立即绑定)。自 glibc 2.1.1,此标志被LD_BIND_NOW环境变量的效果覆盖。
  • RTLD_NOW 如果指定了此值,或者环境变量 LD_BIND_NOW 设置为非空字符串,则在dlopen()返回之前,将解析共享对象中的所有未定义符号。如果无法执行此操作,则会返回错误。

我们还以刚刚生成的动态链接库 librandom.so 为例,测试代码如下

/* test_dlopen.c */
#include <stdio.h>
#include <dlfcn.h>

#define DLL_FILE_NAME "librandom.so"

int main(int argc, char const *argv[])
{
	void * handle;
	int (* func)(int);
	char * error;
	
	/* 加载动态链接库 */
	handle = dlopen(DLL_FILE_NAME, RTLD_NOW);
	if(handle == NULL)
	{
		printf("Failed to open library %s, error: %s\n", DLL_FILE_NAME, dlerror());
		return -1;
	}
	
	/* 获取特定的函数 */
	func = dlsym(handle, "obtain_random");
	printf("%d\n", func(100));

	return 0;
}

编译

gcc test_dlopen.c -o test_dlopen -ldl
  • dl 显式加载动态库的动态函数库。在 Linux 上,使用动态链接的应用程序需要和库 libdl.so 一起链接,也就是使用选项 -ldl。但是,编译时不需要和动态装载的库一起链接。

结果如下
在这里插入图片描述

0x22 使用 python 调用动态链接库

其实 python 也可以调用 C/C++ 编写的动态链接库,只要引入 ctypes 库就可以使用了。

#! /usr/bin/python
#-*- coding=utf-8 -*-

import ctypes

librandom = ctypes.cdll.LoadLibrary( ".//librandom.so" )
print "random: " + str(librandom.obtain_random(100))

0x30 传统编译法

如果你不想使用动态链接库,想直接引用一些开源代码,比如这里,直接将 random.h random.c test.c 一起编译,怎么做呢?也就是说,如果我们不使用动态链接库,怎么链接多个源文件呢?

0x31 传统方法编译和链接文件

生成目标文件

gcc -c random.c -o random.o
gcc -c test.c -o test.o

这里 -c 的作用是,不让 gcc 编译器自动链接,下一步,将目标文件链接在一起,生成可执行程序

gcc random.o test.o -o test

运行结果
在这里插入图片描述

0x32 使用 Makefile 文件构建项目

在正式的项目中,通常会使用 Makefile 文件构建和管理工程。简单来说,以上几个命令,也可以用一个 Makefile 文件搞定

CFLAGS = -Wall
test: test.o random.o
test.o: test.c
	gcc $(CFLAGS) -c test.c -o test.o
random.o: random.c
	gcc $(CFLAGS) -c random.c -o random.o

clean:
	rm -rvf *.o 
  • CFLAGS 用来消除编译中的警告,也可以不加
  • clean 用来删除中间文件,也可以不加

结果如下
在这里插入图片描述

0x40 总结

在 0x21 节,我们提出,dlopen 用来加载库中的符号,在编译时并不知晓动态链接库中的符号,这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。而在 0x10 节,使用头文件的方式引入库函数,如果函数未找到,代码就不能运行。使用头文件的方式,是在链接时定位相关函数,使用 dlopen,则是在程序实际运行时,定位要使用的函数。本篇博客介绍了动态链接库的常见用法,还给出不使用动态链接,直接使用源文件进行编译的一般方法,并且还使用 python 的相关库,调用 C/C++ 编写的动态链接库,相信你读过此文,对编译、动态链接都会有一个清晰的认识,而且可以使用本文的方法来进行实际的 Linux C 动态链接库调用。


0x50 Q&A

运行程序出现错误:cannot open shared object file: No such file or directory,即未找到动态链接库。这是因为默认的链接器只会从 /usr/lib/ 寻找库文件。

方法一:在链接语句后面添加如下命令

-Wl,-rpath=路径

方法二:运行程序的时候,直接在前面加上LD_LIBRARY_PATH= 是用来告诉链接程序,从指定的目录下寻找链接库。

Logo

更多推荐