Решение проблем приложений с помощью трассировки

Исследование приложений при помощи truss

Утилита truss помогает разобраться в работе приложения. Если приложение работает неправильно, то программист первым делом смотрит журнальные файлы системы и приложения. Но если информация в журнале не помогает найти источник проблемы, UNIX® предоставляет мощный инструментарий, с помощью которого можно трассировать приложение во время его выполнения. При помощи этого инструментария и некоторых знаний можно решить проблемы с приложением.

Шон Уолберг, старший сетевой инженер, P.Eng

Шон Уолберг работал с Linux- и UNIX-системами с 1994 года в академических, корпоративных и "провайдерских" кругах. Он широко освещает вопросы системного администрирования в течение нескольких последних лет. С ним можно связаться по адресу sean@ertw.com.



27.08.2009

Системные администраторы часто интересуются механизмом работы приложений. С первого взгляда ясно только одно: приложение вроде бы запускается, а потом останавливается или зависает без всяких объяснений. Ни системные журнальные файлы, ни техническая документация не могут помочь. Трассировка приложения - следующее действие в таком случае.

Трассировка программы отображает вызовы, которые программа делает к внешним библиотекам и ядру. Эти вызовы дают приложению доступ в сеть, к файловой системе и к дисплею. Наблюдая за вызовами и их результатами, можно понять, что послужило причиной некорректной работы приложения.

Каждая UNIX®-система предоставляет свои собственные команды для трассировки. Эта статья рассматривает truss, поддерживаемую в Solaris и AIX®. На Linux® для трассировки можно использовать команду strace. Параметры командной строки могут незначительно отличаться друг от друга, поэтому трассировка приложений на других UNIX-системах может выполняться командами ptrace, ktrace, trace и tusc.

Классическая проблема с правами доступа к файлу

Один класс проблем, который постоянно преследует системных администраторов, - это права на доступ к файлу. Приложение наверняка откроет какие-то файлы, чтобы выполнить свою работу. Если операция открытия файла будет неудачной, программа должна дать знать об этом администратору. Однако разработчики часто забывают проверить результаты работы функций или выполняют проверку, но забывают об обработке ошибок. Например, вот информация, которая выводится приложением при неудачной попытке открыть файл:

$ ./openapp
This should never happen!

После запуска фиктивного приложения openapp было получено бесполезное и неверное сообщение об ошибке: This should never happen!. Пришло время использовать truss. В листинге 1 показано то же приложение, работающее под командой truss, которая показывает вызовы функции, сделанные программой к внешним библиотекам.

Листинг 1. Приложение openapp выполняется под truss
$ truss ./openapp
execve("openapp", 0xFFBFFDEC, 0xFFBFFDF4)  argc = 1
getcwd("/export/home/sean", 1015)               = 0
stat("/export/home/sean/openapp", 0xFFBFFBC8)   = 0
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
stat("/opt/csw/lib/libc.so.1", 0xFFBFF6F8)      Err#2 ENOENT
stat("/lib/libc.so.1", 0xFFBFF6F8)              = 0
resolvepath("/lib/libc.so.1", "/lib/libc.so.1", 1023) = 14
open("/lib/libc.so.1", O_RDONLY)                = 3
memcntl(0xFF280000, 139692, MC_ADVISE, MADV_WILLNEED, 0, 0) = 0
close(3)                                        = 0
getcontext(0xFFBFF8C0)
getrlimit(RLIMIT_STACK, 0xFFBFF8A0)             = 0
getpid()                                        = 7895 [7894]
setustack(0xFF3A2088)
open("/etc/configfile", O_RDONLY)               Err#13 EACCES [file_dac_read]
ioctl(1, TCGETA, 0xFFBFEF14)                    = 0
fstat64(1, 0xFFBFEE30)                          = 0
stat("/platform/SUNW,Sun-Blade-100/lib/libc_psr.so.1", 0xFFBFEAB0) = 0
open("/platform/SUNW,Sun-Blade-100/lib/libc_psr.so.1", O_RDONLY) = 3
close(3)                                        = 0
This should never happen!
write(1, " T h i s   s h o u l d  ".., 26)      = 26
_exit(3)

