ALSA库包含两个API版本,可通过定义用于访问较旧版本的ALSA_PCM_OLD_HW_PARAMS_API来启用。它采用了一些高级技巧(使用.symver汇编指令)来使单个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();
}

第二个是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/)可能会遇到程序编译和链接时没有错误或警告,但仍发生错误行为的情况。

有人可以解释这里发生的事情吗,也许这个问题可以被认为是错误并已解决?

最佳答案

这是设计使然。如果在链接-lasound时未添加libmylib.so,则链接器将看不到符号版本,因此它将 undefined symbol 添加非版本引用。当运行时链接程序绑定(bind)非版本符号时,它将尝试使用最早的版本。

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_nearlibmylib.so绑定(bind)不同版本的a.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]

此行为已记录。根据Ulrich Drepper的DSO howto§3.8:



然后,在“符号查找”中引用的document继续说明,当尝试将非版本化引用绑定(bind)到版本化定义时,它将尝试以下操作:
  • 在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
    

    关于linux - 从共享库调用时,ALSA出现意外结果,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35480928/

    10-12 17:08