Операционные системы/Базовые средства взаимодействия процессов в ОС UNIX.
Материал из eSyr's wiki.
Обзор форм межпроцессного взаимодействия в UNIX был бы не полон, если бы мы не рассмотрели простейшую форму взаимодействия, используемую для отладки — трассировку процессов. Принципиальное отличие трассировки от остальных видов межпроцессного взаимодействия в том, что она реализует модель «главный-подчиненный»: один процесс получает возможность управлять ходом выполнения, а также данными и кодом другого.
[править] Трассировка процессов
В UNIX трассировка возможна только между родственными процессами: процесс-родитель может вести трассировку только непосредственно порожденных им потомков, при этом трассировка начинается только после того, как процесс-потомок дает разрешение на это.
Далее схема взаимодействия процессов путем трассировки такова: выполнение отлаживаемого процесса-потомка приостанавливается всякий раз при
- получении им какого-либо сигнала
- выполнении вызова exec()
- достижении контрольной точки. Контрольную точку можно поставить, например, выполнив деление на 0 (т.е. частный случай 1).
Если в это время отлаживающий процесс осуществляет системный вызов wait(), этот вызов немедленно возвращает управление. В то время, как трассируемый процесс находится в приостановленном состоянии, процесс-отладчик имеет возможность анализировать и изменять данные в адресном пространстве отлаживаемого процесса и в пользовательской составляющей его контекста. Далее, процесс-отладчик возобновляет выполнение трассируемого процесса до следующей приостановки (либо, при пошаговом выполнении, для выполнения одной инструкции).
Основной системный вызов, используемый при трассировке,– это ptrace(), прототип которого выглядит следующим образом:
#include <sys/ptrace.h> int ptrace(int cmd, pid, addr, data);
где cmd – код выполняемой команды, pid – идентификатор процесса-потомка, addr – некоторый адрес в адресном пространстве процесса-потомка, data – слово информации.
Чтобы оценить уровень предоставляемых возможностей, рассмотрим основные коды - cmd операций этой функции.
- cmd = PTRACE_TRACEME — ptrace() с таким кодом операции сыновний процесс вызывает в самом начале своей работы, позволяя тем самым трассировать себя. Все остальные обращения к вызову ptrace() осуществляет процесс-отладчик.
- cmd = PTRACE_PEEKDATA — чтение слова из адресного пространства отлаживаемого процесса по адресу addr, ptrace() возвращает значение этого слова.
- cmd = PTRACE_PEEKUSER — чтение слова из контекста процесса. Речь идет о доступе к пользовательской составляющей контекста данного процесса, сгруппированной в некоторую структуру, описанную в заголовочном файле <sys/user.h>. В этом случае параметр addr указывает смещение относительно начала этой структуры. В этой структуре размещена такая информация, как регистры, текущее состояние процесса, счетчик адреса и так далее. ptrace() возвращает значение считанного слова.
- cmd = PTRACE_POKEDATA — запись данных, размещенных в параметре data, по адресу addr в адресном пространстве процесса-потомка.
- cmd = PTRACE_POKEUSER — запись слова из data в контекст трассируемого процесса со смещением addr. Таким образом можно, например, изменить счетчик адреса трассируемого процесса, и при последующем возобновлении трассируемого процесса его выполнение начнется с инструкции, находящейся по заданному адресу.
- cmd = PTRACE_GETREGS, PTRACE_GETFREGS — чтение регистров общего назначения (в т.ч. с плавающей точкой) трассируемого процесса и запись их значения по адресу data.
- cmd = PTRACE_SETREGS, PTRACE_SETFREGS — запись в регистры общего назначения (в т.ч. с плавающей точкой) трассируемого процесса данных, расположенных по адресу data в трассирующем процессе.
- cmd = PTRACE_CONT — возобновление выполнения трассируемого процесса. Отлаживаемый процесс будет выполняться до тех пор, пока не получит какой-либо сигнал, либо пока не завершится.
- cmd = PTRACE_SYSCALL, PTRACE_SINGLESTEP — эта команда, аналогично PTRACE_CONT, возобновляет выполнение трассируемой программы, но при этом произойдет ее остановка после того, как выполнится одна инструкция. Таким образом, используя PTRACE_SINGLESTEP, можно организовать пошаговую отладку. С помощью команды PTRACE_SYSCALL возобновляется выполнение трассируемой программы вплоть до ближайшего входа или выхода из системного вызова. Идея использования PTRACE_SYSCALL в том, чтобы иметь возможность контролировать значения аргументов, переданных в системный вызов трассируемым процессом, и возвращаемое значение, переданное ему из системного вызова.
- cmd = PTRACE_KILL — завершение выполнения трассируемого процесса.
[править] Общая схема использования механизма трассировки
Рассмотрим некоторый модельный пример, демонстрирующий общую схему построения отладочной программы:
... 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() и погружается в состояние ожидания, а ядро возобновляет выполнение трассируемого потомка, исполняет трассируемую команду и вновь передает управление отладчику, выводя его из ожидания.
[править] Трассировка процессов
/* Процесс-сын: */ 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, ®, ®); /* выводим статус отлаживаемого процесса, номер сигнала, который его остановил и значения прочитанных регистров */ 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); } }