Найти имя пути из дескриптора dlopen на OSX

У меня есть dlopen() 'библиотека, и я хочу инвертировать обратно из дескриптора, который он передает мне, в полный путь к общей библиотеке. В Linux и друзьях я знаю, что я могу использовать dlinfo() для получения linkmap и итерации по этим структурам, но я не могу найти аналог на OSX. Самое близкое, что я могу сделать, это либо:

  • Используйте dyld_image_count() и dyld_get_image_name(), итерации по всем открытым в настоящее время библиотекам и надеемся, что могу догадаться, какой из них соответствует моему дескриптору

  • Как-то найти символ, который живет внутри дескриптора, который у меня есть, и передать это dladdr().

Если у меня есть априорное знание о имени символа внутри библиотеки, которую я только что открыл, я могу dlsym(), а затем использовать dladdr(). Это прекрасно работает. Но в общем случае, когда я понятия не имею, что находится внутри этой общей библиотеки, мне нужно было бы перечислять символы, чтобы сделать это, и я не знаю, как это сделать.

Поэтому любые советы о том, как искать путь к библиотеке из своего дескриптора dlopen, будут очень оценены. Спасибо!

Ответ 1

Примерно через год с использованием решения, предоставленного 0xced, мы обнаружили альтернативный метод, который проще и позволяет избежать одного (довольно редкого) режима отказа; в частности, потому, что фрагмент кода xx итерации выполняется через каждый загруженный dylib, находит первый экспортированный символ, пытается разрешить его в текущем поиске dylib и возвращает положительный результат, если этот символ найден в этом конкретном dylib, вы можете иметь ложные срабатывания, если первый экспортированный символ из произвольной библиотеки присутствует внутри находящегося в данный момент dylib.

Мое решение состояло в том, чтобы использовать _dyld_get_image_name(i) для получения абсолютного пути каждого загруженного изображения, dlopen() к этому изображению и сравнения дескриптора (после маскировки любых битов режима, установленных dlopen() из-за использования таких вещей, как RTLD_FIRST), чтобы гарантировать, что этот dylib фактически является тем же файлом, что и дескриптор, переданный в мою функцию.

Полную функцию можно увидеть здесь, как часть языка Джулии, с соответствующей частью, скопированной ниже:

// 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, но дух кода остается тем же.

Ответ 2

Вот как вы можете получить абсолютный путь дескриптора, возвращаемого dlopen.

  • Чтобы получить абсолютный путь, вам нужно вызвать функцию dladdr и получить поле Dl_info.dli_fname.
  • Чтобы вызвать функцию dladdr, вам нужно указать ему адрес.
  • Чтобы получить адрес, заданный с помощью дескриптора, вы должны вызвать функцию dlsym с символом.
  • Чтобы получить символ из загруженной библиотеки, вам нужно проанализировать библиотеку, чтобы найти ее таблицу символов и перебрать символы. Вам нужно найти внешний символ, потому что dlsym выполняет поиск только внешних символов.

Поместите все это вместе, и вы получите следующее:

#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