Программа Linux C: как найти библиотеку, к которой принадлежит функция

Скажите во время выполнения, я хочу узнать, где определена функция "printf". Как мне это сделать? Моя первая попытка состояла в том, чтобы распечатать адрес "printf" и сравнить его с отображением виртуального адреса процесса:

моя программа:

#include <stdio.h>
#include <unistd.h>

void main()
{
    printf("address of printf is 0x%X\n", printf);
    printf("pid is  %d\n", getpid());
    while (1);
}

выход:

-bash-4.1$ ./a &
[1] 28837
-bash-4.1$ address of printf is 0x4003F8
pid is  28837

Однако это говорит о том, что функция определена в моей собственной программе!

-bash-4.1$ head /proc/28837/maps 
00400000-00401000 r-xp 00000000 08:06 6946857                            /data2/temp/del/a      <<<<<<< Address 0x4003F8 is in my own program?
00600000-00601000 rw-p 00000000 08:06 6946857                            /data2/temp/del/a
397ec00000-397ec20000 r-xp 00000000 08:11 55837039                       /lib64/ld-2.12.so
397ee1f000-397ee20000 r--p 0001f000 08:11 55837039                       /lib64/ld-2.12.so
397ee20000-397ee21000 rw-p 00020000 08:11 55837039                       /lib64/ld-2.12.so
397ee21000-397ee22000 rw-p 00000000 00:00 0 
397f000000-397f18a000 r-xp 00000000 08:11 55837204                       /lib64/libc-2.12.so
397f18a000-397f38a000 ---p 0018a000 08:11 55837204                       /lib64/libc-2.12.so
397f38a000-397f38e000 r--p 0018a000 08:11 55837204                       /lib64/libc-2.12.so
397f38e000-397f38f000 rw-p 0018e000 08:11 55837204                       /lib64/libc-2.12.so

Разве это не вызов в libc? Как узнать, откуда взялась эта "printf" или любая другая функция?

Ответ 1

Во время выполнения вы можете использовать gdb для этого:

(terminal 1)$ ./a
pid is  16614
address of printf is 0x400450

(terminal 2)$ gdb -p 16614
(...)
Attaching to process 16614
(...)
0x00000000004005a4 in main ()
(gdb)

(gdb) info sym printf
printf in section .text of /lib/x86_64-linux-gnu/libc.so.6

Если вы не хотите прерывать свою программу или не хотите использовать gdb, вы также можете запросить ld.so для вывода некоторой информации об отладке:

(terminal 1)$ LD_DEBUG=bindings LD_DEBUG_OUTPUT=syms ./a
pid is  17180
address of printf is 0x400450

(terminal 2)$ fgrep printf syms.17180
    17180:  binding file ./a [0] to /lib/x86_64-linux-gnu/libc.so.6 [0]: normal symbol 'printf' [GLIBC_2.2.5]

Ответ 2

Адрес, который вы наблюдаете, находится в таблице привязки процедур (PLT). Этот механизм используется, когда местоположение внешнего (динамически связанного) символа не известно в то время, когда ваш бинарный файл скомпилирован и связан.

Цель состоит в том, что внешняя связь происходит только в одном месте, PLT, а не во всех местах всего вашего кода, где происходит вызов символа. Итак, если вызывается printf(), путь:

main → printf @PLT → printf @libc

Во время выполнения вы не можете легко узнать, в какой внешней библиотеке находится функция, которую вы вызываете; вам придется анализировать коды операций в пункте назначения (PLT), который обычно извлекает адрес из раздела.dynamic и перескакивает туда, затем просматривает, где символ действительно находится, и, наконец, parse/proc/pid/maps, чтобы получить внешняя библиотека.

Ответ 3

  1. указатели printf с использованием %p, а не %X:

    printf("address of printf is 0x%p\n", printf);
    
  2. Если вы скомпилируете статический libc, то printf будет связан с вашим двоичным

  3. при компиляции с

    gcc -fPIC a.c # (older gccs)
    ...
    gcc -fno-plt a.c # (gcc 6 and above)
    

    выходы:

    address of printf is 0x0x7f40acb522a0
    

    который находится внутри

    7f40acaff000-7f40accc2000 r-xp 00000000 fd:00 100687388                  /usr/lib64/libc-2.17.so
    

Читайте, что здесь означает @plt? чтобы узнать больше об этом.

Ответ 4

Скажите во время выполнения, я хочу узнать, где определена функция "printf".

В общем и абсолютном выражении вы, вероятно, не сможете (по крайней мере, не легко). Данная функция может быть определена в нескольких библиотеках (для printf это маловероятно, поскольку она находится в стандартной библиотеке C).

Если вы построите свою систему Linux с нуля, вы можете мечтать о том, что что-то обрабатывает каждую библиотеку во время сборки (например, при создании каждой общей библиотеки вы можете получить все свои общедоступные имена с помощью nm (1) и поместить их в некоторую базу данных). На сегодняшний день это еще не сделано, но некоторые исследовательские проекты идут в этом направлении (в частности, программный и другие в 2019 году).

Кстати, у вас может быть несколько библиотек, определяющих printf. Например, если вы устанавливаете GNU glibc и musl-libc на свой компьютер (или, более вероятно, если у вас есть несколько вариантов glibc). Конкретная программа вряд ли будет использовать как (но все еще может, теоретически, dlopen оба из них).

Возможно, вам нужна функция dladdr (3) для Linux. Из некоторого заданного адреса он сообщает об этом общему объекту.

функция определена в моей собственной программе

Да. Узнайте больше о динамической компоновке. В частности, прочитайте статью Drepper How To Write Shared Libraries. Поймите, какова цель таблицы связывания процедур.

Ответ 5

Разберите файл эльфа для необходимых динамически связанных библиотек. Затем вы можете проанализировать их поиск требуемого символа

Ответ 6

Вы можете сделать это статически. Нет необходимости выполнять:

$ readelf -Ws a.out | grep printf
      1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.2.5 (2)
     51: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]@GLIBC_2.2.5