Linux 从共享库调用ALSA时出现意外结果

Linux 从共享库调用ALSA时出现意外结果,linux,gcc,alsa,Linux,Gcc,Alsa,ALSA库包含两个API版本,通过定义用于访问旧版本的ALSA_PCM_OLD_HW_PARAMS_API来启用。它采用了一些高级技巧(使用.symverassembly指令)使单个C库能够包含不同的函数,具有相同的名称但参数不同(对于旧API和新API)。这一切都很好,但在某些情况下会造成麻烦 例如,让我们创建两个源文件。第一个是main.cpp: #include <alsa/asoundlib.h> void lib_func(); void local_func() {

ALSA库包含两个API版本,通过定义用于访问旧版本的ALSA_PCM_OLD_HW_PARAMS_API来启用。它采用了一些高级技巧(使用
.symver
assembly指令)使单个C库能够包含不同的函数,具有相同的名称但参数不同(对于旧API和新API)。这一切都很好,但在某些情况下会造成麻烦

例如,让我们创建两个源文件。第一个是main.cpp:

#include <alsa/asoundlib.h>

void lib_func();

void local_func()
{
    int err;
    unsigned int rate = 22050;
    snd_pcm_t *handle;
    snd_pcm_hw_params_t *params;
    snd_pcm_hw_params_alloca(&params);
    assert(snd_pcm_open(&handle, "default", snd_pcm_stream_t(0), 0) >= 0);
    assert(snd_pcm_hw_params_any(handle, params) >= 0);
    err = snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0);
    printf("err out of lib: %d\n", err);
    snd_pcm_close(handle);
}

int main(int argc, char *argv[])
{
    local_func();
    lib_func();
}
我们在运行时得到的结果是:

err out of lib: 0
err in lib: 192000
这意味着两个代码模块之间的
snd_pcm_hw_params_set_rate_near
表现不同。在共享库中,它错误地调用了旧版本的函数,该函数期望采样率为
unsigned int val
,而新版本期望
unsigned int*val
,并返回采样率(192000,因为它不接受我们的输入),而不是错误代码

我们找到了解决此问题的方法:在创建共享库时,将
-lasound
参数添加到链接器。然而,这仍然是一个bug,一些用户(比如这个用户,我们认为他有这个确切的问题:)可能会遇到这样的情况:程序编译和链接时没有错误或警告,但出现了错误行为


有人能解释一下这里发生了什么,也许这个问题可以被认为是一个bug并得到修复吗?

这是设计的。如果链接
libmylib.so
时不添加
-lasound
,链接器将看不到符号版本,因此会将未版本化的引用添加到未定义的符号。当运行时链接器绑定非版本符号时,它会尝试使用最早的版本


ALSA对附近的
snd\u pcm\u hw\u params\u set\u rate\u有以下定义:

$ readelf -Ws /usr/lib64/libasound.so.2 | grep set_rate_near
   255: 0000000000062640    72 FUNC    GLOBAL DEFAULT   12 snd_pcm_hw_params_set_rate_near@ALSA_0.9
   256: 000000000005d140    61 FUNC    GLOBAL DEFAULT   12 snd_pcm_hw_params_set_rate_near@@ALSA_0.9.0rc4
  1115: 000000000005d140    61 FUNC    GLOBAL DEFAULT   12 __snd_pcm_hw_params_set_rate_near@@ALSA_0.9
$ readelf -Ws libmylib.so | grep set_rate_near
     3: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND snd_pcm_hw_params_set_rate_near
    31: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND snd_pcm_hw_params_set_rate_near
有一个较旧的
ALSA_0.9
版本,还有一个较新的
ALSA_0.9.0rc4
,后者标记为默认(
@
),当静态链接器(
ld
)链接到
-lasound
时将使用该版本

ld
链接
libmylib.so
而没有
-lasound
时,
libmylib.so
附近有一个未定义且未版本化的对
snd\u pcm\u hw\u params\u set\u rate\u的引用:

$ readelf -Ws /usr/lib64/libasound.so.2 | grep set_rate_near
   255: 0000000000062640    72 FUNC    GLOBAL DEFAULT   12 snd_pcm_hw_params_set_rate_near@ALSA_0.9
   256: 000000000005d140    61 FUNC    GLOBAL DEFAULT   12 snd_pcm_hw_params_set_rate_near@@ALSA_0.9.0rc4
  1115: 000000000005d140    61 FUNC    GLOBAL DEFAULT   12 __snd_pcm_hw_params_set_rate_near@@ALSA_0.9
$ readelf -Ws libmylib.so | grep set_rate_near
     3: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND snd_pcm_hw_params_set_rate_near
    31: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND snd_pcm_hw_params_set_rate_near
而已针对
-lasound
链接的
a.out
,包含针对默认版本的参考:

