面试 | Linux 下的动态链接库问题

面试 | Linux 下的动态链接库问题,第1张

在 Linux 开发时,我们经常会看到一些形如 xxx.so 的名称出现,其中 so 是 Shared Object 的缩写,即可以共享的目标文件,也就是我们所称为的动态链接库,和在 Windows 下大家玩 游戏 时遇到的 xxx.dll 错误中的文件是一个类型的。

面试中经常会问到以下问题:

库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。本质上来说库是一种可执行代码的二进制形式,可以被 *** 作系统载入内存执行。

库有两种:

在一个程序的编译过程中,分为以下几个步骤: 预处理 编译 汇编 链接 。本文中讨论的链接库就是针对最后一个步骤「链接」而言的。

动态库和静态库的区别

左图为静态链接库,右图为动态链接库

对于静态链接库而言在链接阶段,会将汇编生成的「目标文件.o」与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接:

静态链接可以理解为最后生成了一个「单文件免安装绿色版」的程序,优点在于移植的时候只需要移动这一个文件,缺点在于文件体积非常大,为了解决这样的问题,就有了动态链接库。动态链接库在程序编译时并不会被连接到目标代码敬大中,而是在程序运行时才被载入。

动态库连接到系统空间,如果多个程序连接了同一个库,那么只需要一份,优点在于编译程序的时候不会将对应的库文件全部打包在生成的程序中,而是保留了到对应库的链接,缺点就是移植的时候如果只移动了对应的程序没有安装相关的库的话,就会看到类似以下喜闻乐见的结果了。

在 Linux 下一个动态库有y三个不同名字的文件组成:

当程序在内部列出所需要的链亮核竖接库时,仅仅使用 soname。当你创建一个链接库时,使用 real name。安装一个新的链接库时,把它复制到一个DLL文件夹里,然后运行程序 ldconfig。ldconfig 检查氏磨存在的 real name 文件,并且创建指向它符号链接 soname 文件。可能大家比较常见到的有 libsodium 等。

有了上面关于库的一些基础知识之后,我们可以开始尝试创建一个动态库来供程序使用了。

比如我们有一个求最大值的函数 max(int a,int b,int c) ,放在文件 max.c 中文件内容如下:

可以通过:

将其编译为共享库,-fPIC是编译选项,PIC是 Position Independent Code 的缩写,表示要生成位置无关的代码,这是动态库需要的特性; -shared是链接选项,告诉 gcc 生成动态库而不是可执行文件。为了让用户知道我们的动态库中有哪些接口可用,我们需要编写对应的头文件,比如可以写一个 max.h :

设置一个驱动函数来测试我们编写的动态库:

通过 gcc test.c -L. -lmax来生成 a.out,其中-lmax表示要链接 libmax.so,-L.表示搜索要链接的库文件时包含当前路径。

但是这样直接运行的话,会出现一个错误:

由于 Linux 是通过/etc/ld.so.cache文件搜寻要链接的动态库的,而 /etc/ld.so.cache 是 ldconfig 程序读取 /etc/ld.so.conf 文件生成的,本次使用的动态库 libmax.so 并不在对应的目录下,就会导致程序无法找到对应的动态链接库,这样我们的解决方法有二:

小结

​动态链接库是各个系统中的一个重要的组成部分且在 Linux 开发相关领域中尤为重要,也是一个面试的高频考点,除了动态链接库以外,还有以下相关知识也是高频考点,在面试前一定要准备好:

本文作者:Nova Kwok

大家都知道在 Linux 可以用 gdb 来调试应用程序,当然前提是用 gcc 编译程序时要加上

-g 参数。

我这篇文章里将讨论一下用 gdb 来调试动态链接库的问题。

首先,假设我们准备这样的一个动态链接库:

QUOTE:

库名称是: ggg

动态链接库文件名是: libggg.so

头文件是: get.h

提供这样两个函数调用接口:

int get ()

int set (int a)