Каждая строка выведенной информации представляет собой вызов функции, сделанный приложением, и его результат, если возможно. (Подробно узнать о том, что делает каждая вызываемая функция, можно с помощью справочной системы man для конкретной функции, например, man open.) Для того чтобы найти вызов, возможно, являющийся причиной неполадок программы, часто бывает проще начать с конца (или так близко, как только возможно к тому месту, где возникает проблема). Например, вам известно, что приложение выводит This should never happen! и это сообщение появляется где-то ближе к концу выводимой утилитой truss информации. Если вы нашли такое сообщение, то проанализировав вывод команды truss, наверняка сможете выявить причину проблемы..

Прокрутив вверх сообщение об ошибке, можно обратить внимание на строку open("/etc/configfile"..., которая не только имеет отношение к данной проблеме, но и возвращает Err#13 EACCES. Если взглянуть на описание функции open() (команда man open), очевидно, что целью этой функции является открыть файл - в этом случае /etc/configfile - а возвращаемое значение EACCES означает то, что проблема связана с правами доступа к этому. После изучения свойств файла /etc/configfile станет ясно, что пользователь не имеет прав на чтение этого файла. Далее можно применить команду chmod, и приложение начнет работать правильно.

В выводе в листинге 1 показаны два других вызова - open() и stat(), которые возвращают ошибку. Многие вызовы, включая те два, что вызывают ошибки, в начале работы приложения выполняются операционной системой по отношению к этому приложению. Только опыт может помочь определить, когда ошибки безвредны, а когда нет. В этом случае имеются две ошибки и следом за ними идут три строки, в которых делается попытка найти libc.so.1. О проблемах с совместно используемыми библиотеками будет рассказано позднее.


Приложение не запускается

Иногда приложение не может корректно запуститься или вместо корректного завершения работы оно зависает. Такое поведение часто является симптомом конфликтов из-за ресурсов (например, два процесса конкурируют за блокировку файла), или приложение ждет ответа на свой запрос. Под последний вариант проблемы подпадают почти все ситуации: длительный поиск имени или файл, который должен был быть найден в определенном месте, но не оказался там. В любом случае наблюдение за приложением с помощью truss должно принести определенный результат.

В первом примере была показана очевидная связь между системным вызовом (источником проблемы) и файлом; следующий пример будет немного более сложным. Листинг 2 показывает некорректно работающее приложение getlock, работающее совместно с truss.

Листинг 2. getlock под наблюдением truss
$ truss ./getlock
execve("getlock", 0xFFBFFDFC, 0xFFBFFE04)  argc = 1
getcwd("/export/home/sean", 1015)               = 0
resolvepath("/export/home/sean/getlock", "/export/home/sean/getlock", 1023) = 25
resolvepath("/usr/lib/ld.so.1", "/lib/ld.so.1", 1023) = 12
stat("/export/home/sean/getlock", 0xFFBFFBD8)   = 0
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
stat("/opt/csw/lib/libc.so.1", 0xFFBFF708)      Err#2 ENOENT
stat("/lib/libc.so.1", 0xFFBFF708)              = 0
resolvepath("/lib/libc.so.1", "/lib/libc.so.1", 1023) = 14
open("/lib/libc.so.1", O_RDONLY)                = 3
close(3)                                        = 0
getcontext(0xFFBFF8D0)
getrlimit(RLIMIT_STACK, 0xFFBFF8B0)             = 0
getpid()                                        = 10715 [10714]
setustack(0xFF3A2088)
open("/tmp/lockfile", O_WRONLY|O_CREAT, 0755)   = 3
getpid()                                        = 10715 [10714]
fcntl(3, F_SETLKW, 0xFFBFFD60)  (sleeping...)

Последний вызов fcntl() отмечен как sleeping (засыпающий), потому что функция блокируется. Это означает, что функция ждет, пока что-нибудь случится, и ядро переводит этот процесс в спящее состояние, пока не произойдет ожидаемое событие. Чтобы узнать, какое событие ожидается, нужно изучить функцию fcntl().

Страница помощи man для fcntl() (man fcntl) описывает функцию как "file control" (управление файлами) в Solaris и "manipulate file descriptor" (управление файловыми дескрипторами) в Linux. В любом случае fcntl() требует файлового дескриптора, который представляет собой целое число, описывающее файл, открытый процессом, действие, которое будет выполнено при получении файлового дескриптора, и любые остальные аргументы, которые могут понадобиться функции. В листинге 2 файловый дескриптор равен 3, а команда F_SETLKW. (Число 0xFFBFFD60 является указателем на структуру данных, которая в данный момент интереса не представляет.) Двигаясь дальше, можно выяснить, что F_SETLKW открывает блокировку на файле и ждет, пока эта блокировка не будет получена.

Из первого примера с системным вызовом open() видно, что успешный вызов возвращает файловый дескриптор. В информации, выводимой truss в листинге 2, есть два случая, когда результатом системного вызова open() будет 3. Поскольку файловые дескрипторы повторно используются после того как их закрыли, функция open() расположена прямо над fcntl(), которая работает с /tmp/lockfile. Утилита типа lsof показывает список процессов, которые держат какие-либо файлы открытыми. Таким образом, можно найти процесс в каталоге /proc, который держит этот файл открытым. Однако обычно файл блокируется из благих намерений, например, чтобы ограничить число экземпляров приложений или настроить приложение, чтобы оно выполнялось в специфическом для пользователя каталоге.


Работа с уже выполняющими процессами

Иногда, когда происходит ошибка, приложение уже работает. Возможность запускать работающий процесс под truss может оказаться полезной. Например, из информации, выводимой утилитой top, видно что один процесс потребляет 95% мощности CPU в течение некоторого времени, как показано в листинге 3.

Листинг 3. top выводит самый ресурсоемкий процесс
   PID USERNAME LWP PRI NICE  SIZE   RES STATE    TIME    CPU COMMAND
 11063 sean       1   0    0 1872K  952K run     87.9H 94.68% udpsend

Применение опции -p с truss дает возможность владельцу процесса или root (администратору) присоединить трассировщик к выполняемому процессу и отследить системные вызовы. Для этого требуется знать PID. В листинге 3 PID соответствует 11063. Листинг 4 показывает системные вызовы для исследуемого приложения.

Листинг 4. Информация, выводимая truss, после присоединения к работающему процессу
$ truss -p 11063
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
sendto(3, " a b c", 3, 0, 0xFFBFFD58, 16)       = 3
. repeats ...

Страница man sendto говорит, что функция sendto() используется, чтобы посылать сообщения от сокета (обычно это выполняется через сеть). Вывод truss показывает файловый дескриптор (первая цифра 3) и данные, которые были переданы (abc). Анализ процесса при помощи инструмента snoop или tcpdump покажет большое количество трафика, предназначенного для какого-либо хоста, что, конечно, не является свидетельством корректно работающего приложения.

Отметим, что truss не был способен в данном случае отобразить создание файлового дескриптора 3, поскольку был присоединен к программе после того как дескриптор был создан. Это является ограничением, возникающим при присоединении к выполняемому процессу утилиты truss, поэтому для сбора более подробной информации следует использовать другие инструменты, например, анализатор пакетов (packet analyzer), прежде чем делать какие-либо выводы.

Этот пример мог бы показаться несколько натянутым (и с технической точки зрения так и есть, поскольку я написал вспомогательное приложение, чтобы проиллюстрировать работу truss), но в его основе лежит реальная ситуация. Я искал на UNIX-системе процесс, предельно загружающий CPU. Трассировка приложения показала, что активность работы с пакетами не изменилась. Трассировка при помощи сетевого анализатора показала, что пакеты направлялись на какой-то хост в интернете. После консультации с разработчиком приложения я определил, что проблема была в том, что он не протестировал должным образом двоичный файл конфигурации. Каким-то образом этот файл был испорчен. В результате приложение интерпретировало это файл неправильно и постоянно закидывало случайный IP-адрес UDP-датаграммами. После того как я заменил файл, приложение стало работать корректно.


Фильтрация выводимой информации

Поскольку grep можно использовать для обработки информации в выводе, будет проще сконфигурировать truss таким образом, чтобы наблюдать только за отдельными вызовами. Данная практика очень популярна в случае, когда пытаются определить, как работает приложение (например, какие конфигурационные файлы использует это приложение). В этом случае, системные вызовы open() и stat() указывают на все файлы, которые пытается открыть приложение.

Для открытия файла используется open(), для получения информации о файле используется stat(). Часто приложение ищет файл при помощи серии вызовов stat(), а затем открывает этот файл.

Чтобы отфильтровать системные вызовы, используется опция -t совместно с truss. Для strace под Linux используется -e. В любом случае необходимо передать в командную строку список системных вызовов, отделенных друг от друга запятой. Поставив восклицательный знак (!) перед списком, можно отфильтровать нужную информацию. Листинг 5 показывает, как тестовое приложение ищет файл конфигурации.

Листинг 5. Фильтр, наложенный на вывод truss, чтобы отображать только функции stat() и open()
$ truss -tstat,open ./app
stat("/export/home/sean/app", 0xFFBFFBD0)   = 0
open("/var/ld/ld.config", O_RDONLY)             Err#2 ENOENT
stat("/opt/csw/lib/libc.so.1", 0xFFBFF700)      Err#2 ENOENT
stat("/lib/libc.so.1", 0xFFBFF700)              = 0
open("/lib/libc.so.1", O_RDONLY)                = 3
stat("/export/home/sean/.config", 0xFFBFFCF0)   Err#2 ENOENT
stat("/etc/app/configfile", 0xFFBFFCF0)         Err#2 ENOENT
stat("/etc/configfile", 0xFFBFFCF0)             = 0
open("/etc/configfile", O_RDONLY)               = 3

Последние четыре строки самые важные. Функция stat() /export/home/sean/.config возвращает ENOENT, что означает, что файл не был найден. Затем код пытается найти файл в /etc/app/configfile и, наконец, находит его в /etc/configfile. Значение первой проверки в домашнем каталоге пользователя состоит в том, что можно менять этот адрес проверки в зависимости от пользователя.


Заключение

Независимо от того, что использует операционная система - truss, strace, trace или что-либо другое, возможность наблюдать за деталями выполнения программы является мощным инструментом для устранения сбоев программного обеспечения. Обобщенно эту методику можно описать так:

  1. Описание проблемы.
  2. Трассировка приложения.
  3. Начать с позиции, в которой проявляется проблема, и исследовать системные вызовы; чтобы определить проблему, можно применять справочную систему man для интерпретации системных вызовов.
  4. Исправить поведение и оттестировать приложение.

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

Ресурсы

Научиться

  • Solve application problems with tracing: оригинал статьи (EN).
  • AIX 5.2 performance tools update, Part 2, (developerWorks, ноябрь 2003): статья содержит список опций для трассировки, доступных для AIX.
  • Manipulating Files and Directories in UNIX (EN): учебный курс по управлению файлами в UNIX является достаточно простым (для непрограммистов) описанием системных вызовов, которые совершаются при работе с файлами. Ввиду того что многие неисправности в приложении связаны с файлами, информация по соответствующим системным вызовам, представленная в этом учебном курсе, будет очень полезной.
  • UNIX Network Programming: нет лучшей книги про UNIX, чем UNIX Network Programming от Ричарда Стивенса (Richard Stevens) (Prentice Hall, 1990). В этой книге подробно описаны сигналы и системные вызовы, сокеты и файлы; эта книга станет хорошим другом для того, кто захочет узнать гораздо больше подробностей, чем способна предложить система помощи man. Исходное издание этой книги было расширено, и теперь книга представлена в двух томах.(EN)
  • Popular content: популярные материалы об AIX и UNIX.(EN)
  • Раздел developerWorks AIX and UNIX содержит сотни информативных статей для читателей начальной, средней и высокой квалификации.
  • Раздел developerWorks Linux содержит сотни информативных статей для читателей начальной, средней и высокой квалификации.
  • Новичок в AIX и UNIX?: страница AIX и UNIX для новичков.
  • AIX 5L Wiki: совместная разработка документации AIX.(EN)
  • Разделы библиотеки информации по темам AIX и UNIX:(EN)
  • Safari bookstore: сайт магазина книг по ИТ.(EN)
  • Команда IBM developerWorks проводит по всему миру сотни бесплатных технических консультаций.(EN)
  • Podcasts: аудиозаписи презентаций экспертов IBM.(EN)

Получить продукты и технологии

  • IBM trial software: ознакомительные версии программного обеспечения для разработчиков, которые можно загрузить прямо со страницы сообщества developerWorks.(EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=423538
ArticleTitle=Решение проблем приложений с помощью трассировки
publish-date=08272009