strace — отслеживаем выполнение процесса

Thank you for reading this post, don't forget to subscribe!

strace — ути­ли­та для Linux, кото­рая поз­во­ля­ет отсле­дить выпол­не­ние систем­ных вызо­вов (system call) и сиг­на­лов к ядру системы

КОМАНДА STRACE LINUX

Как я уже ска­зал, коман­да strace пока­зы­ва­ет все систем­ные вызо­вы про­грам­мы, кото­рые та отправ­ля­ет к систе­ме во вре­мя выпол­не­ния, а так­же их пара­мет­ры и резуль­тат выпол­не­ния. Но при необ­хо­ди­мо­сти мож­но под­клю­чить­ся и к уже запу­щен­но­му про­цес­су. Перед тем, как перей­ти к прак­ти­ке, раз­бе­рём опции ути­ли­ты и её синтаксис:

$ strace опции коман­да аргументы

В самом про­стом вари­ан­те strace запус­ка­ет пере­дан­ную коман­ду с её аргу­мен­та­ми и выво­дит в стан­дарт­ный поток оши­бок все систем­ные вызо­вы коман­ды. Давай­те раз­бе­рём опции ути­ли­ты, с помо­щью кото­рых мож­но управ­лять её поведением:

  • -i - выво­дить ука­за­тель на инструк­цию во вре­мя выпол­не­ния систем­но­го вызова;
  • -k - выво­дить стек вызо­вов для отсле­жи­ва­е­мо­го про­цес­са после каж­до­го систем­но­го вызова;
  • -o - выво­дить всю инфор­ма­цию о систем­ных вызо­вах не в стан­дарт­ный поток оши­бок, а в файл;
  • -q - не выво­дить сооб­ще­ния о под­клю­че­нии о отклю­че­нии от процесса;
  • -qq - не выво­дить сооб­ще­ния о завер­ше­нии рабо­ты процесса;
  • -r - выво­дить вре­мен­ную мет­ку для каж­до­го систем­но­го вызова;
  • -s - ука­зать мак­си­маль­ный раз­мер выво­ди­мой стро­ки, по умол­ча­нию 32;
  • -t - выво­дить вре­мя суток для каж­до­го вызова;
  • -tt - доба­вить микросекунды;
  • -ttt - доба­вить мик­ро­се­кун­ды и коли­че­ство секунд после нача­ла эпо­хи Unix;
  • -T - выво­дить дли­тель­ность выпол­не­ния систем­но­го вызова;
  • -x - выво­дить все не ASCI-стро­ки в шест­на­дца­те­рич­ном виде;
  • -xx - выво­дить все стро­ки в шест­на­дца­те­рич­ном виде;
  • -y - выво­дить пути для фай­ло­вых дескрипторов;
  • -yy - выво­дить инфор­ма­цию о про­то­ко­ле для фай­ло­вых дескрипторов;
  • -c - под­счи­ты­вать коли­че­ство оши­бок, вызо­вов и вре­мя выпол­не­ния для каж­до­го систем­но­го вызова;
  • -O - доба­вить опре­де­лён­ное коли­че­ство мик­ро­се­кунд к счет­чи­ку вре­ме­ни для каж­до­го вызова;
  • -S - сор­ти­ро­вать инфор­ма­цию выво­ди­мую при опции -c. Доступ­ны поля time, calls, name и nothing. По умол­ча­нию исполь­зу­ет­ся time;
  • -w - сум­ми­ро­вать вре­мя меж­ду нача­лом и завер­ше­ни­ем систем­но­го вызова;
  • -e - поз­во­ля­ет отфиль­тро­вать толь­ко нуж­ные систем­ные вызо­вы или события;
  • -P - отсле­жи­вать толь­ко систем­ные вызо­вы, кото­рые каса­ют­ся ука­зан­но­го пути;
  • -v - поз­во­ля­ет выво­дить допол­ни­тель­ную инфор­ма­цию, такую как вер­сии окру­же­ния, ста­ти­сти­ку и так далее;
  • -b - если ука­зан­ный систем­ный вызов обна­ру­жен, трас­си­ров­ка прекращается;
  • -f - отсле­жи­вать так­же дочер­ние про­цес­сы, если они будут созданы;
  • -ff - если зада­на опция -o, то для каж­до­го дочер­не­го про­цес­са будет создан отдель­ный файл с име­нем имя_файла.pid.
  • -I - поз­во­ля­ет бло­ки­ро­вать реак­цию на нажа­тия Ctrl+C и Ctrl+Z;
  • -E - добав­ля­ет пере­мен­ную окру­же­ния для запус­ка­е­мой программы;
  • -p - ука­зы­ва­ет pid про­цес­са, к кото­ро­му сле­ду­ет подключиться;
  • -u - запу­стить про­грам­му, от име­ни ука­зан­но­го пользователя.