要生成这样一个动态链接库,我们首先编写这样一个头文件:

[Copy to clipboard]

CODE:

/************关于本文档********************************************

*filename: get.h

*purpose: 一个动态链斗尺接库头文件示例

*tided by: zhoulifa() 周立发 ()

Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言

*date time: 2006-11-15 21:11:54

*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途

* 但请遵循 GPL

*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力

* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!

*感谢 提供原始代码,

我在他的基础上整理了此文

*********************************************************************/

int get ()

int set (int a)

然后准备这样一个生成动拆岩态链接库的源文件:

[Copy to clipboard]

CODE:

/************关于本文档********************************************

*filename: get.cpp

*purpose: 一个动态链接库源文件示例

*tided by: zhoulifa() 周立发 ()

Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言

*date time:2006-11-15 21:11:54

*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途

* 但请遵循 GPL

*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力

* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!

*感谢 提供原始代码,

我在他的基础上整理了此文

*********************************************************************/

#include <stdio.h>

#include "get.h"

static int x=0

int get ()

{

printf ("get x=%d\n", x)

return x

}

int set (int a)

{

printf ("set a=%d\n", a)

x = a

return x

}

然后我们用 GNU 的 C/C++ 编译器来生成动态链接库,编译命令如下:

QUOTE:

g++ get.cpp -shared -g -DDEBUG -o

libggg.so

这样我们就准备好了动态链接库了,下面我们编写一个应用程序来调用此动态链接库,源代码如下:

[Copy to clipboard]

CODE:

/************关于本文档********************************************

*filename: pk.cpp

*purpose: 一个调用动态链接库的示例旅销御

*tided by: zhoulifa() 周立发 ()

Linux 爱好者 Linux 知识传播者 SOHO 族 开发者 最擅长 C 语言

*date time:2006-11-15 21:11:54

*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途

* 但请遵循 GPL

*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力

* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!

*感谢 提供原始代码,

我在他的基础上整理了此文

*********************************************************************/

#include <stdio.h>

#include "get.h"

int main (int argc, char** argv)

{

int a = 100

int b = get ()

int c = set (a)

int d = get ()

printf ("a=%d,b=%d,c=%d,d=%d\n",a,b,c,d)

return 0

}

编译此程序用下列命令,如果已经把上面生成的 libggg.so 放到了库文件搜索路径指定的文件目录,比如 /lib 或 /usr/lib 之类的,就用下面这条命令:

QUOTE:

g++ pk.cpp -o app -Wall -g -lggg

否则就用下面这条命令:

QUOTE:

g++ pk.cpp -o app -Wall -g -lggg -L`pwd`

下面我们就开始调试上面命令生成的 app 程序吧。如果已经把上面生成的 libggg.so 放到了库文件搜索路径指定的文件目录,比如 /lib或 /usr/lib 之类的,调试就顺利完成,如下

QUOTE:

./app

GNU gdb 6.4-debian

Copyright 2005 Free Software Foundation,Inc.

GDB is free software, covered by the GNU

General Public License, and you are

welcome to change it and/or distribute

copies of it under certain conditions.

Type "show copying" to see theconditions.

There is absolutely no warranty for GDB.

Type "show warranty" for details.This GDB was configured as "i486-linux-

gnu"...Using host libthread_db library"/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) b main/* 这是在程序的 main 处设置断点 */

Breakpoint 1 at 0x804853c: file pk.cpp,line 7.

(gdb) b set /* 这是在程序的 set 处设置断点 */

Function "set" not defined.

Make breakpoint pending on future shared

library load? (y or [n]) y /* 这里必须选择 y 调试程序才会跟踪到动态链接库内部去

*/Breakpoint 2 (set) pending.

(gdb) run /* 开始运行我们的程序,直到遇见断点时暂停 */

Starting program: /data/example/c/app

Breakpoint 3 at 0xb7f665f8: file get.cpp,line 11.

Pending breakpoint "set" resolved

