Программирование

Получить адрес void* нестатической функции-члена для dladdr

Нельзя привести нестатическую функцию-член к void*. Используйте dlsym() с манглированным именем или обёртку, чтобы передать dladdr() и определить библиотеку.

Как получить адрес void* для нестатической member‑функции, чтобы передать его в dladdr и выяснить, из какой shared‑library загружена функция?

Для статических функций я могу просто написать:

cpp
void* fAddr = reinterpret_cast<void*>( &A::f );

но для нестатической member‑функции, например void g() в struct A, приведение запрещено:

cpp
void* gAddr = reinterpret_cast<void*>( &A::g );   // ❌ не разрешено

Какой код должен заменить gAddr = ???( &A::g );, чтобы получить указатель, который dladdr сможет принять?

cpp
#include <dlfcn.h>
#include <iostream>

struct __attribute__((visibility("default"))) A {
    static void f() {}
    void g() {}
};

void* fAddr = reinterpret_cast<void*>( &A::f );
// void* gAddr = ???( &A::g );

int main() {
    Dl_info info;
    dladdr(fAddr, &info);
    std::cout << info.dli_fname; // выводит имя shared library
    // то же самое для gAddr
}

Я попробовал reinterpret_cast<void*>( &A::g ) в Clang и получил:

error: reinterpret_cast from 'void (A::*)()' to 'void *' is not allowed

Что нужно использовать вместо ???, чтобы получить пригодный для dladdr адрес?

Адрес, который ожидает dladdr(), — это адрес обычной функции (или объекта данных).
Нестатический член‑функция не является обычной функцией – её тип void (A::*)(). На самом деле, в большинстве ABI представление указателя на член содержит не просто один машинный адрес (может включать поправку для виртуальных функций, множественного наследования и т.д.), и стандарт C++ явно запрещает преобразовывать его в void*.

Вы можете задаться вопросом, почему так, но правило очевидно.

Таким образом, нет переносимого или стандарта‑совместимого способа получить void* из нестатического члена‑функции, который можно передать в dladdr(). Единственные корректные подходы:

Что вы хотите Как это сделать
Получить файл/библиотеку, содержащую реализацию члена Использовать манглённое имя символа с dlsym() (или проверкой link_map) вместо dladdr().
Вызвать dladdr() на функции, которая вызывает член Написать небольшую статическую или свободную обёртку, которая перенаправляет вызов, и передать её адрес в dladdr().

Ниже два распространённых шаблона.


1. Используйте dlsym() с манглённым именем

Компиляторы Linux манглют имена член‑функций предсказуемым способом:

cpp
#include <dlfcn.h>
#include <iostream>
#include <cxxabi.h>
#include <string>

struct A {
    void g() {}
};

int main() {
    // Get the mangled name of A::g
    std::string mangled = abi::__cxa_demangle("vN1A1gEv", nullptr, nullptr, nullptr);
    // In practice you would know the mangled name or obtain it with
    // `nm`, `objdump`, or a compiler‑specific API.

    void *sym = dlsym(RTLD_DEFAULT, mangled.c_str());
    if (!sym) {
        std::cerr << "symbol not found\n";
        return 1;
    }

    Dl_info info;
    dladdr(sym, &info);
    std::cout << "function is in " << info.dli_fname << '\n';
}

Таким образом, вы полностью обойдёте преобразование в void*.


2. Вызовите dladdr() на обёртке

Если хотите оставить саму член‑функцию в выводе, оберните её в обычную функцию, которую можно передать в dladdr():

cpp
#include <dlfcn.h>
#include <iostream>

struct A {
    void g() { std::cout << "A::g\n"; }
};

// Wrapper that forwards the call
extern "C" void wrapper_A_g(A *self) {
    self->g();          // call the member
}

int main() {
    // Get the address of the wrapper
    void *wrapperAddr = reinterpret_cast<void*>(&wrapper_A_g);

    Dl_info info;
    dladdr(wrapperAddr, &info);
    std::cout << "wrapper is in " << info.dli_fname << '\n';

    // You can still use the original member function normally
    A a;
    (a.*&A::g)();     // call the member directly
}

wrapper_A_g — обычная C‑функция, поэтому её адрес можно безопасно преобразовать в void* и обработать в dladdr(). Обёртка не даёт точный адрес реализации член‑функции, но обеспечивает стабильную точку входа, принадлежащую тому же разделяемому объекту.


Итоги

  • Преобразование указателя на нестатический член‑функцию в void* является неопределённым поведением и не допускается стандартом C++.
  • dladdr() нельзя использовать напрямую с адресом нестатического члена‑функции.
  • Если нужна информация о библиотеке, используйте разрешение символов (dlsym()) или обёртку.

Это единственные переносимые, стандарта‑совместимые способы добиться желаемого.

Авторы
Проверено модерацией
Модерация