从共享库调用时,ALSA意外结果

时间:2016-02-18 12:01:19

标签: linux gcc alsa

ALSA lib包含两个API版本,通过定义ALSA_PCM_OLD_HW_PARAMS_API来访问旧版本来启用。它采用了一些高级技巧(使用.symver汇编指令)来使单个C库包含不同的函数,这些函数具有相同的名称但不同的参数(对于旧的和新的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();
}

第二个是mylib.cpp:

#include <alsa/asoundlib.h>

void lib_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 in lib: %d\n", err);
    snd_pcm_close(handle);
}

请注意,除了打印的消息外,local_func()lib_func()的内容相同。

在Linux机器上(我们测试了Ubuntu 12 / gcc 4.6.3和Ubuntu 14 / gcc 4.8.4)构建并运行:

g++ -shared -fPIC -o libmylib.so mylib.cpp && g++ main.cpp -lasound -L . -lmylib
LD_LIBRARY_PATH=. ./a.out

运行时得到的结果是:

err out of lib: 0
err in lib: 192000

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

我们找到了解决此问题的方法:在创建共享库时将-lasound参数添加到链接器。但是,这仍然是一个错误,一些用户(例如我们认为有这个问题的用户:http://www.linuxquestions.org/questions/programming-9/snd_pcm_hw_params_set_rate_near-returns-huge-value-900199/)可能遇到程序编译和链接没有错误或警告且行为不正确的情况发生。

有人可以解释一下这里发生了什么,也许这个问题可能会被认为是一个错误而且已经修复了吗?

1 个答案:

答案 0 :(得分:2)

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

ALSA对snd_pcm_hw_params_set_rate_near具有以下定义:

$ 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

有较旧的ALSA_0.9版本和较新的ALSA_0.9.0rc4,后者标记为默认值(@@),将在静态链接器(ld时使用)链接-lasound

ld链接libmylib.so-lasound没有libmylib.so时,snd_pcm_hw_params_set_rate_near最终会有$ 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 的未定义和非版本化引用:

a.out

虽然与-lasound关联的$ 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

然后,当运行时链接程序(snd_pcm_hw_params_set_rate_near)使用此信息时,它最终会为libmylib.soa.out绑定不同版本的$ 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]

BASE

记录此行为。来自Ulrich Drepper的DSO howto§3.8:

  

所有依赖于符号版本控制的方法都有一个要求   常见的:DSO的用户始终是绝对必要的   与它联系。

     

[...]

     

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

引用的document,在“符号查找”中,然后继续解释当尝试将非版本化引用绑定到版本化定义时,它会尝试以下操作: / p>

  • 它在ELF文件的版本定义表中的索引1处尝试$ 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 [...] 版本。
  • 它在索引2处尝试基线版本,即文件启动时使用的第一个版本使用符号版本。
  • 否则,如果仅为版本定义符号,则使用该版本的符号。

版本定义表如下所示:

-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 可用于在链接时禁止未解析的符号:

{{1}}