为什么同名的“构造函数”属性函数在 X 库中调用了 3 次,而不是在 X、Y、Z 库中调用了 1 次

问题描述 投票:0回答:1

场景:

$ cat lib.c
#include <stdio.h>
#define STR_(x) #x
#define STR(x) STR_(x)
#define CAT_(x,y) x##y
#define CAT(x,y) CAT_(x,y)

__attribute__((constructor))
void CAT(foo,)(void) { printf("foo" STR(N) " %p\n", CAT(foo,)); }
void CAT(bar,N)(void){ puts("bar" STR(N)); }

$ cat main.c
void barx(void);
void bary(void);
void barz(void);

int main(void)
{
    barx();
    bary();
    barz();
}

$ cat build_run.sh
gcc lib.c -DN=x -c -fPIC -o libx.o && gcc libx.o -o libx.so -shared &&
gcc lib.c -DN=y -c -fPIC -o liby.o && gcc liby.o -o liby.so -shared &&
gcc lib.c -DN=z -c -fPIC -o libz.o && gcc libz.o -o libz.so -shared &&
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH &&
gcc -L. -o main main.c -lx -ly -lz &&
./main

$ bash build_run.sh
foox 0x7f0bf002e139
foox 0x7f0bf002e139
foox 0x7f0bf002e139
barx
bary
barz

在这里我们看到:

  1. 所有
    .so
    库都具有同名的
    constructor
    属性函数
    foo
  2. 函数
    foo
    从库 X 调用 3 次(这可能是意外行为),而不是从库 X、Y、Z 调用 1 次(这可能是预期行为)。

据我了解,

constructor
属性函数
foo
的地址(直接或间接)放置在
.init_array
部分
中。因此,函数名称应该是无关紧要的。

核心问题:为什么函数

foo
从库 X 被调用 3 次,而不是从库 X、Y、Z 调用 1 次?


额外观察:

  1. 如果我们将
    lib.c
    CAT(foo,)
    更改为
    CAT(foo,N)
    并重新运行
    build_run.sh
    ,那么我们将看到:
$ bash build_run.sh
fooz 0x7fc121dcc139
fooy 0x7fc121dd1139
foox 0x7fc121dd6139
barx
bary
barz

这是预期的行为。

  1. 在 Cygwin 上运行原始(即使用
    CAT(foo,)
    )示例会导致函数
    foo
    从库 X、Y、Z 中被调用 1 次(这可能是预期的行为)。

系统和软件信息:

$ uname -a
Linux xxx 5.15.0-79-generic #86-Ubuntu SMP Mon Jul 10 16:07:21 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
c dll constructor shared-libraries
1个回答
0
投票

我认为这是符号插入的一个例子。

加载共享库时,动态链接器默认全局解析符号(函数名、变量)。如果两个或多个共享库定义相同的符号(例如,

foo
),则该符号的第一个加载版本将用于所有后续引用。这称为“符号插入”。

潜在的修复: 编译每个共享库时,您可以使用

-Bsymbolic
链接器选项。

Cygwin 使用 DLL 库,而 Windows 有不同的机制。在 Windows 中,每个 DLL 的函数和变量都有不同的地址空间,这意味着符号名称在每个 DLL 内独立解析,而不是在所有加载的库中全局解析。

© www.soinside.com 2019 - 2024. All rights reserved.