【问题标题】:ALSA unexpected results when called from shared library从共享库调用 ALSA 时出现意外结果
【发布时间】:2016-05-30 14:05:41
【问题描述】:

ALSA 库包含两个 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/)可能会遇到程序编译和链接没有错误或警告但发生错误行为的情况。

谁能解释这里发生了什么,也许这个问题可以被确认为一个错误并修复?

【问题讨论】:

    标签: linux gcc alsa


    【解决方案1】:

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


    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
    

    虽然与-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.soa.out 绑定不同版本的 snd_pcm_hw_params_set_rate_near

    $ 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 的 DSO howto §3.8:

    所有依赖于符号版本控制的方法都有一个要求 common:DSO 的用户绝对有必要始终 链接。

    [...]

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

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

    • 它尝试BASE 版本,位于 ELF 文件中版本定义表中的索引 1。
    • 它在索引 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 决定
    • @Kaz:删除旧符号会破坏与旧版本链接的旧二进制文件的兼容性,因此需要升级 ALSA 的版本。恕我直言,避免此类问题的最佳方法是将-z defs|--no-undefined设为默认值,AFAICT,不是这样的主要原因是允许构建回调主可执行文件定义的符号的插件,而我没有不认为需要传递链接器选项来启用该用例是不合理的。但这只是我个人的看法。
    • 这也一直困扰着我,将显式链接添加到 asound 为我解决了这个问题。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2013-11-07
    • 1970-01-01
    • 2017-10-04
    • 2018-07-18
    • 2015-05-30
    • 2016-11-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多