Skip to content

Commit

Permalink
some posts
Browse files Browse the repository at this point in the history
  • Loading branch information
cvvz committed Jan 26, 2021
1 parent 3c2ee7a commit 3db64d4
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 3 deletions.
57 changes: 57 additions & 0 deletions content/post/compile-error.md
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表示;
4 changes: 2 additions & 2 deletions content/post/process-and-thread.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "进程和线程"
date: 2019-06-23T20:34:56+08:00
title: "关于进程和线程的一些思考"
date: 2018-06-23T20:34:56+08:00
draft: false
comments: true
keywords: ["process", "thread"]
Expand Down
2 changes: 1 addition & 1 deletion content/post/usage-of-git.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "Git笔记"
date: 2019-06-02T23:41:24+08:00
date: 2018-06-02T23:41:24+08:00
draft: false
comments: true
keywords: ["git"]
Expand Down
62 changes: 62 additions & 0 deletions content/post/version-control-of-shared-object.md
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的程序没有影响。

0 comments on commit 3db64d4

Please sign in to comment.