在OSX上从dlopen句柄中查找路径名

时间:2013-12-09 21:21:44

标签: c macos dyld

我有dlopen()编辑了一个库,我想从它传递给我的句柄反转回到共享库的完整路径名。在Linux和朋友们,我知道我可以使用dlinfo()获取链接映射并遍历这些结构,但我似乎无法在OSX上找到类似物。我能做的最接近的事情是:

  • 使用dyld_image_count()dyld_get_image_name(),遍历所有当前打开的库,希望我能猜出哪一个对应于我的句柄

  • 以某种方式找到一个生活在我手柄内部的符号,并将其传递给dladdr()

如果我对刚刚打开的库中的符号名称有先验知识,我可以dlsym()然后使用dladdr()。这很好。但是在一般情况下,我不知道这个共享库中有什么内容,我需要能够枚举符号才能做到这一点,我不知道该怎么做。

因此,非常感谢有关如何从其dlopen句柄查找库的路径名的任何提示。谢谢!

2 个答案:

答案 0 :(得分:16)

以下是如何获取dlopen返回的句柄的绝对路径。

  1. 要获取绝对路径,您需要调用dladdr函数并检索Dl_info.dli_fname字段。
  2. 要调用dladdr函数,您需要为其指定一个地址。
  3. 为了获得给定句柄的地址,您必须使用符号调用dlsym函数。
  4. 为了从加载的库中获取符号,您必须解析库以查找其符号表并迭代符号。您需要找到外部符号,因为dlsym仅搜索外部符号。
  5. 把它们放在一起然后你就明白了:

    #import <dlfcn.h>
    #import <mach-o/dyld.h>
    #import <mach-o/nlist.h>
    #import <stdio.h>
    #import <string.h>
    
    #ifdef __LP64__
    typedef struct mach_header_64 mach_header_t;
    typedef struct segment_command_64 segment_command_t;
    typedef struct nlist_64 nlist_t;
    #else
    typedef struct mach_header mach_header_t;
    typedef struct segment_command segment_command_t;
    typedef struct nlist nlist_t;
    #endif
    
    static const char * first_external_symbol_for_image(const mach_header_t *header)
    {
        Dl_info info;
        if (dladdr(header, &info) == 0)
            return NULL;
    
        segment_command_t *seg_linkedit = NULL;
        segment_command_t *seg_text = NULL;
        struct symtab_command *symtab = NULL;
    
        struct load_command *cmd = (struct load_command *)((intptr_t)header + sizeof(mach_header_t));
        for (uint32_t i = 0; i < header->ncmds; i++, cmd = (struct load_command *)((intptr_t)cmd + cmd->cmdsize))
        {
            switch(cmd->cmd)
            {
                case LC_SEGMENT:
                case LC_SEGMENT_64:
                    if (!strcmp(((segment_command_t *)cmd)->segname, SEG_TEXT))
                        seg_text = (segment_command_t *)cmd;
                    else if (!strcmp(((segment_command_t *)cmd)->segname, SEG_LINKEDIT))
                        seg_linkedit = (segment_command_t *)cmd;
                    break;
    
                case LC_SYMTAB:
                    symtab = (struct symtab_command *)cmd;
                    break;
            }
        }
    
        if ((seg_text == NULL) || (seg_linkedit == NULL) || (symtab == NULL))
            return NULL;
    
        intptr_t file_slide = ((intptr_t)seg_linkedit->vmaddr - (intptr_t)seg_text->vmaddr) - seg_linkedit->fileoff;
        intptr_t strings = (intptr_t)header + (symtab->stroff + file_slide);
        nlist_t *sym = (nlist_t *)((intptr_t)header + (symtab->symoff + file_slide));
    
        for (uint32_t i = 0; i < symtab->nsyms; i++, sym++)
        {
            if ((sym->n_type & N_EXT) != N_EXT || !sym->n_value)
                continue;
    
            return (const char *)strings + sym->n_un.n_strx;
        }
    
        return NULL;
    }
    
    const char * pathname_for_handle(void *handle)
    {
        for (int32_t i = _dyld_image_count(); i >= 0 ; i--)
        {
            const char *first_symbol = first_external_symbol_for_image((const mach_header_t *)_dyld_get_image_header(i));
            if (first_symbol && strlen(first_symbol) > 1)
            {
                handle = (void *)((intptr_t)handle | 1); // in order to trigger findExportedSymbol instead of findExportedSymbolInImageOrDependentImages. See `dlsym` implementation at http://opensource.apple.com/source/dyld/dyld-239.3/src/dyldAPIs.cpp
                first_symbol++; // in order to remove the leading underscore
                void *address = dlsym(handle, first_symbol);
                Dl_info info;
                if (dladdr(address, &info))
                    return info.dli_fname;
            }
        }
        return NULL;
    }
    
    int main(int argc, const char * argv[])
    {
        void *libxml2 = dlopen("libxml2.dylib", RTLD_LAZY);
        printf("libxml2 path: %s\n", pathname_for_handle(libxml2));
        dlclose(libxml2);
        return 0;
    }
    

    如果您运行此代码,它将产生预期结果:libxml2 path: /usr/lib/libxml2.2.dylib

答案 1 :(得分:7)

使用0xced提供的解决方案大约一年后,我们发现了一种更简单的替代方法,避免了一种(相当罕见的)故障模式;具体来说,因为0xced的代码片段遍历当前加载的每个dylib,找到第一个导出的符号,尝试在当前正在搜索的dylib中解析它,如果在该特定dylib中找到该符号,则返回正数,你可以拥有如果来自任意库的第一个导出符号恰好出现在您当前正在搜索的dylib内,则为误报。

我的解决方案是使用_dyld_get_image_name(i)来获取每个图像的绝对路径,dlopen()该图像,并比较句柄(在屏蔽掉由dlopen()到期设置的任何模式位之后使用像RTLD_FIRST这样的东西来确保这个dylib实际上是与传递给我的函数的句柄相同的文件。

完整的函数can be seen here,作为Julia语言的一部分,相关部分复制如下:

// Iterate through all images currently in memory
for (int32_t i = _dyld_image_count(); i >= 0 ; i--) {
    // dlopen() each image, check handle
    const char *image_name = _dyld_get_image_name(i);
    uv_lib_t *probe_lib = jl_load_dynamic_library(image_name, JL_RTLD_DEFAULT);
    void *probe_handle = probe_lib->handle;
    uv_dlclose(probe_lib);

    // If the handle is the same as what was passed in (modulo mode bits), return this image name
    if (((intptr_t)handle & (-4)) == ((intptr_t)probe_handle & (-4)))
        return image_name;
}

请注意,jl_load_dynamic_library()等函数是dlopen()的包装器,返回libuv类型,但代码的精神保持不变。