Avatar ·

Как посмотреть список символов (функций, классов) в .so файле на Linux?

Всем привет! Я новичок в C++ и только начал разбираться с динамическими библиотеками (.so) на Linux. У меня есть библиотека, собранная из нескольких файлов. Я хочу проверить, какие функции и классы она экспортирует, то есть какие символы доступны для других программ. Проблема: я скомпилировал свою библиотеку `libmylib.so` с помощью g++, но когда пытаюсь слинковать с ней тестовую программу, компоновщик выдает ошибки "undefined reference". Я подозреваю, что имена функций искажаются (name mangling) или я неправильно объявил их как extern "C". Пример кода, который я использую для сборки библиотеки: ```cpp // mylib.h #pragma once void hello(); int add(int a, int b); ``` ```cpp // mylib.cpp #include "mylib.h" #include void hello() { std::cout << "Hello from lib!" << std::endl; } int add(int a, int b) { return a + b; } ``` Компилирую так: ```bash g++ -shared -fPIC mylib.cpp -o libmylib.so ``` Как мне теперь посмотреть, какие символы (например, `hello` и `add`) на самом деле оказались в .so файле? И как отличить символы C от C++ (с искажением)? Буду благодарен за подробное объяснение с примерами команд. <<>>

Основной ответ: используйте утилиты nm и objdump

Для просмотра символов в .so файле в Linux есть несколько стандартных инструментов. Самый популярный — nm. Он показывает имена символов, их типы и адреса. Дополнительно можно использовать objdump -T для просмотра только динамических (экспортированных) символов.

1. Команда nm -D (динамические символы)

Так как .so файл — это динамическая библиотека, нас интересуют только экспортированные символы. Используйте флаг -D:

nm -D libmylib.so

Вывод будет примерно таким:

0000000000001119 T _Z3addii
0000000000001109 T _Z5hellov
                 U __cxa_finalize@GLIBC_2.17
                 U __gxx_personality_v0@CXXABI_1.3
                 U _Unwind_Resume@GCC_3.0
                 w __cxa_finalize@GLIBC_2.17

Здесь T означает, что символ определён в коде (text section). Вы видите искажённые имена C++: _Z3addii — это add(int, int), а _Z5hellov — hello(). U — неопределённые символы (из других библиотек).

2. Декодирование имён (demangling)

Чтобы получить читаемые имена, добавьте флаг -C:

nm -DC libmylib.so

Результат:

0000000000001119 T add(int, int)
0000000000001109 T hello()
                 U __cxa_finalize@GLIBC_2.17
                 U __gxx_personality_v0@CXXABI_1.3
                 U _Unwind_Resume@GCC_3.0

Теперь видно, что функции add и hello экспортируются с искажёнными именами (C++ mangling).

3. Использование objdump -T

Альтернативный способ — objdump. Он показывает динамическую таблицу символов:

objdump -T libmylib.so

Вывод:

libmylib.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000000  w   D  *UND*	0000000000000000 __cxa_finalize
0000000000000000  w   D  *UND*	0000000000000000 _ITM_registerTMCloneTable
0000000000000000  w   D  *UND*	0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000  w   D  *UND*	0000000000000000 __gmon_start__
0000000000001109 g    F .text	0000000000000015 _Z5hellov
0000000000001119 g    F .text	000000000000001a _Z3addii

Флаг g означает global (доступен для других модулей). F — функция.

4. Если вы хотите экспортировать символы без искажения (как в C)

Для совместимости с C или для простоты используйте extern "C". Измените объявление в заголовочном файле:

// mylib.h
#pragma once
extern "C" {
    void hello();
    int add(int a, int b);
}

После пересборки nm -D покажет:

0000000000001109 T hello
0000000000001119 T add

Имена не искажаются. Это удобно, если библиотеку будут использовать программы на C или других языках.

5. Просмотр всех символов (включая статические)

Если нужно увидеть все символы (не только экспортированные), используйте nm без флага -D:

nm libmylib.so

Вы увидите больше строк, включая внутренние символы (например, статические функции, глобальные переменные).

Дополнительные советы

  • Фильтрация: nm -DC libmylib.so | grep " T " — покажет только определённые функции.
  • Сравнение: Если у вас есть две версии .so, сравните списки символов с помощью diff.
  • Проверка видимости: Символы с флагом g (global) — экспортируются, с l (local) — скрыты.

Теперь вы можете легко проверить, какие функции доступны в вашей библиотеке, и отладить проблемы с компоновкой.

<<>>

Альтернативный ответ: использование readelf и утилит из binutils

Если nm или objdump недоступны, или вам нужна более детальная информация, используйте readelf. Эта утилита входит в состав GNU binutils и показывает структуру ELF-файла.

Команда readelf -s (все символы)

readelf -s libmylib.so

Вывод будет содержать полную таблицу символов, включая секции, размеры и типы. Для экспортированных символов лучше использовать readelf --dyn-syms:

readelf --dyn-syms libmylib.so

Пример:

Symbol table '.dynsym' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     5: 0000000000001109    15 FUNC    GLOBAL DEFAULT   13 _Z5hellov
     6: 0000000000001119    26 FUNC    GLOBAL DEFAULT   13 _Z3addii

Здесь Bind: GLOBAL — символ доступен для других модулей, Type: FUNC — функция.

Почему это может быть полезно?

  • Детализация: readelf показывает размер функции (в байтах), что помогает оценить её сложность.
  • Секции: Вы можете увидеть, в какой секции находится символ (например, .text для кода).
  • Проверка версий: Если библиотека использует версионирование символов (symbol versioning), readelf покажет это.

Сравнение с nm

Для быстрой проверки nm -DC удобнее, так как он сразу декодирует имена. Но если вам нужно автоматизировать анализ (например, в скриптах), readelf даёт более структурированный вывод, который легче парсить. Также readelf работает с файлами, которые могут быть повреждены или не полностью загружены.

Дополнительно: проверка зависимостей

Чтобы увидеть, от каких библиотек зависит ваш .so, используйте:

readelf -d libmylib.so | grep NEEDED

Это покажет список нужных .so файлов (например, libstdc