Вы когда-нибудь ломали себе голову над поиском ошибки? Ошибки,
которую вы не можете найти в исходном коде, но которая появляется с
завидным постоянством после компиляции и запуска программы.
Знакомьтесь: strace
. strace
— это утилита, которая позволяет вам трассировать системные вызовы
и сигналы конкретной команды. Какой команды вы спросите? А какие у вас есть?
strace
это свободное программное
обеспечение, распространяемое под BSD-подобной лицензией.
Изначально, утилита была написана Полом Краненбургом (Paul Kranenburg)
для SunOS по мотивам другой утилиты SunOS trace
.
На Linux ее портировал Бранко Ланкестер (Branko Lankester),
который, кроме того, реализовал поддержку в ядре. В 1993, Рик Слэдки
(Rick Sladkey) объединил strace 2.5 для SunOS
со вторым релизом strace для Linux, добавив при этом много
возможностей от truss(1)
из SVR4. В результате, появилась strace
, которая работала на обеих платформах. Сегодня, strace
поддерживается Уичертом Аккерманом (Wichert Akkerman) и Роландом МакГрасом (Roland McGrath). В Red
Hat® Enterprise Linux® 4 включен strace
версии 4.5.8,
в Fedora™ Core 4 — версии 4.5.11, а на сайте SourceForge.net доступна версия 4.5.12.
В этой статье рассматривается версия 4.5.12.
Если strace
у вас не установлена,
а работаете вы в Red Hat Enterprise Linux 4, то для ее установки
выполните следующую команду от пользователя root:
up2date strace
Для установки strace
в Fedora Core 4, выполните от пользователя root команду:
yum install strace
А можно просто собрать strace
из исходников. Загрузите strace-4.5.12.tar.bz2
с ближайшего к вам зеркала сервера SourceForge.net.
Распакуйте содержимое файла strace-4.5.12.tar.bz2
:
tar -xjvf strace-4.5.12.tar.bz2
Перейдите в каталог strace-4.5.12
:
cd strace-4.5.12
И запустите configure
:
./configure
Вы увидите следующий вывод:
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether to enable maintainer-specific portions of Makefiles... no
checking build system type...
<snip>
checking whether sys_siglist is declared... yes
checking whether _sys_siglist is declared... yes
checking for perl... /usr/bin/perl
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
Теперь запустите make
:
make
Для установки strace
выполните команду make install
:
make install
При установке strace
из исходников, по-умолчанию он будет находиться в каталоге /usr/local/bin/
, но вы можете переопределить это значение с помощью опции --prefix=PATH
скрипта configure
(при установке из RPM-пакетов в системах Red Hat Enterprise Linux и Fedora Core strace
будет находиться в каталоге /usr/bin/
).
Работа strace
заключается в
перехвате и записи системных вызовов, выполненных процессом, а также
полученных им сигналов. Для каждого системного вызова в стандартный файл
ошибок, или в другой заданный файл, выводится его имя, аргументы и
код возврата. Как на счет пары примеров?
Чтобы не усложнять работу strace
,
я использовал простейшую из возможных программ.
#include "stdio.h"
main()
{
printf("Hello World \n");
}
Быстренько компилируем:
cc -o helloworld helloworld.c
Готово! Команда ./helloworld
теперь выдает:
Hello World
Выглядит воплощением простоты, и это действительно так и есть. Но давайте теперь запустим strace
.
strace
протрассирует и выведет в stderr все системные вызовы и сигналы:
strace ./helloworld
Вывод команды strace ./helloworld
strace
для лучшей читабельности, но в выводе strace
их нет.
Первое, что мы видим — это execve
.
execve()
выполняет программу, заданную именем файла. Далее, uname()
получает
имя и тип системы, которую я использовал для теста. После этого, наша
программа запрашивает некоторое количество памяти (brk
)
и карты размещения разделяемых библиотек, необходимых для работы,
например, динамический загрузчик и libc. Ко всей этой информации мы
скоро вернемся, а пока посмотрим, что происходит дальше.
Строка 23 вывода strace
наконец нам открывает назначение нашей программы helloworld
(если бы мы не видели исходного кода): вывести в файловый дескриптор 1
(stdout), сообщить количество байт в выводе и их значение. Не сложно догадаться, что применение strace
может
помочь вам отследить в программе конкретный вызов или сигнал. Чем же
это полезно? Если вы пишете программу чуть более сложную, чем helloworld.c
и она сбоит, выдавая минимум, или вообще не выдавая информации о том, что с ней случилось, strace
может вам помочь обнаружить что вызывает сбой в процессе работы.
Давайте рассмотрим немного более сложный, но и более реальный пример. Программа date
, помимо вывода даты и времени, выполняет массу различных системных вызовов. Запустите strace
для команды date
:
strace date
Мы видим вызовы execve()
,
uname()
и все ту же
информацию компоновщика и информацию о потоках (мы подразумеваем
выполнение на одной и той же системе, что видно по вызову uname()
),
после чего начинается непосредственно работа. Строка 44 показывает нам, что была вызвана local.archive
для определения нашей локали из списка доступных (строка 48). В строке 61, date вызывает clock_gettime()
для определения "реального времени", после чего выясняется наш часовой пояс (строка 62) с помощью вызова read
в строке 66. Вся эта деятельность венчается вызовом write
в строке 73 в том же формате, что и в нашем примере Hello World, за исключением того, что в этом случае date
генерирует вывод, основываясь на конфигурации из файла, а не задаваемой пользователем.
Теперь поговорим про вывод компоновщика, потоках и памяти. Мы видим несколько ссылок на разные компоненты ld. ld это компоновщик GNU, а его работа заключается в сборке объектных и ресурсных файлов, перемещении их данных и разрешении символических указателей. Вызов ld, как правило, является последним шагом в процессе компиляции программы. Кроме того, мы видим много вызовов, выделяющих и освобождающих память. Каждый раз, когда мы что-либо читаем или записываем, мы видим вызовы mmap (в нашем случае mmap2) и munmap. Таким образом, наша программа размещает в памяти файлы и устройства. Это особенно удобно для отладки программ, интенсивно использующих память, потому что вы можете по записи и чтению из памяти точно видеть, где конкретно в программе написаны вызовы, выполняющие операции размещения в памяти. Подробная информация по mmap2 доступна на сайте linuxinfor.com.
Если вы системный администратор, то, возможно, вы сидите и думаете:
"Какое отношение это все имеет ко мне?". Поскольку вы отвечаете за
надежное функционирование систем, то должны тестировать системное ПО до
и после обновления. Часто системным администраторам приходится быть
внештатными отладчиками: найти ошибку, отправить отчет и все такое. И в
этом деле strace
может сильно пригодиться.
Одной из наиболее мощных возможностей strace
является присоединение к существующему процессу для трассировки его системных вызовов. Делается это с помощью ключа -p
с указанием идентификатора процесса, к которому нужно подключиться.
Например, однажды вы обнаружили новую версию Bash. Вы обновили
свою систему, а на следующий день продолжили выполнять свои рутинные
задачи. Но вдруг вы обнаруживаете, что новая версия ведет себя
неожиданным образом.
Тогда, находясь в командном интерпретаторе, узнайте его идентификатор:
echo $$
Допустим, он равен 12307. В другом интерпретаторе запустите strace
с ключом -p
:
strace -p 12307
в ответ вы увидите:
Process 12307 attached - interrupt to quit
read(0,
Незаконченная функция read()
ожидает ввода. Допустим, пользователь выполняет команду date
. Первое, что мы видим, поскольку уже подсоединены к процессу командного интерпретатора, это вводимые символы:
Process 12307 attached - interrupt to quit
read(0, "d", 1) = 1
write(2, "d", 1) = 1
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, "a", 1) = 1
write(2, "a", 1) = 1
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, "t", 1) = 1
write(2, "t", 1) = 1
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, "e", 1) = 1
write(2, "e", 1) = 1
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0,
И снова read()
ожидает. Пользователь нажимает Enter.
Вывод после нажатия клавиши enter
Пропустив вызовы rt_
и ioctl
,
в строке 43 мы видим вывод нашего приглашения, записанного в сам
командный интерпретатор в строке 18. Этот вызов write служит
хорошим разделителем для всех остальных системных вызовов, включая
недокументированные вызовы rt_
. Вы можете попробовать это дома. Вы разберетесь. С помощью strace
вы можете трассировать все типы ошибок.
strace
— очень полезная
утилита диагностики и отладки. Программисты найдут ее полезной для
локализации ошибок, а системные администраторы — бесценной
при отслеживании проблем в общих программах.
Мощь strace
заключается в
возможности обнаружения ошибок, даже когда исходный код недоступен или
невозможна перекомпиляция. Это может помочь при исследовании работы
программного обеспечения с закрытым исходным кодом. И хотя некоторые
считают это "отрицательным" свойством, в действительности, оно может
служить лишь для углубления понимания программистом/администратором
работы таких программ. В конце концов, методы и стили программирования,
использованные при написании программного, обеспечения остаются в тайне,
не требуя дальнейшего судебного рассмотрения. strace
-ируйте что-нибудь.