Стандартные утилиты для UNIX-программиста
Я ничего не буду рассказывать о таких утилитах как emacs и vi, т. к. редактор это дело вкуса. Обойду стороной такие утилиты, как tcpdump и netstat, т. к. они в первую очередь предназначены для системных администраторов. Так о каких же утилитах я тогда хочу рассказать? О тех, которые исключительно предназначены для разработчиков приложений и стандартно присутствуют в большинстве nix-систем (в первую очередь в Linux и BSD). Как ты можешь заметить по объему статьи, таких утилит немало, к тому же в обзор попали далеко не все стандартные утилиты.
Большинство стандартных утилит для программиста входят в пакет GNU Binutils (информацию о том, что это за пакет смотри во врезке). Без пакета Binutils невозможна нормальная работа в системе, поэтому знать об утилитах входящих в этот пакет полезно не только программисту, но и простому юзеру. Впрочем, речь будет идти не только о программах из Binutils.
Что такое GNU Binutils?
GNU Binutils это пакет утилит, который включается в себя многие важные системные программы, такие как as, ld, ar, gprof и пр. Без пакета Binutils невозможна работа компилятора gcc, а, значит, невозможно нормальное функционирование всей системы. Сайт разработчиков пакета Binutils расположен по адресу: http://sources.redhat.com/binutils/.
GPROF
Первая такая утилита, входящая в пакет Binutils — это профайлер или профилировщик. С помощью профайлера можно установить, какие функции в программе вызываются чаще, чем нужно, а также какие из них затрачивают больше всех вычислительных ресурсов, т. е. можно выявить в программе «узкие места». Воспользоваться gprof просто. Сначала компилится и компонуется программа с опциями профилирования (для языка Си (gcc) в опциях должен быть указан флаг –pg). Затем программа запускается, в результате чего генерятся профильные данные и скидываются в файл gmon.out. Замечу, что профильный файл не появится, если программа завершается аварийно, т. е. твоя прога должна быть уже отлажена. Последним этапом запускается сам gprof, которому нужно передать имя исполняемого файла, gprof анализирует файл gmon.out и выдает информацию о том, сколько времени заняло выполнение каждой функции. В общем случае информация будет состоять из двух таблиц — «Простой профиль» ("Flat profile") и «Граф вызовов» ("Call graph") с замечаниями, кратко объясняющими содержимое этих таблиц. Из простого профиля устанавливается, какие функции программы затрачивают больше всего времени, т. к. эта таблица показывает, сколько времени выполнялась каждая функция и сколько раз эта функция вызывалась. Граф вызовов может подсказать те места, в которых можно попытаться исключить вызовы функций, требующие много времени на выполнение. В таблице графа вызовов показано для каждой функции, какие функции ее вызывали, какие функции вызывала она сама и сколько раз. Также здесь есть информация, сколько времени было затрачено на выполнение подпрограмм в каждой функции.
Утилита gprof имеет множество полезных опций, например, при задании опции –A будет отображен исходный текст программы с процентными показателями времени выполнения.
Профилировку имеет смысл делать только в больших программах с множеством вызовов функций. Пример использования:
$ gcc -pg -o you_prog you_prog.c
$ ./you_prog
$ gprof ./you_prog
TIME
Time показывает время, затраченное на выполнение программы. Пример:
$ time ./you_prog
real 0m0.008s user 0m0.001s sys 0m0.010s
Где real — астрономическое время, в течение которого выполнялась программа; user — время центрального процессора, потраченное на исполнение программы; sys — время, затраченное на программу операционной системой. Понятно, что буква m указывает минуты, а s — секунды в десятичных дробях. Если нужно отследить время выполнения программы, которая использует множество флагов и/или каналы, то утилиту time следует использовать так:
$ time /bin/sh -c "you_prog –flags|my_prog"
CTAGS
Если программа состоит из множества модулей, которые в свою очередь разбросаны по множеству исходных файлов, то становится сложно отыскать определение нужной функции. Именно для быстрого поиска функций и предназначена ctags. Достаточно ей скормить исходные файлы твоей проги, как она сформирует особый информационный файл (tags) из трех колонок, где в первой колонке будут названия всех функций, во второй имена исходных файлов в которых расположены эти функции, а в третьей готовый шаблон для поиска функций по файловой системе с помощью таких утилит как find. Пример:
main /usr/src/you_prog.c /^main()$/
func1 /usr/src/you_prog.c /^func1(arg1,arg2)$/
func2 /usr/src/you_prog.c /^func2(arg1,arg2)$/
Пример использования утилиты ctags:
$ ctags *.c
STRACE
Данная утилита отслеживает все запрашиваемые вызовы и получаемые системные сигналы твоей программы. Используется просто:
$ strace ./you_prog
Каждая строка выводимой информации будет соответствовать одному системному вызову. Где сначала будет указано имя системного вызова со списком аргументов (с сокращениями), а после знака равно возвращаемое значение.
В выражении вида:
execve(“./you_prog”, [“./you_prog”], [/*27 vars */]) = 0
[/* 27 vars */] — означает, что здесь идет список переменных среды (27 штук), которые опущены strace для краткости.
KTRACE/KDUMP/TRUSS
В *BSD существует команда ktrace, которая аналогична команде strace. Пример использования:
$ ktrace ./you_prog
В текущей директории образуется файл ktrace.out, куда скидываются результаты работы ktrace. Чтобы просмотреть эту информацию нужно просто запустить утилиту kdump:
$ kdump
Во многих UNIX-like системах присутствует еще одна похожая утилита — truss. По функциям она проще, чем ktrace, но зато сразу отображает все результаты в консоли. Пример:
$ truss ./you_prog
MTRACE
Если твоя прога использует динамическую память, то очень желательно протестировать ее с помощью утилиты mtrace. Mtrace отслеживает соответствие числа операций выделения и освобождения памяти, т. е. вылавливает утечки памяти. Утечки памяти ведут к постепенному сокращению ресурсов системы, до полного их исчерпания. Чтобы выловить все возможные утечки памяти в твоей программе придется проделать несколько неприятных шагов. Во-первых, нужно включить в программу файл и разместить в самом начале программы вызов функции mtrace(). Затем нужно указать имя файла, в котором будет сохраняться информация о проверке, делается это через экспортирования в переменную окружения, например:
$ export MALLOC_TRACE=mem.log
После чего нужно запустить программу и все операции выделения и освобождения памяти будут регистрироваться в mem.log. Последним этапом вызывается утилита mtrace в следующем виде:
$ mtrace you_prog $MALLOC_TRACE.
Теперь нужно внимательно читать полученную информацию с указанием строк, где память не была освобождена.
SIZE
Чтобы узнать размеры секций программы — секции команд (text), данных (data) и секции неинициализированных данных (bss) нужно использовать утилиту size. Она также показывает общую сумму всех секций в десятичном и шестнадцатеричном формате.
$ size ./you_prog
NM
Команда nm выдает на стандартный вывод таблицу внешних символов для каждого файла, указанного в командной строке. Таблица символов используется для отладки приложения. Для каждого символа будет выведено его имя и указано, является ли он символом данных (переменной) или программным символом (меткой или именем функции) и пр. Подробности смотри в man. Пример:
$ nm ./you_prog
STRIP
Когда программа отлажена, таблицу символов из нее можно удалить, для чего используется команда strip. Это уменьшает размер выполняемого файла, но в наше время это не столь существенно и отладочную информацию лучше оставить. Пример:
$ strip ./you_prog
GDB
GNU Debugger – самый известный консольный nix-отладчик. Про gdb написано много, поэтому я напомню лишь основные функции. Запускаем программу в режиме отладки:
$ gdb you_prog
Теперь можно ставить бряк на нужную функцию, например на функцию main:
(gdb) break main
В gdb каждая команда имеет сокращенное обозначение, поэтому вместо break можно просто ввести b. Теперь можно запустить программу на выполнение командой r (run). Можно выполнять пошаговую трассировку: s (step) – с заходом в функцию, n (next) – без захода в функцию. Команда i reg показывает содержимое всех регистров; если нужно посмотреть конкретные регистры, то их можно указать сразу за этой командой, например:
(gdb) i reg ebp eip
Команда x показывает содержимое памяти. В help (h) можно узнать подробности по любой команде.
MAKE/GMAKE
Когда проект состоит из множества файлов, то любое изменение в одном из них неизбежно влечет за собой перекомпиляцию всех остальных, облегчить эту задачу способна утилита make (в некоторых системах она называется gmake). Этой утилите нужно передать простой текстовый файл под названием Makefile, который содержит информацию о правилах сборки и зависимостях. Правила записываются в следующем виде:
<цель>: <зависимости> <команда> <команда> ...
Первая цель в Makefile выполняется по умолчанию при запуске make без аргументов. Ее принято называть all, что эквивалентно команде "make all". Пример Makefile:
all: you_prog you_prog: you_prog.o foo.o boo.o gcc you_prog.o foo.o boo.o -o you_prog you_prog.o: you_prog.c you_prog.h foo.o: foo.c foo.h boo.o: boo.c boo.h clean: rm -f *.o you_prog
Цель «clean» предназначена для удаления всех сгенерированных объектных файлов и программ, чтобы make могла создать их заново. Чтобы собрать проект достаточно в командной строке набрать:
$ make
В man об утилите make можно узнать много других интересных подробностей.
AUTOMAKE/AUTOCONF
Но есть еще один более простой способ создания Make-файлов, с помощью стандартных утилит automake и autoconf. Сначала нужно подготовить файл Makefile.am, например:
bin_PROGRAMS = you_prog you_prog_SOURCES = you_prog.c foo.c boo.c AUTOMAKE_OPTIONS = foreign
Последняя опция указывает на то, что в проект не будут включаться файлы стандартной документации: NEWS, README, AUTHORS и ChangeLog. Согласно стандарту их присутствие в GNU-пакете обязательно. Теперь нужно создать файл configure.in. Это можно сделать с помощью утилиты autoscan. Autoscan выполняет анализ дерева исходных текстов, корень которого указан в командной строке или совпадает с текущим каталогом, и создает файл configure.scan. Нужно просмотреть configure.scan, внести необходимые коррективы и затем переименовать в configure.in. И последним этапом следует запустить утилиты в следующем порядке:
$ aclocal
$ autoconf
$ automake -a -c
В результате в текущей директории появятся скрипты configure, Makefile.in и файлы документации. Чтобы собрать проект достаточно ввести следующие команды:
$ ./configure
$ make
Утилиты autoconf и automake входят в серию Autotools (см. врезку).
Что такое GNU Autotools?
Под названием Autotools объединяются утилиты Automake, Autoconf, Libtool и Shtool. Подробности можно узнать на сайтах разработчиков:
http://sources.redhat.com/automake/
http://www.gnu.org/software/autoconf/
http://freshmeat.net/projects/libtool/
http://www.gnu.org/software/shtool/
Рекомендую также прочитать книгу: "GNU Autoconf, Automake and Libtool" по адресу: http://sources.redhat.com/autobook/autobook/autobook_toc.html.
HEXDUMP и OD
Утилита hexdump может вывести программу в десятичном виде (опция –d), шестнадцатеричном (опция –x), восьмеричном (опция –b) и в ascii-символах (опция –c). Пример использования:
$ hexdump –c ./you_prog
Утилита od аналогична утилите hexdump:
$ od –c ./you_prog
Hexdump и od имеют множество других опций, о которых ты знаешь где узнать ;).
OBJDUMP
Эту утилиту по количеству функций можно сравнить со «швейцарским ножом». Например, она может легко дизассемблировать прогу (опция –D), показать все заголовки программы, в т. ч. файловые, секций и пр. (опция –x), может показать содержимое всех секций (опция –s), динамически перемещаемые данные (опция –R) и многое другое. Пример:
$ objdump –D ./you_prog
LDD
Данная утилита показывает все динамические библиотеки, от которых зависит программа. Пример использования:
$ ldd ./you_prog
libc.so.6 => /lib/i686/libc.so.6 (0x40026000) /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
В скобочках указывается адрес библиотеки в памяти.
IPCS и IPCRM
Если твоя программа использует взаимодействие процессов, то тебе могут пригодиться утилиты ipcs и ipcrm. Команда ipcs указанная с флагом -m показывает сведения о совместно используемых сегментах:
$ ipcs -m
Если указать флаг -s, то ipcs покажет информацию о существующих группах семафоров.
Утилита ipcrm позволяет удалить определенный сегмент в памяти или группу семафоров, например:
$ ipcrm shm 2345097
удаляет сегмент под id равным 2345097.
Чтобы можно было работать с утилитами ipcs и ipcrm в ядре должны быть включены опции:
option SYSVMSG - поддержка сообщений в соответствии с System V;
option SYSVSEM - поддержка семафоров в соответствии с System V;
option SYSVSHM - возможность работы с разделяемой памятью в соответствии с System V.
STRINGS
Утилита strings выводит последовательности ascii-символов (слова) длиннее четырех (по умолчанию), которые хранятся в открытом виде в уже скомпиленной программе. Пример использования:
$ strings ./you_prog
Для создания собственных программ полезность данной утилиты сомнительна, но для исследования чужих очень даже - к примеру, можно найти имена разработчиков интересные комментарии, пароли и даже номера кредитных карт :).
READELF
Выводит файловые заголовки и заголовки секций файлов ELF-формата. Об опциях можно посмотреть в хелпе или в man.
$ readelf ./you_prog
AR и RANLIB
В пакете Binutils существует архиватор ar, который используется для создания статических библиотек. Например:
$ ar cr libmy.a file1.o file2.o
Флаг cr указывает на то, что должен быть создан архив, есть и другие флаги, например для модификации или извлечения из архива (см. man). Для подключения полученной статической библиотеки к программам с помощью gcc или g++ нужно использовать флаг -L, который указывает, в каком каталоге следует искать библиотеку. Флаг -L. (с точкой) указывает на то, что библиотека находится в текущем каталоге. Затем все необходимые библиотеки перечисляются с помощью ключа -l, за которым указывается название библиотеки без префикса lib и окончания .a. Т. е. в нашем случае:
$ gcc -o you_prog.c -L. -lmy -o you_prog
Это работает в большинстве случаев, однако на некоторых системах получить статическую библиотеку не получится таким способом, т. к. после того как архиватор ar создаст архив нужно в него добавить индекс символов, т. е. список вложенных в библиотеку функций и переменных, чтобы линковка проходила нормально. Делается это с помощью стандартной утилиты ranlib из пакета Binutils:
$ ranlib libmy.a
После этого библиотеку можно подключать к программе с помощью gcc, как в предыдущем примере. Для получения статической библиотеки рекомендуется обрабатывать архив утилитой ranlib всегда.
На этом стоит, думаю, остановиться, хотя я рассказал еще далеко не обо всех стандартных утилитах для программиста. Большинство из «забытых» мной утилит либо используются очень редко, например такие как: addr2line, c++filt, objcopy, либо требует для рассказа отдельной статьи, как, например, ассемблер as. В man ты всегда сможешь найти нужную информацию.
Автор: Иван Скляров