【问题标题】:Why is libao silent when loaded using dlopen?为什么使用 dlopen 加载时 libao 静默?
【发布时间】:2019-12-29 20:06:11
【问题描述】:

我正在编写一个使用libao 进行音频输出的应用程序。该部分 我调用 libao 的程序中的一部分存在于共享对象中:

// playao.c
// compile with: gcc -shared -o libplayao.so playao.c -lao -lm
#include <ao/ao.h>
#include <stdio.h>
#include <math.h>

void playao(void) {
    int i;
    unsigned char samps[8000];
    ao_initialize();
    ao_sample_format sf;
    sf.bits = 8;
    sf.rate = 8000;
    sf.channels = 1;
    sf.byte_format = AO_FMT_NATIVE;
    sf.matrix = "M";
    ao_device *device = ao_open_live(ao_default_driver_id(), &sf, NULL);
    if(!device) {
        puts("ao_open_live error");
        ao_shutdown();
        return;
    }
    for(i = 0; i < 8000; ++i) {
        float time = (float)i / 8000;
        float freq = 440;
        float angle = time * freq * M_PI * 2;
        float value = sinf(angle);
        samps[i] = (unsigned char)(value * 127 + 127);
    }
    if(!ao_play(device, (char *)samps, 8000)) {
        puts("ao_play error");
    }
    ao_close(device);
    ao_shutdown();
}

如果我在程序中链接到这个共享对象,它可以正常工作:

// directlink.c
// compile with: gcc -o directlink directlink.c libplayao.so -Wl,-rpath,'$ORIGIN'
void playao(void);

int main(int argc, char **argv) {
    playao();
    return 0;
}

但是,如果我使用dlopen/dlsym 来调用它,则没有错误,但是 程序不会发出任何声音:

// usedl.c
// compile with: gcc -o usedl usedl.c -ldl
#include <dlfcn.h>
#include <stdio.h>

int main(int argc, char **argv) {
    void *handle = dlopen("./libplayao.so", RTLD_LAZY);
    if(!handle) {
        puts("dlopen failed");
        return 1;
    }
    void *playao = dlsym(handle, "playao");
    if(!playao) {
        puts("dlsym failed");
        dlclose(handle);
        return 1;
    }
    ((void (*)(void))playao)();
    dlclose(handle);
    return 0;
}

但是,运行 usedlLD_PRELOAD=/usr/lib/x86_64-linux-gnu/libao.so.4 确实工作。所以有一些关于 libao 的东西想要在 程序启动,不喜欢以后加载。

这是为什么?有什么办法可以解决这个问题,让 libao 工作 即使在程序执行的后期加载也正确?

如果重要的话,我正在运行 Debian 10 “buster”。

【问题讨论】:

    标签: c audio dlopen dynamic-loading


    【解决方案1】:

    我在 Freenode 的 #xiph 频道上询问了这个问题,xiphmont 建议转为 turning on verbose mode。一旦我这样做了,失败的案例就开始收到消息:

    ERROR: Failed to load plugin /usr/lib/x86_64-linux-gnu/ao/plugins-4/libalsa.so => dlopen() failed
    

    所以 libao 本身正在尝试dlopen 某事,但它失败了。它没有显示更多细节,所以我在 GDB 下运行程序并在dlopen 上设置断点。在遇到libalsadlopen 断点并运行finish 后,我尝试使用print (const char *)dlerror() 找出错误所在。有了这个,我得到了一个更详细的错误:

    /usr/lib/x86_64-linux-gnu/ao/plugins-4/libalsa.so: undefined symbol: ao_is_big_endian
    

    所以 ao 的 libalsa 插件试图在 libao 中引用符号,但没有找到它们。为什么会这样?参考dlopen 文档,我看到了:

    以下值中的零个或多个也可以在 flags 中进行 ORed:

    RTLD_GLOBAL:此共享对象定义的符号将可用于后续加载的共享对象的符号解析。

    RTLD_LOCAL:这是 RTLD_GLOBAL 的反面,如果两个标志都没有指定,则为默认值。此共享对象中定义的符号不可用于解析后续加载的共享对象中的引用。

    因为我的dlopen 调用只使用了RTLD_LAZY 并且没有包含RTLD_GLOBALRTLD_LOCAL,所以它默认为RTLD_LOCAL,它不会暴露共享对象中的符号(如ao_is_big_endian)到随后加载的共享对象(如libalsa.so)。

    所以,我尝试将代码更改为:

    void *handle = dlopen("./libplayao.so", RTLD_LAZY);
    

    收件人:

    void *handle = dlopen("./libplayao.so", RTLD_LAZY | RTLD_GLOBAL);
    

    你瞧,它有效!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-30
      • 2015-03-02
      • 2020-11-28
      • 2016-04-25
      • 2011-07-26
      • 1970-01-01
      • 2020-01-26
      • 1970-01-01
      相关资源
      最近更新 更多