Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект Граур.doc
Скачиваний:
62
Добавлен:
14.11.2019
Размер:
3.7 Mб
Скачать

Общая схема трассировки процессов

Рассмотрим некоторый модельный пример, демонстрирующий общую схему построения отладочной программы:

...

if ((pid = fork()) == 0)

{

ptrace(PTRACE_TRACEME, 0, 0, 0);

/* сыновний процесс разрешает трассировать себя */

exec(“трассируемый процесс”, 0);

/* замещается телом процесса, который необходимо трассировать */

}

else

{

/* это процесс, управляющий трассировкой */

wait((int ) 0);

/* процесс приостанавливается до тех пор, пока от трассируемого процесса не придет сообщение о том, что он приостановился */

for(;;)

{

ptrace(PTRACE_SINGLESTEP, pid, 0, 0);

/* возобновляем выполнение трассируемой программы */

wait((int ) 0);

/* процесс приостанавливается до тех пор, пока от трассируемого процесса не придет сообщение о том, что он приостановился */

ptrace(cmd, pid, addr, data);

/* теперь выполняются любые действия над трассируемым процессом */

}

}

Предназначение процесса-потомка — разрешить трассировку себя. После вызова ptrace(PTRACE_TRACEME, 0, 0, 0) ядро устанавливает для этого процесса бит трассировки. Сразу же после этого можно заместить код процесса-потомка кодом программы, которую необходимо отладить. Отметим, что при выполнении системного вызова exec(), если для данного процесса ранее был установлен бит трассировки, ядро перед передачей управления в новую программу посылает процессу сигнал SIGTRAP. При получении данного сигнала трассируемый процесс приостанавливается, и ядро передает управление процессу-отладчику, выводя его из ожидания в вызове wait().

Процесс-родитель вызывает wait() и переходит в состояние ожидания до того момента, пока потомок не перейдет в состояние трассировки. Проснувшись, управляющий процесс, выполняя функцию ptrace(cmd, pid, addr, data) с различными кодами операций, может производить любое действие с трассируемой программой, в частности, читать и записывать данные в адресном пространстве трассируемого процесса, производить его пошаговое выполнение – при этом, как показано в примере выше, применяется та же схема: процесс-отладчик вызывает wait() в состояние ожидания, а ядро возобновляет выполнение трассируемого потомка, исполняет трассируемую команду, и вновь передает управление отладчику, выводя его из ожидания .

Пример. Использование трассировки.

Процесс, который мы будем отлаживать, осуществляет деление на 0. Это сделано для того, чтобы показать, как происходит прерывание выполнения отлаживаемого процесса, когда к нему приходит сигнал. Т.е. когда было произведено деление на 0, приходит сигнал, который мы и будем ловить в нашем отлаживаемом процессе, но в отличии от обычной ситуации, когда обработка по умолчанию на этот сигнал означала бы завершение процесса, в данном случае он будет только приостановлен. В процессе-отладчике, во-первых, описывается структура, при помощи которой будет осуществляться чтение из регистра; далее порождается процесс; в процессе-сыне запускается трассировка (вызов ptrace с командой PTRACE_TRACEME) и запускается замена тела процесса на процесс, который состоит из единственного деления на 0. Отец запускает wait(), первый выход из этого wait() будет сразу после execl() в сыне. После этого он добывает содержимое некоторых регистров, печатает их содержимое. Обратите внимание, что имена этих регистров – EIP(индексный регистр) и ESP(stack pointer) являются машинно-зависимыми, т.е. на другой машине структура REG, а именно имена полей в ней могут быть другими, но в целом структура REG описывает регистры общего назначения на данной архитектуре. Соответственно, они были получены с помощью команды PTRACE_GETREGS в вызове ptrace, затем они распечатываются, затем идет проверка на то, был ли процесс приостановлен с помощью сигнала SIGTRAP и, если этот так, то продолжается его выполнение.Если это не так (это будет в том случае, когда уже второй раз был получен сигнал SIGTRAP от отлаживаемого процесса, когда он остановился в результате деления на 0) он проверяет, что действительно в статусе передано, что этот процесс должен завершится, и он (процесс) закрывается.

Эта схема работает следующим образом: процесс – сын запускает трассировку и запускает execl(), после чего сразу приостанавливается, потому что к нему пришел сигнал SIGTRAP. Процесс-отец в цикле ожидает в wait(), выходит из ожидания, потому что процесс-сын приостанавливается, далее проверяет содержимое его регистров, печатает это содержимое и запускает опять его выполнение. Теперь процесс-сын выполняет программу, которая осуществляет деление на 0, так только он осуществил деление на 0 ему приходит сигнал SIGFPE, и так как он находится в ситуации трассировки, то он приостанавливается, и управление вновь передается процессу-отцу. Процесс-отец вновь вышел из wait(), проанализировал ситуацию, увидел из-за какого сигнала прервалось выполнение отлаживаемого процесса. Для этого он использует следующие макросы: WSTOPSIG() – это макрос, который позволяет из статуса процесса (из того что вернулось в функции wait()) получить информацию из-за чего тот был приостановлен: т.е. то, что он был приостановлен в результате сигнала или в результате какого сигнала. Далее из этого же статуса с помощью макроса WIFEXITED(), что процесс-потомок должен был завершиться, и в этом случае он просто убивается с помощью использования команды PTRACE_KILL в вызове ptrace.

/* Процесс-сын: */

int main(int argc, char **argv)

{

/* деление на ноль – здесь процессу будет послан сигнал SIGFPE – floating point exception */

return argc/0;

}

/* Процесс-родитель:*/

#include <stdio.h>

#include <sys/types.h>

#include <unistd.h>

#include <signal.h>

#include <sys/ptrace.h>

#include <sys/user.h>

#include <sys/wait.h>

int main(int argc, char *argv[])

{

pid_t pid;

int status;

struct user_regs_struct REG;

if ((pid = fork()) == 0) {

/*находимся в процессе-потомке, разрешаем трассировку */

ptrace(PTRACE_TRACEME, 0, 0, 0);

execl(“son”, ”son”, 0); /* замещаем тело процесса */

/* здесь процесс-потомок будет остановлен с сигналом SIG_TRAP, ожидая команды продолжения выполнения от управляющего процесса*/

}

/* в процессе-родителе */

while (1) {

/* ждем, когда отлаживаемый процесс приостановится */

wait(&status);

/*читаем содержимое регистров отлаживаемого процесса */

ptrace(PTRACE_GETREGS, pid, &REG, &REG);

/* выводим статус отлаживаемого процесса, номер сигнала, который его остановил и значения прочитанных регистров */

printf("signal = %d, status = %#x, EIP=%#x ESP=%#x\n", WSTOPSIG(status), status, REG.eip, REG.esp);

if (WSTOPSIG(status) != SIGTRAP) {

if (!WIFEXITED(status)) {

/* завершаем выполнение трассируемого процесса */

ptrace (PTRACE_KILL, pid, 0, 0);

}

break;

}

/* разрешаем выполнение трассируемому процессу */

ptrace (PTRACE_CONT, pid, 0, 0);

}

}