-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
122 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
--- | ||
title: "记一次编译错误的解决过程" | ||
date: 2018-11-01T20:31:15+08:00 | ||
draft: false | ||
comments: true | ||
toc: true | ||
autoCollapseToc: false | ||
keywords: [] | ||
tags: [] | ||
--- | ||
|
||
最近开发新的需求需要使用某个外部模块的库文件,该模块在文档中提供了一个demo,但makefile文件编译报错。通过摸索和学习最终把demo编译成功并运行起来,下面记录一下过程中碰到的问题并进行总结。 | ||
|
||
## so not found | ||
|
||
使用gcc编译c/c++程序时,编译时用`-I`指定头文件查找路径,`-L`指定库文件查找路径,`-l`具体指定依赖的库。 | ||
|
||
如果指定了`-L`,也使用`-l`链接了该库,但是报如下告警: | ||
|
||
```shell | ||
warning: libxxx.so, needed by ./libyyy.so, not found (try using -rpath or -rpath-link) | ||
``` | ||
|
||
说明该so依赖的其他so无法找到。使用`ldd`命令查看该so依赖的所有其他so,会有类似于 | ||
|
||
```shell | ||
/usr/bin/ld: cannot find -lxxx | ||
``` | ||
|
||
的打印,这时就需要找到被依赖的so所在的绝对路径,添加到`/etc/ld.so.conf`文件中,并执行`ldconfig`。 | ||
|
||
另一种办法是向环境变量`LD_LIBRARY_PATH`中添加路径,指定动态库加载路径;对应的静态库加载路径的环境变量名`LIBRARY_PATH`。 | ||
> 注意: 使用`env`命令查看系统中若无环境变量`LD_LIBRARY_PATH`和`LIBRARY_PATH`,则需要使用`export`命令将变量变成环境变量,即该变量在子shell进程中也可见。但重新登录时该环境变量会消失。要想环境变量每次登录都存在,可以向`/etc/profile`文件尾用export添加环境变量。这是因为**每次登录时系统会自动执行/etc/profile脚本**。 | ||
## file format not recognized | ||
|
||
编译时提示错误如: | ||
|
||
```shell | ||
/usr/bin/ld:./libxxx.so: file format not recognized; treating as linker script | ||
/usr/bin/ld:./libxxx.so:2: syntax error | ||
``` | ||
|
||
意思是这个so文件格式不识别,ld试图将它当作链接文件来看待,但仍然出错。查看发现该so文件大小只有几字节,且附近有一个带后缀.1的文件libxxx.so.1。**原因是该so文件实际是一个软链接文件,链接对象就是libxxx.so.1**;但由于该模块提供的lib压缩包是在windows下解压后通过远程文件系统挂载到linux系统上的,软连接文件被当成普通文件解压了。解决办法是重新创建软连接或直接在linux下解压。 | ||
|
||
> so后面带的.1是版本号为1的意思。这是linux下动态库版本控制的一种方法。具体可以看(动态链接库的版本控制)[]一文。 | ||
## undefined reference to | ||
|
||
`undefined reference to` 即未定义的引用,表示某函数被声明了但是却没有找到对应的实现,这种情况是可以编译成功的,但是链接会失败。类似的还有`Undeclared references`,即未声明的引用,表示找不到函数声明。 | ||
|
||
出现这种情况只能考虑是编译时还有必要的库没有链接,对库文件夹中所有的库执行`nm`命令,过滤该函数名,找到该函数定义(`T类`)所在的库文件,并将其加入到编译链接库中。 | ||
|
||
> `nm`用于打印库或可执行文件中的符号名: | ||
> | ||
> * T类:是在库中定义的函数,用T表示; | ||
> * U类:是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
--- | ||
title: "动态链接库的版本控制" | ||
date: 2018-10-20T21:00:51+08:00 | ||
draft: false | ||
comments: true | ||
toc: true | ||
autoCollapseToc: false | ||
keywords: [] | ||
tags: [] | ||
--- | ||
|
||
## DLL Hell | ||
|
||
**[DLL Hell](https://en.wikipedia.org/wiki/DLL_Hell)**:同一台机器上,运行着A和B两个程序,他们使用了同一个so;程序A在升级时使用新的so**直接覆盖**老的so,此时可能会造成程序B无法正常运行。 | ||
|
||
因此需要对动态链接库进行版本控制。 | ||
|
||
## so name | ||
|
||
在介绍版本控制前,需要先了解动态链接库的三种name:`real name`、`soname`、`link name`。 | ||
|
||
* **link name**:`libxxx.so`称为动态链接库的`link name`。 | ||
* **real name**:实际编译出来的动态链接库是具有版本号后缀的,如`libxxx.so.x.y.z`,称为动态链接库的`real name`。 | ||
>其中`x`代表主版本号,`y`代表小版本号,`z`代表duild号。 | ||
* **soname**:`link name`+`主版本号`,即`libxxx.so.1`。 | ||
|
||
## 编译动态库 | ||
|
||
编译动态链接库时要带上编译选项`-soname`以指定soname。例如编译动态库`libtest.so.1.0.0`时,编译方式如下: | ||
|
||
```shell | ||
gcc -fPIC -o test.o -c test.c | ||
gcc -shared -Wl,-soname,libtest.so.1 -o libtest.so.1.0.0 test.o | ||
``` | ||
|
||
通过`readelf -d`查看动态段,可以发现`soname`信息被记录到了`libtest.so.1.0.0`的文件头中: | ||
|
||
```shell | ||
readelf -d libtest.so.1.0.0 | grep soname | ||
0x0000000e (SONAME) Library soname: [libtest.so.1] | ||
``` | ||
|
||
**此时执行`ldconfig`命令将自动生成`libtest.so.1`文件,它是一个指向`libtest.so.1.0.0`的软连接**。 | ||
|
||
不难想到: | ||
|
||
* 如果主版本发生变化,新老版本的soname会发生变化。 | ||
* 如果小版本发生变化,新老版本的soname应该保持不变。 | ||
|
||
## 编译程序 | ||
|
||
以使用上面编译好的`libtest.so.1.0.0`动态库的程序为例,编译的标准步骤如下: | ||
|
||
1. 创建一个指向real name文件的link name文件,即 `ln -s libtest.so.1.0.0 libtest.so` | ||
2. 编译程序,通过指定`-ltest`,编译器会去查找`libtest.so`文件,但实际参与编译的是`libtest.so.1.0.0`文件 | ||
3. 编译器发现`libtest.so.1.0.0`中记录着soname `libtest.so.1`,告诉程序在运行时应该引用`libtest.so.1` | ||
4. 而`libtest.so.1`文件,则是通过执行`ldconfig`命令生成出来的指向`libtest.so.1.0.0`的软链接,所以程序实际运行过程中使用的是`libtest.so.1.0.0` | ||
|
||
## 升级动态库 | ||
|
||
1. 小版本升级,比如从`libtest.so.1.0.0`升级为`libtest.so.1.1.1`。这个时候,按照约定它的soname`libtest.so.1`是不变的,所以使用者可以直接把新版本so丢到机器上,执行`ldconfig`,新生成的`libtest.so.1`就变成了指向`libtest.so.1.1.1`的软连接。小版本升级是后向兼容的,所以这里直接进行升级是没有问题的。 | ||
2. 主版本升级,比如从`libtest.so.1.1.1`升级为`libtest.so.2.0.0`。这个时候,按照约定它的soname变成了`libtest.so.2`,此时`ldconfig`生成的软连接为`libtest.so.2`,指向`libtest.so.2.0.0`。一般主版本升级会有后向兼容性问题,但是由于使用了新的soname,因此对使用老版本so的程序没有影响。 |