Часто администратору Solaris или программисту приходится выяснять причины падения того или иного приложения - это может быть стандартная системная или новая, только что скомпилированная и нуждающаяся в отладке программа. Все усложняется, если речь идет о многопользовательской системе, где запущено множество приложений и очень нелегко отследить причины падения сразу нескольких программ.
Ранее основным способом анализа упавшей программы было исследование core или coredump, что в принципе одно и тоже. Однако этот способ имеет свои недостатки: проблемы с безопасностью - core может содержать данные, которые пользователю знать нельзя; часто огромный размер, из-за чего найти необходимую информацию довольно затруднительно, возможно переполнение диска и послать такой файл для анализа технической поддержке просто нереально.
Можно создавать специальные встраиваемые библиотеки c обработчиками сигналов SIGBUS и SIGSEGV, однако для многопользовательской системы это плохое решение.
Хорошим инструментом контроля приложений является
truss, так как эта утилита, кроме трассировки вызовов умеет еще и наблюдать за процессом и ждать определенного набора сигналов. В следующем примере запускается приложение, затем
truss для наблюдения за SIGSEGV и SIGBUS. Если поступает какой-либо из этих сигналов, truss оставляет приложение в состоянии STOPPED. Если команда
kill обнаруживает, что процесс все еще жив, тогда его карта памяти и трейсбэк выводятся с помощью proc-утилит
pmap и
pstack. Наконец, процессу разрешается выйти, чтобы повторно запуститься через
prun.
application_invocation &
pid=$!
truss -t \!all -m\!all -s \!all -S segv,bus -p $pid
if kill -0 $pid ; then
pmap $pid
pstack $pid
prun $pid
fi
Недостаток truss - обработка одного единственного процесса, тогда как приложения могут создавать множество дочерних, кроме того, для каждой программы необходимо писать свой собственный скрипт для обработки.
Лучшее решение предоставляет нам DTrace, а именно скрипт
app_crash.d:
#!/usr/sbin/dtrace -qws
#pragma D option strsize=500
* Copyright 2005 Sun Microsystems, Inc. ALL RIGHTS RESERVED
* Use of this software is authorized pursuant to the terms of the license found at
* http://developers.sun.com/berkeley_license.html
* By Greg Nakhimovsky and Morgan Herrington, Sun MDE, April 2005.
*
* This DTrace script can be run as a daemon started by root
* (perhaps on boot with an RC script like /etc/rc2.d/S97app_crash),
* or it can be run by any user with appropriate DTrace permissions
* (in which case it would work for that user's applications only).
* args[1]->pr_pid is the signal-receiving process id;
* Only react if it is the same as the sending process id (pid).
proc:::signal-send
/(args[2] == SIGBUS || args[2] == SIGSEGV) &&
pid == args[1]->pr_pid/
{
stop();
/* If $ON_APP_CRASH_INVOKE is defined in pid space,
* run user-provided script as $USER */
system(
"%s=%d; %s=%d; %s=%d; %s=%s; %s %s %s %s %s %s %s %s %s",
"CRASH_PID", pid,
"CRASH_UID", uid,
"DTRACE_UID", $uid,
"PROG", execname,
"SCRIPT=`/bin/pargs -e $CRASH_PID | ",
" /bin/grep ON_APP_CRASH_INVOKE | /bin/cut -d= -f2`;",
"[ -z \"$SCRIPT\" -o ! -x \"$SCRIPT\" ] && exit 0;",
"if [ $DTRACE_UID -eq 0 -a $CRASH_UID -ne 0 ] ; then",
" USER_NAME=`/bin/getent passwd $CRASH_UID|/bin/cut -d: -f1`;",
" /bin/su $USER_NAME -c \"$SCRIPT $CRASH_PID $PROG\";",
"else ",
" $SCRIPT $CRASH_PID $PROG; ",
"fi"
);
/* Continue normal processing */
system("/bin/prun %d", pid);
}
Рассмотрим некоторые строки этого скрипта:
#!/usr/sbin/dtrace -qws
Опция "-q" указывает dtrace не выводить дополнительные сообщения, "w" - разрешает
destructive actions типа system(), "s" - указывает выполнить данный сценарий.
#pragma D option strsize=500
Эта опция определяет допустимую длину строк в 500 символов. Размер по умолчанию в 256 не является достаточным для наших целей.
proc:::signal-send
/(args[2] == SIGBUS || args[2] == SIGSEGV) &&
pid == args[1]->pr_pid/
Эти строки определяют pid любого процесса, который пошлет сигнал SIGBUS или SIGSEGV.
В то время как индивидуальные пользователи могут использовать AppCrash для контроля за собственными приложениями, администратор можете запускать этот скрипт в качестве демона /etc/rc2.d/S97app_crash, или создать SMF-службу для отслеживания всех пользовательских программ.
После запуска демона app_crash.d, DTrace будет реагировать на любые приложения, подающие сигналы SIGSEGV или SIGBUS. Если для процесса определена переменная окружения $ON_APP_CRASH_INVOKE (shell-скрипт типа
runme_on_app_crash):
#!/bin/sh
# Template for a user-defined script invoked on application crash.
# Copyright 2005 Sun Microsystems, Inc. ALL RIGHTS RESERVED
# Use of this software is authorized pursuant to the terms of the license found at
# http://developers.sun.com/berkeley_license.html
# By Greg Nakhimovsky and Morgan Herrington, Sun MDE, April 2005.
# $1 = process id of the crashing process.
# $2 = name of the crashing program.
PID=$1
PROG=$2
APPCRASH_OUT=/var/tmp/appcrash.$PROG.$PID
# Unset ON_APP_CRASH_INVOKE to prevent recursion
ON_APP_CRASH_INVOKE=
export ON_APP_CRASH_INVOKE
# Function to print and run a given command:
print_run()
{
COMMAND="$@"
echo "\n> $COMMAND" >> $APPCRASH_OUT 2>&1
# "eval" is needed to recognize the pipe ("|") character:
eval $COMMAND >> $APPCRASH_OUT 2>&1
}
# Write a message to console if we have the permissions:
PROCESS_OWNER=`/usr/xpg4/bin/id -u -n`
CONSOLE_OWNER=`/bin/ls -lL /dev/console | /bin/awk '{ print $3; }'`
if [ $PROCESS_OWNER = "root" -o $PROCESS_OWNER = $CONSOLE_OWNER ] ; then
echo "`/bin/date`: $PROG (pid=$PID) has crashed, see $APPCRASH_OUT" > /dev/console 2>&1
fi
echo "Output from runme_on_app_crash" > $APPCRASH_OUT 2>&1
echo "Program: $PROG" >> $APPCRASH_OUT 2>&1
echo "Process ID: $PID" >> $APPCRASH_OUT 2>&1
echo "\nApplication Debugging Data" >> $APPCRASH_OUT 2>&1
echo "--------------------------" >> $APPCRASH_OUT 2>&1
print_run "/bin/pstack $PID"
print_run "/bin/pmap -x $PID"
print_run "/bin/pldd $PID"
print_run "/bin/ptree $PID"
print_run "/bin/pargs -ace $PID"
print_run "/bin/plimit -m $PID"
print_run "/bin/pwdx $PID"
print_run "/bin/pfiles $PID"
# You may want to add "pcred" if interested in user ids:
# print_run "/bin/pcred $PID"
echo "\nSystem Configuration Data" >> $APPCRASH_OUT 2>&1
echo "-------------------------" >> $APPCRASH_OUT 2>&1
print_run "/bin/uname -a"
print_run "/bin/cat /etc/release"
print_run "/usr/sbin/psrinfo -v"
print_run "/usr/sbin/swap -s"
print_run "/usr/sbin/swap -l"
print_run "/usr/sbin/prtconf|/bin/head -2"
print_run "/bin/showrev -p|/bin/cut -d' ' -f2|/bin/sort"
# For more system configuration commands (such as graphics
# configuration) see script "check_config" described in
# http://www.sun.com/technical-computing/ISV/PTCFaq.html#CHECK_CONFIG
# If desired, add application version/build information like this:
# if [ $PROG = "foo" ]; then
# VER=`cat /opt/foo/txt/version.txt`
# elif [ $PROG = "bar" ]; then
# # Use an appropriate method to get the version of program "bar"
# else
# VER="Unknown"
# fi
# print_run "echo Application Version/Build = $VER"
# Optionally, you can automatically email the result to your
# tech support organization, like this:
# /bin/cat $APPCRASH_OUT | /bin/mailx -s \
# "$PROG crash: pid $PID" support@whatever.com
тогда dtrace сделает следующее:
* Остановит процесс после получения сигнала SIGSEGV или SIGBUS.
* Запустит определенный пользователем скрипт ( runme_on_app_crash) для сбора всех необходимых для отладки данных.
* Возобновит нормальную обработку.
Приведенный выше скрипт - только шаблон, который можно использовать для создания собственного обработчика.
Для отладки своего приложения пользователем, администратору необходимо внести разрешения в файл /etc/user_attr:
< user-name>::::defaultpriv=basic,dtrace_proc,dtrace_kernel
Давайте испытаем работу DTrace, скомпилировав заведомо "падающую" программу (из-за null pointer в sub2()):
% cat test1.c
#include
#include
static void sub2(int *p)
{
int i;
i = *p;
}
static void sub(int *p)
{
sub2(p);
}
int main()
{
int *p=NULL;
sub(p);
return 0;
}
Компилируем:
Теперь запускаем наш демон:
~ ./app_crash.d &
[1] 5707
Определяем переменную для обработчика:
~ setenv ON_APP_CRASH_INVOKE $HOME/tests/runme_on_app_crash
Запускаем программу test1, которая сразу же падает после запуска:
~ test1
Segmentation Fault
Наш скрипт-обработчик runme_on_app_crash собрал всю информацию в /var/tmp, давайте посмотрим на результаты:
~ ls -lt /var/tmp/ | head -2
total 42
-rw-r--r-- 1 gregns staff
4037 Apr 20 11:30 /var/tmp/appcrash.test1.5174
% cat /var/tmp/appcrash.test1.5174
Output from runme_on_app_crash
Program: test1
Process ID: 5174
Application Debugging Data
--------------------------
> /bin/pstack 5174
5174: test1
08050652 sub2 (0) + 12
08050688 sub (0) + 18
080506bf main (1, 8047cec, 8047cf4) + 1f
080505aa ???????? (1, 8047db0, 0, 8047db6, 8047dc8, 8047e49)
> /bin/pmap -x 5174
5174: test1
Address Kbytes RSS Anon Locked Mode Mapped File
08047000 4 4 4 - rwx-- [ stack ]
08050000 4 4 - - r-x-- test1
08060000 4 4 4 - rwx-- test1
FEEE0000 4 4 4 - rwx-- [ anon ]
FEEF0000 24 12 12 - rwx-- [ anon ]
FEF00000 724 724 - - r-x-- libc.so.1
FEFC5000 24 24 24 - rw--- libc.so.1
FEFCB000 8 8 8 - rw--- libc.so.1
FEFDA000 128 128 - - r-x-- ld.so.1
FEFFA000 4 4 4 - rwx-- ld.so.1
FEFFB000 8 8 8 - rwx-- ld.so.1
-------- ------- ------- ------- -------
total Kb 936 924 68 -
....
и так далее.
Приведенный нами пример использования DTrace , несомненно, является самым эффективным и настраиваемым
для анализа падения и отладки приложений в многопользовательских системах.
(По материалам "Enabling User-Controlled Collection of Application Crash Data With DTrace" c developers.sun.com)