$ readelf -Ws a.out | grep set_rate_near
    15: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND snd_pcm_hw_params_set_rate_near@ALSA_0.9.0rc4 (5)
    56: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND snd_pcm_hw_params_set_rate_near@@ALSA_0.9.0rc4
然后,当运行时链接器(
ld.so
)使用此信息时,它会在
libmylib.so
a.out
附近绑定不同版本的
snd\u pcm\u hw\u params\u set\u rate\u:

$ LD_DEBUG=bindings LD_LIBRARY_PATH=. ./a.out 2>&1 | grep set_rate_near
     11364:     binding file ./libmylib.so [0] to /usr/lib64/libasound.so.2 [0]: normal symbol `snd_pcm_hw_params_set_rate_near'
     11364:     binding file /usr/lib64/libasound.so.2 [0] to /usr/lib64/libasound.so.2 [0]: normal symbol `__snd_pcm_hw_params_set_rate_near' [ALSA_0.9]
     11364:     binding file ./a.out [0] to /usr/lib64/libasound.so.2 [0]: normal symbol `snd_pcm_hw_params_set_rate_near' [ALSA_0.9.0rc4]

这种行为被记录在案。来自Ulrich Drepper的§3.8:

所有依赖于符号版本控制的方法在中都有一个要求 常见:DSO的用户必须始终 联系它

[……]

问题是,除非使用包含定义的DSO 链接时,链接器无法将版本名添加到未定义的 参考资料。遵循符号版本控制规则[]这意味着 使用运行时可用的最早版本,但通常不使用 预期版本

“符号查找”中的引用接着解释,当尝试将非版本化引用绑定到版本化定义时,它会尝试以下操作:

  • 它在ELF文件版本定义表的索引1处尝试
    BASE
    版本
  • 它尝试索引2处的基线版本,即文件开始使用符号版本时使用的第一个版本
  • 否则,如果仅为版本定义符号,则使用该版本的符号
版本定义表如下所示:

$ readelf -a /usr/lib64/libasound.so.2 | awk '/Version.*.gnu.version_d/,/^$/'
Version definition section '.gnu.version_d' contains 8 entries:
  Addr: 0x000000000001b470  Offset: 0x01b470  Link: 4 (.dynstr)
  000000: Rev: 1  Flags: BASE   Index: 1  Cnt: 1  Name: libasound.so.2
  0x001c: Rev: 1  Flags: none  Index: 2  Cnt: 1  Name: ALSA_0.9
  0x0038: Rev: 1  Flags: none  Index: 3  Cnt: 2  Name: ALSA_0.9.0rc4
  0x0054: Parent 1: ALSA_0.9
  [...]

链接器标志
-z,defs |--no undefined
可用于在链接时禁止未解析的符号:

$ g++ -Wl,-z,defs -shared -fPIC -o libmylib.so mylib.cpp 
/tmp/ccfJdVDG.o: In function `lib_func()':
mylib.cpp:(.text+0x20): undefined reference to `snd_pcm_hw_params_sizeof'
mylib.cpp:(.text+0x4b): undefined reference to `snd_pcm_hw_params_sizeof'
mylib.cpp:(.text+0x7c): undefined reference to `snd_pcm_open'
mylib.cpp:(.text+0xb2): undefined reference to `snd_pcm_hw_params_any'
mylib.cpp:(.text+0xf1): undefined reference to `snd_pcm_hw_params_set_rate_near'
mylib.cpp:(.text+0x116): undefined reference to `snd_pcm_close'
collect2: ld returned 1 exit status

谢谢你的详细回复。你说这是出于设计,并引用了一篇关于编写库的论文(你希望ALSA的大多数用户都能自然地阅读它吗?),但我仍然认为这是拙劣的设计;显然,我们的一个团队多年来一直被这个问题困扰,没有任何人能够对它指手画脚,我相信世界各地的许多用户也是如此。我认为必须开发一些机制,这将导致我问题中引用的编译和链接命令组合失败,并出现错误,而不是在错误版本的函数中进行无声链接。(续)这是否可能,以及应该如何实现(这是对gcc、ALSA或两者的修改)但是,这超出了我的专业水平。这里有链接器标志
-z,defs
。也许默认情况下它应该是打开的,但这应该由发行版工具链维护人员和binutils决定upstream@Kaz:删除旧符号将破坏与旧版本链接的旧二进制文件的兼容性,因此需要升级ALSA的版本。恕我直言,避免这类麻烦的最好方法是使
-z defs |--no undefined
成为默认值,AFAICT,它不是这样的主要原因,是允许构建调用主可执行文件定义的符号的插件,我不认为需要传递链接器选项来启用该用例是不合理的。但这只是我个人的观点。这也一直困扰着我,并为我添加了与asound的明确链接。非常感谢!