Linux 为什么dlsym()返回的符号值可以为null?

Linux 为什么dlsym()返回的符号值可以为null?,linux,dlsym,Linux,Dlsym,在Linux中。根据dlsym(3)Linux手册页 *Since the value of the symbol could actually be NULL (so that a NULL return from dlsym() need not indicate an error),* 当一个符号(特别是函数)实际为空时,为什么会出现这种情况?我正在检查代码,发现一段代码首先使用Dleror进行清理,然后使用dlsym进行清理,然后使用Dleror检查错误。但在调用之前,

在Linux中。根据dlsym(3)Linux手册页

    *Since the value of the symbol could actually be NULL
    (so that a NULL return from dlsym() need not indicate an error),*
当一个符号(特别是函数)实际为空时,为什么会出现这种情况?我正在检查代码,发现一段代码首先使用Dleror进行清理,然后使用dlsym进行清理,然后使用Dleror检查错误。但在调用之前,它不会检查结果函数是否为null:

  • 德莱罗()
  • a_func_name=…dlsym(…)
  • 如果(dlerror())转到结束
  • 一个函数名(…);//从不检查a_func_name==NULL

我只是一个审核人,所以没有选择权只添加检查。也许作者知道NULL永远不会被返回。我的工作是挑战这一点,但不知道是什么使这个返回为有效的NULL,这样我就可以检查在这个代码的上下文中是否可以满足这样的条件。如果您没有找到适合Google阅读的内容,那么一个指向好文档的指针就足够了,除非您想明确解释哪一个更好。

好吧,如果它返回时没有错误,那么指针是有效的,
NULL
与来自共享对象的任何随机指针一样非法。类似于错误的函数、数据或任何内容。

dlerror()
返回最后一个错误,而不是最后一个调用的状态。因此,如果没有其他内容,您显示的代码可能会从
dlsym()
中获得有效的结果,并欺骗自己认为存在错误(因为队列中仍然有一个错误)。dlerror背后的目的是提供人类可读的错误消息。如果没有打印结果,则使用错误。

我知道一种特殊情况,即dlsym()返回的符号值可以为NULL,即使用(IFUNCs)时。然而,可能还有其他的情况,因为在IFUNC发明之前的文本

下面是一个使用IFUNCs的示例。首先,将用于创建共享库的文件:

$ cat foo.c 
/* foo.c */

#include <stdio.h>

/* This is a 'GNU indirect function' (IFUNC) that will be called by
   dlsym() to resolve the symbol "foo" to an address. Typically, such
   a function would return the address of an actual function, but it
   can also just return NULL.  For some background on IFUNCs, see
   https://willnewton.name/uncategorized/using-gnu-indirect-functions/ */

asm (".type foo, @gnu_indirect_function");

void *
foo(void)
{
    fprintf(stderr, "foo called\n");
    return NULL;
}
$ cat main.c
/* main.c */

#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char *argv[])
{
    void *handle;
    void (*funcp)(void);

    handle  = dlopen("./foo.so", RTLD_LAZY);
    if (handle == NULL) {
        fprintf(stderr, "dlopen: %s\n", dlerror());
        exit(EXIT_FAILURE);
    }

    dlerror();      /* Clear any outstanding error */

    funcp = dlsym(handle, "foo");

    printf("Results after dlsym(): funcp = %p; dlerror = %s\n",
            (void *) funcp, dlerror());

    exit(EXIT_SUCCESS);
}
现在构建并运行一个案例,其中
dlsym()
返回
NULL
,而
dlerror()
也返回
NULL

$ cc -Wall -fPIC -shared -o libfoo.so foo.c
$ cc -Wall -o main main.c libfoo.so -ldl
$ LD_LIBRARY_PATH=. ./main
foo called
Results after dlsym(): funcp = (nil); dlerror = (null)

如果库/饼图是普通C编译的产物,则不能这样做,因为C永远不会将全局对象放在
NULL
地址,但您可以使用特殊的链接器技巧获得一个符号来解析为
NULL

null.c:

#include <stdio.h>
extern char null_addressed_char;
int main(void) 
{
    printf("&null_addressed_char=%p\n", &null_addressed_char);
}

如果您不允许任何这种奇怪的情况,您可以将
dlsym
返回的
NULL
视为错误。

这就是在
dlsym
之前立即调用
dlerror()
以清除最近的错误变量的目的。没有队列(如果手册页可信的话)。啊,错过了。是的,这是毫无意义但正确的
dlsym
被记录为在出错时返回NULL,但是
dlerror
的非NULL结果是等效的(除非出现线程安全性bug之类的问题——显然,如果另一个线程也在做同样的废话,这里就存在竞争)。这仍然是对API的滥用,当然每个线程都有自己的错误变量副本。无论如何,这是调用API的正确方法,而不是滥用。比较:
errno=0;INTA=itoa(s);if(errno).
因为if(a)无法区分
s=“0”来自
s=“垃圾”
。这是正确的方法,但另一方面,在这种特殊情况下,检查
NULL
也应该足够了。在glibc中可能是这样的(errno/perror也是如此),但对于无提示的文档来说则不然。我不指望所有的C库都那么健壮。。。事实上,我刚刚检查了仿生系统,它根本就不是线程安全的。“滥用”不是关于bug,而是关于意图——Dleror设计用于格式化错误消息,句号。抱歉,使用它来避免检查您已经拥有的返回值是疯狂的。如果返回值是共享变量(或函数)的值,这是有意义的。但它应该是地址,不是吗(或者这取决于标志)?好吧,假设它实际上是从一个地址表中读取一个值,并且二进制可以被编辑为在该表中有零(或者像你说的任何无效指针)。好吧,我不确定,但是导出的符号不能有一个绝对地址吗?你可以定义,在汇编程序中或者使用特定于GCC的技巧,给定的符号位于地址0,您可以
dlsym
该符号。谢谢。但是,在什么情况下会导致dlsym()返回NULL作为符号值?我无法生成一个案例,但我可能缺少一些内容。@mtk如果要使用defsym从null.c文件生成一个共享库来提供null_addressed_char符号,您应该能够dlopen它和dlsym null_addressed_char,并且它应该返回null而不设置错误。理论上。实际上,Linux的动态链接器正在设置一个错误,除非您删除main(因为它引用null_addressed_char),否则它甚至不会打开。根据dlsym手册页上的说法,我认为这是一个错误。@mtk这很有趣,但看起来dlsym手册页希望人们允许动态符号被空寻址的可能性,而Linux动态链接器却不能很好地处理它们。“实际上,Linux的动态链接器设置了一个错误,除非您删除主链接(因为它引用了null_addressed_char),所以它甚至不会dlopen。”。是的。我看到的就是这样。如果删除对null_addressed_char的引用,以便库可以加载,那么使用dlsym()进行查找会得到null+dlerror()=“未定义的符号”。这似乎与值为0的null_addressed_char有关。如果我将-defsym设置为非零值,那么dlsym()将成功。但是,dlsym(3)手册页中的哪些文本会让您得出这是一个bug的结论?@mtk
dlsym
手册页说:“因为符号的值实际上可能是null(因此从dlsym()返回null)无需指示错误),测试错误的正确方法是调用dlerro
$ clang null.c -Xlinker --defsym -Xlinker null_addressed_char=0 && ./a.out
&null_addressed_char=(nil)