whole-archive的缺席

好久没更新了,来水一篇,毕竟今年有50篇的目标呢。

前两天遇到一个问题,因为链接依赖的某个库的时候少加了一个参数,导致最后程序逻辑出现了问题。缺席的参数是--whole-archive,由于依赖的库中使用了contructor这个函数属性来做一些特别的处理,两个因素叠加导致了问题。

先贴下这两个特性的描述:

1
2
3
4
5
6
7
8
--whole-archive
For each archive mentioned on the command line af-
ter the --whole-archive option, include every ob-
ject file in the archive in the link, rather than
searching the archive for the required object
files. This is normally used to turn an archive
file into a shared library, forcing every object to
be included in the resulting shared library.

--whole-archive的作用后面再说,constructor这个函数属性的作用就是可以让加了这个属性的函数在main函数执行之前自动被调用。

1
2
constructor
The constructor attribute causes the function to be called automatically before execution enters main ()

下面用一个小例子来描述下问题,小例子要做的事情就是打印出一个全局变量toy_table的值,并且我有一个静态库libnew_toy.a,我希望链接这个库的时候就能添加一些新的内容到全局变量toy_table中。先来看main是怎么写的。

代码列表

嗯,简单粗暴,就打印了下变量值,下面看下top.h的实现。

1
2
3
4
5
6
7
#include <stdio.h>
#include "toy.h"
int main(int argc, char **argv) {
printf("toy table size: %lu\n", toy_table.num_toy);
return 0;
}

toy.h

这个头文件定义了两个结构,一个全局变量,一个函数声明,一个宏。宏REGISTER_NEW_TOY中会声明并定义函数new_toy_init_##toy,这个函数添加了函数属性contructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdint.h>
#define MAX_TOY_NAME_LENGTH 20
#define MAX_TOY_COUNT 20
/* struct for a toy */
struct toy {
char name[MAX_TOY_NAME_LENGTH];
};
/* stcut for toy table */
struct toy_table {
uint32_t num_toy;
struct toy toys[MAX_TOY_COUNT];
};
/* global variable */
extern struct toy_table toy_table;
/* function to register new toy */
void new_toy_register(const struct toy *ops);
/* macro to invoke new_toy_register */
#define REGISTER_NEW_TOY(toy) \
void new_toy_init_##toy(void); \
void __attribute__((constructor, used)) new_toy_init_##toy(void) \
{ \
new_toy_register(&toy); \
}

toy.c

toy.c中会初始化全局变量toy_table,并实现函数new_toy_register

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include "toy.h"
struct toy_table toy_table = {
.num_toy = 0
};
/* register a new toy
* fill the toy_table.toys, and incr the toy_table.num_toy
* no lock here as just a sample :)
*/
void new_toy_register(const struct toy *toy) {
struct toy *added_toy = &toy_table.toys[toy_table.num_toy];
snprintf(added_toy->name, sizeof(added_toy->name), "%s", toy->name);
toy_table.num_toy++;
}

下面看下我们想生成的静态库libnew_toy.a对应有源码。

new_toy.c

这里定义了一个my_new_toy的变量,并且调用了宏REGISTER_NEW_TOY

1
2
3
4
5
6
7
#include "toy.h"
static const struct toy my_new_toy = {
.name = "my_new_toy",
};
REGISTER_NEW_TOY(my_new_toy)

实验

生成静态库libnew_toy.a

1
2
$ gcc -c -o new_toy.o new_toy.c
$ ar rcs libnew_toy.a new_toy.o

不加--whole-archive直接链接

1
2
3
$ gcc -o print_toy_table print_toy_table.c toy.c libnew_toy.a
$ ./print_toy_table
toy table size: 0

可以看到虽然链接了静态库libnew_toy.a,但是相应的函数被没有被执行。回头再看看--whole-archive的描述:

include every object file in the archive in the link, rather than
searching the archive for the required object files.

因为new_toy.c中的内容没有被print_toy_table.c引用,所以自然REGISTER_NEW_TOY(my_new_toy)这一句没有被执行。

链接时添加参数--whole-archive

1
2
3
4
$ gcc -o print_toy_table print_toy_table.c toy.c -Wl,--whole-archive libnew_toy.a -Wl,--no-whole-archive
$ ./print_toy_table
add toy: my_new_toy
toy table size: 1

可以看到在main函数执行之前my_new_toy就被添加到toy_table中了。

总结

事情落地之前,总有你意想不到的问题会发生,这时候就需要利用以往的经验快速解决。