2011. Машбук (1114722), страница 44
Текст из файла (страница 44)
Рассмотримтиповую схему организации трассировки. Будем рассматривать взаимодействиеродительского процесса-отладчика с подчиненным сыновним процессом (Рис. 98)....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);/*теперьвыполняютсялюбыедействиянадтрассируемым процессом */161…}}Процесс-потомоксигналSIGTRAPptrace(PTRACE_TRACEME,0, 0, 0);exec(...);...сигналSIGTRAPПроцесс-предокwait(...);for(;;){...ptrace(PTRACE_SINGLESTEP,...);...wait(...);}Рис.
98. Общая схема трассировки процессов.Отцовский процесс формирует сыновний процесс и ожидает его завершенияпосредством обращения к системному вызову wait(). Сыновний процесс подтверждаетправо родителя его трассировать (обращаясь к системному вызову ptrace() с кодомcmd = PTRACE_TRACEME и нулевыми оставшимися аргументами). После чего он меняетсвое тело на тело процесса, которое необходимо отлаживать (посредством обращения кодному из системных вызовов exec()). После смены тела данный процесс приостановитсяна точке входа и пошлет сигнал SIGTRAP родительскому процессу.
Именно с этогомомента начинается отладка: отлаживаемый процесс загружен, он готов к отладке инаходится в начальной точке процесса. Дальше родительский трассирующий процессможет делать все те действия, которые ему необходимы по отладке: запустить процесс сточки останова, читать содержимое различных переменных, устанавливать контрольныеточки и т.п.Отладчики бывают двух типов: адресно-кодовыми и символьными.
Адреснокодовые отладчики оперируют адресами тела отлаживаемого процесса, в то время каксимвольные отладчики позволяют оперировать объектами языка, т.е. переменными иоператорами языка.Механизм организации контрольной точки в адресно-кодовом отладчикедостаточно простой. Пусть нам необходимо по некоторому адресу A установитьконтрольную точку, т.е.
организовать так, чтобы при приходе управления в эту точкупрограммы процесс всегда приостанавливался, и управление передавалось процессуотладчику. В отладчике имеется таблица контрольных точек, в каждой строке которойприсутствует адрес некоторой контрольной точки и оригинальный код (содержимое)отлаживаемого процесса, взятый по данному адресу. Для установки контрольной точки поадресу A необходимо тем или иным способом остановить отлаживаемый процесс (либо оностанавливается при входе, либо отладчик посылает ему соответствующий сигнал). Затемотладчик читает из сегмента кода машинное слово по адресу A (посредством обращения ксистемному вызову ptrace()), которое он записывает в соответствующую строку таблицыконтрольных точек, тем самым, сохраняя оригинальное содержимое тела трассируемогопроцесса.
Далее по адресу A в сегмент кода записывается команда, которая вызываетпрерывание, и, соответственно, приход предопределенного события (сигнала). Примеромможет служить команда деления на ноль. После этого запускаем отлаживаемый процессна исполнение.162Итак, трассируемый процесс исполняется, и управление, наконец, передается намашинное слово по адресу A. Происходит деление на ноль. Соответственно, происходитпрерывание, система передает сигнал. И отладчик через системный вызов wait() получаеткод возврата и «понимает», что в сыновнем процессе случилось деление на ноль.Отладчик посредством системного вызова ptrace() читает адрес останова в контекстесыновнего процесса.
Далее анализируется причина останова. Если причиной остановаявилось деление на ноль, то возможны две ситуации: либо это действительно деление наноль как ошибка, либо это деление на ноль как контрольная точка. Для идентификацииэтой ситуации отладчик обращается к таблице контрольных точек и ищет там адресостанова подчиненного процесса. Если в данной таблице указанный адрес встретился, тоэто означает, что отлаживаемый процесс пришел на контрольную точку (иначе деление наноль отрабатывается как ошибка).Находясь в контрольной точке, отладчик может производить различныеманипуляции с трассируемым процессом (читать данные, устанавливать новыеконтрольные точки и т.п.).
Далее встает вопрос, как корректно продолжить подчиненныйпроцесс. Для этого производятся следующие действия. В сегмент кода по адресу Aзаписывается оригинальное машинное слово (которое берётся из таблицы контрольныхточек). После этого системным вызовом ptrace() включаем пошаговый режим выполненияпроцесса. И выполняем одну команду (которую только что записали по адресу A). Из-завключенного режима пошаговой отладки подчиненный процесс снова остановится. Затемотладчик выключает режим пошаговой отладки и запускает процесс с текущей точки.Для организации контрольных точек в символьных отладчиках необходимаинформация, собранная на этапах компиляции и редактирования связей. Если скомпилятором связан символьный отладчик, то компилятор формирует некоторуюспециализированную базу данных, в которой находится информация по всем именам,используемым в программе.
Для каждого имени определены диапазоны видимости исуществования этого имени, его тип (статическая переменная, автоматическаяпеременная, регистровая переменная и т.п.). А также данная база содержит информациюобо всех операторах (диапазон начала и конца оператора и т.п.).Предположим, необходимо просмотреть содержимое некоторой переменной v.
Дляэтого трассируемый процесс должен быть остановлен. По адресу останова можноопределить, в какой точке программы произошел останов. На основе информации об этойточке программы можно (обратившись к содержимому базы данных) определить топространство имен, которое доступно из этой точки. Если интересующая нас переменная vоказалась доступна, то работа продолжается: происходит обращение к базе данных иопределяется тип данной переменной. Если тип переменной v — статическая переменная,то в соответствующей записи будет адрес, по которому размещена данная переменная(этот адрес станет известным на этапе редактирования связей). И с помощью ptrace()отладчик берет содержимое по этому адресу.
Также из базы данных берется информация отипе переменной (char, float, int и т.п.), и на основе этой информации пользователюсоответствующим образом отображается значение указанной переменной v.Рассмотрим теперь случай, когда переменная v – автоматическая переменная илиформальный параметр.
Такие переменные обычно размещаются в блоке нафиксированном смещении относительно вершины стека (т.е. для этих переменных вкачестве адреса фиксируется смещение от вершины стека). Чтобы прочитать содержимоепеременной подобного типа, необходимо обратиться к контексту процесса, считатьзначение регистра-указателя стека (адрес вершины стека), после чего к содержимомуэтого регистра необходимо прибавить смещение и по получившемуся адресу обратиться ксоответствующему сегменту.Если переменная v — регистровая переменная, то происходит обращение к базеданных, считывается номер регистра, затем идет обращение к сегменту кода исчитывается содержимое нужного регистра.163При записи значений в переменные происходит та же последовательностьдействий.Если необходимо установить контрольную точку на оператор, то через базу данныхопределяется диапазон адресов оператора, определяется начальный адрес, а дальшепроизводятся действия по описанной выше схеме.Пример.
Трассировка процессов./* Процесс-сын: */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, ®, ®);/* выводим статус отлаживаемого процесса, номерсигнала,которыйегоостановилизначенияпрочитанных регистров */164printf("signal = %d, status = %#x, EIP=%#xESP=%#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);}}3.2Системамежпроцессного(Inter-Process Communication)взаимодействияIPCВсе рассмотренные выше средства взаимодействия процессов не обладаютдостаточной универсальностью и имеют те или иные недостатки: так, сигналы несут всебе слишком мало информации и не могут использоваться для передачи сколь либозначительных объемов данных; неименованный канал должен быть создан до того, какпорождается процесс, который будет осуществлять коммуникацию; именованный канал,хотя и лишен этого недостатка, требует одновременной работы с ним обоих процессов,участвующих в коммуникации.
Поэтому практически все UNIX-системы поддерживаютболее мощные и развитые средства межпроцессного взаимодействия.Система IPC (известная так же, как IPC System V) позволяет обеспечиватьвзаимодействие произвольных процессов в пределах локальной машины. Для этого онапредоставляет взаимодействующим процессам возможность использования общих, илиразделяемых, ресурсов.
Эти ресурсы могут существовать в системе с момента создания домомента их принудительного удаления либо в течение сеанса работы операционнойсистемы (вне зависимости от наличия процессов, которые их используют). Такиеразделяемые ресурсы можно подразделить на три типа:Очередь сообщений — это разделяемый ресурс, позволяющий организовыватьочереди сообщений: один процесс может в эту очередь положить сообщение, а другойпроцесс — прочитать его. Данный механизм имеет возможность блокировок, поэтомуего можно использовать и как средство передачи информации междувзаимодействующими процессами, и как средство их синхронизации.Массив семафоров — ресурс, представляющий собой массив из N элементов, где Nзадается при создании данного ресурса, и каждый из элементов является семафоромIPC (а не семафором Дейкстры: семафор Дейкстры так или иначе являетсяформализмом, не опирающимся ни на какую реализацию, а семафор IPC —конкретной программной реализацией семафора в ОС).