Вы зна­е­те основ­ные опции strace, но что­бы пол­но­цен­но ею поль­зо­вать­ся, нуж­но ещё разо­брать­ся с систем­ны­ми вызо­ва­ми, кото­рые исполь­зу­ют­ся чаще все­го. Мы не будем рас­смат­ри­вать все, а толь­ко основ­ные. Мно­гие из них вы уже и так зна­е­те, пото­му что они назы­ва­ют­ся так же, как и коман­ды в терминале:

  • fork - созда­ние ново­го дочер­не­го процесса;
  • read - попыт­ка читать из фай­ло­во­го дескриптора;
  • write - попыт­ка запи­си в фай­ло­вый дескриптор;
  • open - открыть файл для чте­ния или записи;
  • close - закрыть файл после чте­ния или записи;
  • chdir - изме­нить теку­щую директорию;
  • execve - выпол­нить испол­ня­е­мый файл;
  • stat - полу­чить инфор­ма­цию о файле;
  • mknod - создать спе­ци­аль­ный файл, напри­мер, файл устрой­ства или сокет;

А теперь раз­бе­рём при­ме­ры strace Linux.

ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ STRACE

1. ЗАПУСК ПРОГРАММЫ

Самый про­стой спо­соб запус­ка ути­ли­ты - про­сто пере­дать ей в пара­мет­рах имя коман­ды или испол­ня­е­мый файл про­грам­мы, кото­рую мы хотим иссле­до­вать. Напри­мер, uname:

strace uname

Как и ожи­да­лось, мы видим спи­сок систем­ных вызо­вов, кото­рые дела­ет ути­ли­та, что­бы узнать вер­сию ядра. Син­так­сис выво­да такой:

имя_системного_вызова (параметр1, параметр2) = резуль­тат сообщение

Имя систем­но­го вызо­ва ука­зы­ва­ет, какой имен­но вызов исполь­зо­ва­ла про­грам­ма. Для боль­шин­ства вызо­вов харак­тер­но то, что им нуж­но пере­да­вать пара­мет­ры, име­на фай­лов, дан­ные и так далее. Эти пара­мет­ры пере­да­ют­ся в скоб­ках. Далее идет знак равен­ства и резуль­тат выпол­не­ния. Если всё про­шло успеш­но, то здесь будет  ноль или поло­жи­тель­ное чис­ло. Если же воз­вра­ща­ет­ся отри­ца­тель­ное зна­че­ние, дела­ем вывод, что про­изо­шла ошиб­ка. В таком слу­чае выво­дит­ся сообщение.

Напри­мер, в нашем выво­де есть сооб­ще­ния об ошибке:

openat(AT_FDCWD, "/usr/local/cuda-6.5/lib64/tls/x86_64/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

 

Здесь резуль­тат выпол­не­ния -1 и сооб­ще­ние гово­рит, что файл не най­ден. Но на рабо­ту про­грам­мы это не вли­я­ет. Это про­бле­ма под­клю­че­ния сто­рон­них биб­лио­тек и к этой ути­ли­те она не име­ет отно­ше­ния. А основ­ная рабо­та про­грам­мы выпол­ня­ет­ся строчкой:

uname({sysname="Linux", nodename="sergiy-pc1", ...}) = 0

И здесь ядро вер­ну­ло поло­жи­тель­ный результат.

2. ПОДКЛЮЧЕНИЕ К ЗАПУЩЕННОЙ ПРОГРАММЕ

Если про­грам­ма, кото­рую нам надо отсле­дить, уже запу­ще­на, то не обя­за­тель­но её пере­за­пус­кать с нашей ути­ли­той. Мож­но под­клю­чить­ся к ней по ее иден­ти­фи­ка­то­ру PID. Для тести­ро­ва­ния этой воз­мож­но­сти запу­стим ути­ли­ту dd, кото­рая будет запи­сы­вать нули из /dev/zero в файл file1:

dd if=/dev/zero of=~/file1

Теперь узна­ем PID наше­го про­цес­са, посколь­ку он такой один, мож­но вос­поль­зо­вать­ся pidof, вы же може­те исполь­зо­вать ps:

pidof dd

И оста­лось под­клю­чить­ся к наше­му процессу:

sudo strace -p 31796

В выво­де ути­ли­ты мы видим, что она чита­ет дан­ные из одно­го места с помо­щью вызо­ва read и запи­сы­ва­ет в дру­гое через write. Что­бы отсо­еди­нит­ся от про­цес­са, доста­точ­но нажать Ctrl+C. Даль­ше рас­смот­рим при­ме­ры strace Linux для филь­тра­ции данных.

3. ФИЛЬТРАЦИЯ СИСТЕМНЫХ ВЫЗОВОВ

Ути­ли­та выво­дит слиш­ком мно­го дан­ных, и, зача­стую, боль­шин­ство из них нас не инте­ре­су­ют. С помо­щью опции -e мож­но при­ме­нять раз­лич­ные филь­тры для более удоб­но­го поис­ка про­бле­мы. Мы можем отоб­ра­зить толь­ко вызо­вы stat, пере­дав в опцию -e такой пара­метр trace=stat:

sudo strace -e trace=stat nautilus

Кро­ме непо­сред­ствен­но систем­ных вызо­вов, в каче­стве пара­мет­ра для trace мож­но пере­да­вать и такие значения:

  • file - все систем­ные вызо­вы, кото­рые каса­ют­ся файлов;
  • process - управ­ле­ние процессами;
  • network - сете­вые систем­ные вызовы;
  • signal - систем­ные вызо­вы, что каса­ют­ся сигналов;
  • ipc - систем­ные вызо­вы IPC;
  • desc - управ­ле­ние дескрип­то­ра­ми файлов;
  • memory - рабо­та с памя­тью программы.

4. ВОЗВРАЩЕНИЕ ОШИБКИ

Мож­но попро­сить strace вер­нуть про­грам­ме ошиб­ку по нуж­но­му систем­но­му вызо­ву -e, но с пара­мет­ром fault. Син­так­сис кон­струк­ции такой:

fault=имя_вызова:error=тип_ошибки:when=количество

С име­нем вызо­ва всё понят­но, тип ошиб­ки, номер ошиб­ки, кото­рую надо вер­нуть. А с коли­че­ством всё немно­го слож­нее. Есть три варианта:

  • циф­ра - вер­нуть ошиб­ку толь­ко после ука­зан­но­го коли­че­ства запросов;
  • циф­ра+ - вер­нуть ошиб­ку после ука­зан­но­го коли­че­ства запро­сов и для всех последующих;
  • цифра+шаг - вер­нуть ошиб­ку для ука­зан­но­го коли­че­ства и для после­ду­ю­щих с ука­зан­ным шагом.

Напри­мер, сооб­щим uname, что систем­но­го вызо­ва uname не существует:

sudo strace -e fault=uname uname

В выво­де видим, что систе­ма вер­ну­ла про­грам­ме нашу ошиб­ку, а потом та с помо­щью вызо­ва write гово­рит поль­зо­ва­те­лю, что узнать вер­сию и назы­ва­ние систе­мы невозможно.

5. ФИЛЬТРАЦИЯ ПО ПУТИ

Если вас инте­ре­су­ет не опре­де­лён­ный вызов, а все опе­ра­ции с нуж­ным фай­лом, то мож­но выпол­нить филь­тра­цию по нему с помо­щью опции -P. Напри­мер, меня инте­ре­су­ет, дей­стви­тель­но ли ути­ли­та lscpu обра­ща­ет­ся к фай­лу /proc/cpuinfo, что­бы узнать инфор­ма­цию о процессоре:

strace -P /proc/cpuinfo lscpu

И в резуль­та­те мы видим, что дей­стви­тель­но обра­ща­ет­ся. Она откры­ва­ет его для чте­ния с помо­щью вызо­ва openat.

6. СТАТИСТИКА СИСТЕМНЫХ ВЫЗОВОВ

С помо­щью опции  вы може­те собрать ста­ти­сти­ку для систем­ных вызо­вов, кото­рые исполь­зу­ет про­грам­ма. Напри­мер, сде­ла­ем это для nautilus:

sudo strace -c nautilus

Во вре­мя рабо­ты ути­ли­та ниче­го выво­дить не будет. Резуль­тат будет рас­счи­тан и выве­ден, когда вы завер­ши­те отлад­ку. В выво­де будут такие колонки:

  • time - про­цент вре­ме­ни от обще­го вре­ме­ни выпол­не­ния систем­ных вызовов;
  • seconds - общее коли­че­ство секунд, затра­чен­ное на выпол­не­ние систем­ных вызо­вов это­го типа;
  • calls - коли­че­ство обра­ще­ний к вызову;
  • errors - коли­че­ство ошибок;
  • syscall - имя систем­но­го вызова.

Если вы хоти­те полу­чать эту инфор­ма­цию в режи­ме реаль­но­го вре­ме­ни, исполь­зуй­те опцию -C.

7. ОТСЛЕЖИВАНИЕ ВРЕМЕНИ ВЫПОЛНЕНИЯ

Что­бы отоб­ра­жать вре­мя выпол­не­ния каж­до­го систем­но­го вызо­ва, исполь­зуй­те опцию -t:

strace -t uname

Мож­но так­же отоб­ра­жать микросекунды:

strace -tt uname

Или отоб­ра­жать вре­мя в фор­ма­те UNIX:

strace -ttt uname

Что­бы доба­вить вре­мя выпол­не­ния вызо­ва, добавь­те -T:

strace -ttt -T uname

 

==================================================

 

Для при­ме­ра возь­мем про­стую про­грам­му на С, кото­рая выво­дит содер­жи­мое ука­зан­но­го файла:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Ее выпол­не­ние:

./openfile
Enter the name of file you wish to see
openfile.c
The contents of openfile.c file are :
include <stdio.h>
include <stdlib.h>
int main()
{

return 0;
}

Запус­ка­ем openfile с starce:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Напри­мер, тут виден вызов mmap(), кото­рый выпол­ня­ет запрос на выде­ле­ние памяти:


mmap(NULL, 4096, PROT_READ|PROT_WRITEMAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f115bfbb000

Про­ве­ря­ем

pmap 23342 | grep 7f115bfbb
00007f115bfbb000     12K rw---    [ anon ]

Или — фай­лы, к кото­рым выпол­ня­ет­ся систем­ный вызов open(), после пере­да­чи име­ни фай­ла функ­ции gets():

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Отображение определенных вызовов

Что бы выве­сти толь­ко спи­сок инте­ре­су­ю­щих систем­ных вызо­вов — исполь­зуй­те опцию -e.

Напри­мер — отоб­ра­зить толь­ко вызо­вы open():

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Что бы отоб­ра­зить несколь­ко опре­де­лен­ных вызо­вов — добавь­те -e trace=, и через запя­тую — спи­сок вызовов:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Сохранение вывода в файл

Так как тек­ста может быть мно­го — ино­гда вывод удоб­нее сохра­нить в файл.

Для это­го исполь­зуй­те опцию -o:

strace -o openfile_strace.log -e trace=open,mmap ./openfile
Enter the name of file you wish to see
sc
Error while opening the file.

Резуль­тат

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Трассировка вызовов запущенного процесса

Для под­клю­че­ния к уже рабо­та­ю­ще­му про­цес­су — исполь­зуй­те опцию -p.

Напри­мер — берем любой PID:

ps -au mid
PID TTY          TIME CMD
1334 ?        00:04:17 uwsgi
1335 ?        00:04:17 uwsgi
1749 ?        00:13:26 uwsgi

Под­клю­ча­ем­ся к нему, запи­сы­вая вывод в файл:

sudo strace -p 1334 -o uwsgi_strace.log -e mmap
[sudo] password for mid:
Process 1334 attached
^CProcess 1334 detached

Про­ве­ря­ем

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Отображение времени выполнения вызова

Доба­вим опцию -t:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Статистика системных вызовов

С помо­щью опции -c — мож­но полу­чить нагляд­ную ста­ти­сти­ку выпол­не­ния программы:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]

Напри­мер, вызов:

[codesyntax lang="php" blockstate="collapsed"]

[/codesyntax]