Если вы когда-либо работали с Linux или компилировали программное обеспечение из исходного кода, то, скорее всего, использовали GNU Binutils, даже не подозревая об этом. Набор GNU Binutils необходим для сборки, компоновки и управления двоичными файлами. Независимо от того, являетесь ли вы новичком, пытающимся понять, как компилируется программное обеспечение, или опытным разработчиком, отлаживающим исполняемый файл, Binutils поможет вам.
В этой статье мы расскажем вам, что такое GNU Binutils, о его преимуществах, основных инструментах и о том, как использовать GNU Binutils для отладки исполняемых файлов в Linux.
GNU Binutils (сокращение от «бинарные утилиты») — это набор инструментов командной строки для работы с объектными файлами, исполняемыми файлами и двоичными данными. Эти инструменты помогают выполнять такие задачи, как сборка кода, связывание библиотек, дизассемблирование исполняемых файлов и анализ скомпилированных программ.
Пакет Binutils включает в себя некоторые из наиболее важных инструментов в наборе разработчика, такие как:
Если вы увлекаетесь программированием (особенно на таких языках, как C или C++) и системным администрированием, то рано или поздно столкнётесь с GNU Binutils.
Короче говоря, понимание Binutils поможет вам стать более эффективным программистом и системным администратором.
Давайте рассмотрим наиболее важные инструменты GNU Binutils и то, как они могут вам помочь.
as
: Ассемблер GNUИнструмент as
преобразует ассемблерный код в машинный.
Пример:
as hello.s -o hello.o
Это компилирует исходный файл сборки (hello.s
) в объектный файл (hello.o
).
ld
: Компоновщик GNUИнструмент ld
связывает объектные файлы для создания исполняемого файла.
Пример:
ld -o hello hello.o
Это связывает hello.o
с исполняемым файлом с именем hello
.
objdump
: Проверьте исполняемые файлыКоманда objdump
отображает подробную информацию об объектных файлах и исполняемых файлах.
Пример:
ld -o hello hello.o
При этом команда ls разбирается, показывая инструкции по ее сборке.
nm
: Список символов в двоичном файлеКоманда nm
выводит список символов (функций и переменных) внутри объектного файла или исполняемого файла.
Пример:
objdump -d /bin/ls | less
Здесь показаны первые несколько символов в объектном файле hello.o
.
readelf
: Прочитайте заголовки ELFИнструмент readelf
предоставляет подробную информацию о файлах ELF (исполняемый и компонуемый формат).
Пример:
readelf -h /bin/ls
При этом отображается заголовок ELF команды ls
.
strings
: Извлечение читаемого текста из двоичных файловИнструмент strings
находит и извлекает читаемый текст из двоичных файлов.
Пример:
strings /bin/ls | grep "Usage"
При этом выполняется поиск сообщений об использовании внутри двоичного файла ls
.
strip
: Уменьшите размер двоичного файла, удалив символыКоманда strip
удаляет ненужные символы из исполняемых файлов, уменьшая их размер.
Пример:
strip myprogram
Это сводит к минимуму размер myprogram
за счет удаления символов отладки.
size
: Отображение размеров секцийИнструментsize
показывает использование памяти различными разделами объектного файла.
Пример:
size /bin/ls
Здесь отображаются размеры сегментов текста, данных и bss (неинициализированных данных) для ls
.
objcopy
: Копирование и изменение объектных файловИнструментobjcopy
позволяет вам манипулировать объектными файлами.
Пример:
objcopy --only-keep-debug myprogram myprogram.debug
При этом символы отладки извлекаются в отдельный файл.
addr2line
: Сопоставление адресов с исходным кодомКоманда addr2line
помогает сопоставить адрес памяти с исходной строкой исходного кода.
Пример:
addr2line -e /bin/ls 0x4005a0
Это показывает, какая строка в исходном коде ls
соответствует адресу памяти 0x4005a0
.
ar
: АрхиваторИнструмент ar
используется для создания архивов объектных файлов и управления ими. Эти архивы, часто называемые статическими библиотеками, можно подключать к программам для повторного использования кода.
Пример:
Вы можете создать библиотеку с помощью ar rcs libmylib.a file1.o file2.o
, а затем связать ее со своей программой.
Как мы уже упоминали, GNU Binutils — это набор двоичных инструментов, необходимых для работы с исполняемыми, объектными файлами и библиотеками в Linux. Эти инструменты полезны для отладки, дизассемблирования и анализа двоичных файлов.
В следующем примере мы сосредоточимся на использовании objdump
, nm
и readelf
для целей отладки.
objdump
: Разбирает двоичные файлы, отображает разделы объектных файлов и извлекает информацию из исполняемых файлов.nm
: Перечисляет символы (функции, переменные и т.д.) Из объектных файлов.readelf
: Отображает подробную информацию о файлах ELF (исполняемый и компонуемый формат).
Давайте начнём с написания простой программы на C, её компиляции, а затем анализа с помощью Binutils.
Создайте файл с именем example.c
со следующим содержимым:
#include <stdio.h> int add(int a, int b) { return a + b; } int main() { int result = add(3, 4); printf("Result: %d\n", result); return 0; }
Скомпилируйте программу с включёнными символами отладки. Это упростит анализ:
gcc -g -o example example.c
Флаг -g
указывает компилятору включить отладочную информацию в исполняемый файл.
1. Использование objdump
для дизассемблирования двоичного файла
Чтобы просмотреть дизассемблированный код исполняемого файла, запустите:
objdump -d example
Это отобразит код сборки исполняемого файла. Найдите в выводе функции main
и add
. Например:
example: file format elf64-x86-64 Disassembly of section .init: 0000000000001000 <_init>: 1000: 48 83 ec 08 sub $0x8,%rsp 1004: 48 8b 05 c5 2f 00 00 mov 0x2fc5(%rip),%rax # 3fd0 <__gmon_start__@Base> 100b: 48 85 c0 test %rax,%rax 100e: 74 02 je 1012 <_init+0x12> 1010: ff d0 call *%rax 1012: 48 83 c4 08 add $0x8,%rsp 1016: c3 ret Disassembly of section .plt: 0000000000001020 <printf@plt-0x10>: 1020: ff 35 ca 2f 00 00 push 0x2fca(%rip) # 3ff0 <_GLOBAL_OFFSET_TABLE_+0x8> 1026: ff 25 cc 2f 00 00 jmp *0x2fcc(%rip) # 3ff8 <_GLOBAL_OFFSET_TABLE_+0x10> 102c: 0f 1f 40 00 nopl 0x0(%rax) 0000000000001030 <printf@plt>: 1030: ff 25 ca 2f 00 00 jmp *0x2fca(%rip) # 4000 <printf@GLIBC_2.2.5> 1036: 68 00 00 00 00 push $0x0 103b: e9 e0 ff ff ff jmp 1020 <_init+0x20> Disassembly of section .plt.got: 0000000000001040 <__cxa_finalize@plt>: 1040: ff 25 9a 2f 00 00 jmp *0x2f9a(%rip) # 3fe0 <__cxa_finalize@GLIBC_2.2.5> 1046: 66 90 xchg %ax,%ax Disassembly of section .text: 0000000000001050 <_start>: 1050: 31 ed xor %ebp,%ebp 1052: 49 89 d1 mov %rdx,%r9 1055: 5e pop %rsi 1056: 48 89 e2 mov %rsp,%rdx 1059: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 105d: 50 push %rax 105e: 54 push %rsp 105f: 45 31 c0 xor %r8d,%r8d 1062: 31 c9 xor %ecx,%ecx 1064: 48 8d 3d e2 00 00 00 lea 0xe2(%rip),%rdi # 114d <main> 106b: ff 15 4f 2f 00 00 call *0x2f4f(%rip) # 3fc0 <__libc_start_main@GLIBC_2.34> 1071: f4 hlt 1072: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1) 1079: 00 00 00 107c: 0f 1f 40 00 nopl 0x0(%rax) [...]
В этом выводе показаны инструкции по сборке для функций add
и main
.
2. Использование nm
для перечисления символов
Чтобы составить список символов (функций и переменных) в исполняемом файле, выполните:
nm example
Вы увидите результат, подобный этому:
0000000000401136 T add 000000000040114a T main U printf@@GLIBC_2.2.5
Здесь T
указывает на то, что символ находится в текстовом (кодовом) разделе, а U
указывает на неопределённый символ (например, printf
, который связан с библиотекой C).
3. Использование readelf
для проверки файла ELF
Чтобы просмотреть подробную информацию о файле ELF, используйте readelf
. Например, чтобы увидеть заголовки разделов, выполните:
readelf -S example
Это отобразит информацию о разделах исполняемого файла, таких как .text
(код), .data
(инициализированные данные) и .debug
(информация для отладки).
Чтобы просмотреть таблицу символов, выполните:
readelf -s example
При этом будут показаны все символы, включая их адреса и типы.
objdump
и gdb
Если вы хотите отладить программу, вы можете использовать gdb
(GNU Debugger) в сочетании с objdump
.
Примечание: если gdb недоступен, вы можете установить его с помощью менеджера пакетов по умолчанию, например:
sudo apt install gdb
1. Начните gdb
с исполняемого файла:
gdb ./example
Теперь вы будете находиться в оболочке gdb:
GNU gdb (Debian 13.1-3) 13.1 Copyright (C) 2023 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./example... (gdb)
2. Установите точку останова в функции main
:
break main
3. Запустите программу:
run
4. Используйте disassemble
in gdb
для просмотра дизассемблированного кода:
disassemble main
Здесь будет показан код сборки для функции main
, аналогичный тому, что был показан ранее для objdump
Комбинируя эти инструменты, вы можете эффективно анализировать и отлаживать исполняемые файлы в Linux.
GNU Binutils — это обязательный к изучению набор инструментов для всех, кто работает с компилированным программным обеспечением. Даже если вы новичок в Linux, изучение основ этих инструментов поможет вам понять, что происходит за кулисами при запуске программного обеспечения.