Breakpoint 1, main (argc=1,argv=0xbf990504) at pk.cpp:7

7 int a = 100

(gdb) n /* 继续执行程序的下一行代码

*/

8 int b = get ()

(gdb) n /* 程序执行到了我们断点所在的动态链接库了 */

get x=0

9 int c = set (a)(gdb) n

Breakpoint 3, set (a=100) at get.cpp:11

11 printf ("set a=%d\n", a)

(gdb) list /* 查看当前代码行周围的代码,证明我们已经跟踪到动态链接库的源代码里面了 */

6 printf ("get x=%d\n", x)

7 return x

8 }

9 int set (int a)

10 {

11 printf ("set a=%d\n", a)

12 x = a

13 return x

14 }

(gdb) n

set a=100

12 x = a(gdb) n

13 return x(gdb) n

14 }

(gdb) n

main (argc=1, argv=0xbf990504) at

pk.cpp:10

10 int d = get ()

(gdb) n

get x=100

11 printf ("a=%d,b=%d,c=%

d,d=%d\n",a,b,c,d)

(gdb) n

a=100,b=0,c=100,d=100

12 return 0

(gdb) c

Continuing.

Program exited normally.

(gdb) quit /* 程序顺利执行结束 */#

如果我们没有把动态链接库放到指定目录,比如/lib里面,调试就会失败,过程如下:

QUOTE:

# gdb ./app

GNU gdb 6.4-debian

Copyright 2005 Free Software Foundation,

Inc.

GDB is free software, covered by the GNU

General Public License, and you arewelcome to change it and/or distribute

copies of it under certain conditions.

Type "show copying" to see theconditions.

There is absolutely no warranty for GDB.

Type "show warranty" for details.

This GDB was configured as "i486-linux-

gnu"...Using host libthread_db library

"/lib/tls/i686/cmov/libthread_db.so.1".

(gdb) b main

Breakpoint 1 at 0x804853c: file pk.cpp,

line 7.

(gdb) b set

Function "set" not defined.

Make breakpoint pending on future shared

library load? (y or [n]) y

Breakpoint 2 (set) pending.

(gdb) run /* 虽然调试 *** 作都一样,但程序执行失败 */

Starting program: /data/example/c/app

/data/example/c/app: error while loading

shared libraries: libggg.so: cannot open

shared object file: No such file or

directory

Program exited with code 0177.

(gdb) quit

#

本次实验的环境是:

CPU:AMD Athlon(tm) 64 Processor 3000+

内存:512M

OS:Ubuntu GNU/Linux 6.06 dapper LTS

gcc:gcc 版本 4.0.3 (Ubuntu 4.0.3-1ubuntu5)

break(b) 行号:在某一行设置断点

break 函数名:在某个函数开头设置断点

break...if...:设置条件断点

continue(或c):从当前位置开始连续而非单步执行程序

delete breakpoints:删除所有断点

delete breakpoints n:删除序号为n的断点

disable breakpoints:禁用断点

enable breakpoints:启用断点

info(或i) breakpoints:参看当前设置了哪些断点

run(或r):从开始连续而非单步执行程序

display 变量名:跟踪查看一个变量,每次停下来都显示它的值

undisplay:取消对先前设置的那些变量的跟踪

第一步,我先从简单的调用出发,定义了一个简单的函数,该函数仅仅实现一个整数加法求和:

LIBEXPORT_API int mySum(int a,int b){ return a+b}

C# 导入定义:

public class RefComm

{

[DllImport("LibEncrypt.dll",

EntryPoint=" mySum ",

CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]

public static extern int mySum (int a,int b)

}

在C#中调用测试:

int iSum = RefComm.mySum(,)

运行查看结果iSum为5,调用正确。第一步试验完成,说明在C#中能够调用自定义的动态链接库函数。

第二步,我定义了字符串 *** 作的函数(简单起见,还是采用前面的函数名),返回结果为字符串:

LIBEXPORT_API char *mySum(char *a,char *b){sprintf(b,"%s",a)return a}

C# 导入定义:

public class RefComm

{

[DllImport("LibEncrypt.dll",

EntryPoint=" mySum ",

CharSet=CharSet.Auto,

CallingConvention=CallingConvention.StdCall)]

public static extern string mySum (string a, string b)

}

在清信汪C#中调用测试:

string strDest=""

string strTmp= RefComm.mySum("45", strDest)

运答仔行查看结果 strTmp 为"45",但是strDest为空。我修改动态链接库实现,返回结果为串b:

LIBEXPORT_API char *mySum(char *a,char *b){sprintf(b,"%s",a) return b}

修改 C# 导入定义,将串b修改为ref方式:

public class RefComm

{

[DllImport("LibEncrypt.dll",

EntryPoint=" mySum ",

CharSet=CharSet.Auto,CallingConvention=CallingConvention.StdCall)]

public static extern string mySum (string a, ref string b)

}

在C#中再调用测试:

string strDest=""

string strTmp= RefComm.mySum("45", ref strDest)

运行查看结果 strTmp 和 strDest 均不对,含不可见字符。再修改 C# 导入定义,将CharSet从Auto修改为Ansi:

public class RefComm

{

[DllImport("LibEncrypt.dll",

EntryPoint=" mySum ",

CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]

public static extern string mySum (string a, string b)

}

在C#中再调用测试:

string strDest=""

string strTmp= RefComm. mySum("45", ref strDest)

运行查看结果 strTmp 为"45",但是串 strDest 没有赋值。第二步实现函数返回串,但是在函数出口参数中没能进行输出。再次修改 C# 导入定义,将串b修改为引用(ref):坦空

public class RefComm

{

[DllImport("LibEncrypt.dll",

EntryPoint=" mySum ",

CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]

public static extern string mySum (string a, ref string b)

}

运行时调用失败,不能继续执行。

第三步,修改动态链接库实现,将b修改为双重指针:

LIBEXPORT_API char *mySum(char *a,char **b){sprintf((*b),"%s",a)return *b}

C#导入定义:

public class RefComm

{

[DllImport("LibEncrypt.dll",

EntryPoint=" mySum ",

CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]

public static extern string mySum (string a, ref string b)

}

在C#中调用测试:

string strDest=""

string strTmp= RefComm. mySum("45", ref strDest)

运行查看结果 strTmp 和 strDest 均为"45",调用正确。第三步实现了函数出口参数正确输出结果。

第四步,修改动态链接库实现,实现整数参数的输出:

LIBEXPORT_API int mySum(int a,int b,int *c){ *c=a+breturn *c}

C#导入的定义:

public class RefComm

{

[DllImport("LibEncrypt.dll",

EntryPoint=" mySum ",

CharSet=CharSet.Ansi,CallingConvention=CallingConvention.StdCall)]

public static extern int mySum (int a, int b,ref int c)

}

在C#中调用测试:

int c=0

int iSum= RefComm. mySum(,, ref c)

运行查看结果iSum 和c均为5,调用正确。

经过以上几个步骤的试验,基本掌握了如何定义动态库函数以及如何在 C# 定义导入,有此基础,很快我实现了变长加密函数在 C# 中的调用,至此目标实现。

三、结论

在 C# 中调用 C++ 编写的动态链接库函数,如果需要出口参数输出,则需要使用指针,对于字符串,则需要使用双重指针,对于 C# 的导入定义,则需要使用引用(ref)定义。

对于函数返回值,C# 导入定义和 C++ 动态库函数声明定义需要保持一致,否则会出现函数调用失败。定义导入时,一定注意 CharSet 和 CallingConvention 参数,否则导致调用失败或结果异常。运行时,动态链接库放在 C# 程序的目录下即可,我这里是一个 C# 的动态链接库,两个动态链接库就在同一个目录下运行。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12465639.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-25
下一篇 2023-05-25

发表评论

登录后才能评论

评论列表(0条)

保存