Эта информация доступна в таблице символов двоичного файла, хотя она может не означать того, чего вы ожидаете.
Компилятор берет один или несколько исходных файлов, компилирует код в объектный код и генерируетобъектный файл (.o в Unix, .obj в Windows).Все переменные и функции, на которые есть ссылки в исходном файле, упоминаются в таблице символов.Переменные и функции, определенные в исходном файле, имеют определенные адреса и размеры, в то время как символы, не определенные в исходном файле, помечаются как неопределенные и должны быть связаны позже.Все символы перечислены относительно определенного раздела.Общими разделами являются «.text» для исполняемого кода, «.bss» для переменных, которые инициализируются нулями при запуске программы, и «.data» для переменных, инициализированных ненулевыми значениями.
Компоновщик принимаетодин или несколько объектных файлов, объединяет разделы (помещая весь код и данные из каждого объектного файла в один большой раздел для кода и данных) и записывает выходной файл.Этот выходной файл может быть исполняемым или общей библиотекой.Исполняемый файл на диске все еще не имеет указателя для каждой переменной;он по-прежнему сохраняет смещение от начала раздела к переменной.
При запуске исполняемого файла динамический загрузчик операционной системы считывает исполняемый файл, находит каждый раздел и выделяет память для этого раздела.(Он также может устанавливать различные разрешения для каждого раздела - сегмент «.text» часто помечается как доступный только для чтения, и (на процессорах, которые его поддерживают) сегменты данных иногда помечаются как неисполняемые.) Только тогдапеременная get указатель - когда код должен получить доступ к определенной переменной, он добавляет адрес начала раздела к смещению от начала раздела, чтобы получить указатель.
Вы можете использовать различныеинструменты для исследования таблицы символов каждого двоичного файла.GNU toolchain objdump
(используется в Linux) является одним из таких инструментов.
Для простой программы hello-world на C:
#include <stdio.h>
const char message[] = "Hello world!\n";
int main(int argc, char ** argv) {
printf(message);
return 0;
}
Я ее компилирую (но не связываю)в моем окне Linux:
$ gcc -c hello.c -o hello.o
Теперь я могу посмотреть на таблицу символов:
$ objdump -t hello.o
hello.o: file format elf32-i386
SYMBOL TABLE:
00000000 l df *ABS* 00000000 hello.c
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
00000000 l d .bss 00000000 .bss
00000000 l d .rodata 00000000 .rodata
00000000 l d .note.GNU-stack 00000000 .note.GNU-stack
00000000 l d .comment 00000000 .comment
00000000 g O .rodata 0000000e message
00000000 g F .text 0000002b main
00000000 *UND* 00000000 puts
Первый столбец - это адрес каждого символа относительно начала раздела.Каждый символ имеет различные флаги, и некоторые символы используются в качестве подсказок для остальной части цепочки инструментов и отладчика.(Если бы я создавал символы отладки, я бы также увидел много записей, посвященных им.) Моя простая программа имеет только одну переменную:
00000000 g O .rodata 0000000e message
В пятом столбце указано, что символ message
- это размер0xe - 14 байтов.