My favorites | Sign in
Project Logo
                
Search
for
Updated Jul 20, 2008 by jetbird
ProcessTracingUsingPtrace  
описан процесс отладки, взаимодействие процессов, пример кода

ОТЛАДКА ПРОЦЕССА С ПОМОЩЬЮ Ptrace

Оригинал
Перевод: Леонид Крашенко (jetbird@jabber.org, http://jetbird.wordpress.com).
"LinuxGazette... прикалывая Линукс!"

Системный вызов ptrace необходим таким программам-отладчикам, как gdb - пока что он не очень-то хорошо документирован - хотя, поверьте, лучшая документация - исходники ядра! Я попытаюсь продемонстрировать, как можно использовать ptrace для реализации некоторой части функциональности таких инструментов, как gdb.

1. Введение.

ptrace() - это системный вызов, позволяющий одному процессу управлять выполнением другого. Он также позволяет заменить образ(core image) управляемого процесса. Отслеживаемый процесс чувствует себя нормально до тех пор, пока не будет получен какой-нибудь сигнал. Когда такое происходит, процесс входит в ступор (stopped state), информирую управляющий процесс вызовом wait(). Управляющий, в свою очередь, решает, что бы такое ему ответить. Хотя в случае сигнала SIGKILL управляемый процесс уничтожается наверняка.

Управляемый процесс может остановиться и по другим причинам, например, из-за специфических событий, возникающих во время выполнения. Это может произойти только в том случае, если управляющий выставил некоторые флаги событий в контексте управляемого процесса. Управляющий процесс может даже уничтожить управляемый процесс, устанавливая код выхода (exit code). После завершения управления управляющий может как уничтожить управляемого, так и оставить его выполнять свои задачи.

Примечание: Ptrace() сильно зависит от архитектуры оборудования. Приложения, использующие ptrace непортабельны (не так легко).

2. Поглубже в детали

Прототип ptrace() выглядит так:

#include <sys/ptrace.h>
long int ptrace(enum __ptrace_request request, pid_t pid,
	void *addr, void *data)

Значение request описывает, что должно быть сделано. Pid - это ID ведомого процесса. Addr - это смещение в пользовательском пространсве управляемого процесса (туда будут записаны данные, если потребуется). Это смещение в пользовательском пространстве управляемого процесса, откуда будет прочитано машинное слово как возвращаемое значение вызова.

Родитель может сделать fork() и управлять им с помощью request, равного PTRACE_TRACEME. Родитель может также отслеживать существующий процесс при помощи PTRACE_ATTACH. Различные значения request обсуждаются ниже.

2.1. Как работает ptrace()

Каждый раз, когда вызывается ptrace(), первым делом он запирает ядро. Прямо перед возвратом он снова открывает ядро. Давайте посмотрим, как это работает при различных значениях request.

PTRACE_TRACEME: вызывается, когда дочерний процесс отслеживается родительским. Как было отмечено выше, любы сигналы, кроме SIGKILL, либо пришедшие извне или через вызовы exec, сделанные процессом, заставляют его остановиться и предоставляют родительскому процессу решить, что делать дальше. Внутри ptrace() проверяется только 1 вещь: поставлен ли флаг ptrace на данном процессе или нет. Если нет, разрешение получено и устанавливается флаг. Все остальные параметры ptrace игнорирует.

PTRACE_ATTACH: 1 процесс хочет поуправлять другим. Нужно зарубить на носу только, что никому не дозволено контролировать init-процесс. Он даже сам себя контролировать права не имеет. Итак, текущий процесс (вызывающий) становится родительским по отношению к процессу с номером pid. Однако getpid(), вызванная в дочернем процессе, возвращает pid родительского.

За кулисами происходят обычные проверки прав доступа, является ли процесс init-ом или нет. Если нет препятствий, дается разрешение и устанавливается флаг. В этот момент ссылки дочернего процессе перераспределяются, т.е. дочерний процесс снимается с очереди задач и позиция его родителя также меняется (настоящий родитель остается прежним). Он возвращается в очередь задач таким образом, чтобы init следовал прямо за ним. В конце концов ему приходит сигнал SIGSTOP. Параметры addr и data игнорируются.

PTRACE_DETACH: завершение управления процессом. Управляющему надо решать, что делать с управляемым: мочить немедленно или оставить барахтаться. ptrace отменяет все изменения, сделанные при PTRACE_ATTACH/PTRACE_TRACEME. Родитель посылает код выхода дочернему процессу в параметре data. Сбрасывается флаг Ptrace. Дочерний процесс помещается на прежнее место в очереди задач. pid реального родителя пишется в поле parent. Сбрасывается бит single-step. Наконец, дочерний процесс просыпается как ни в чем, будто бы его никто не ...; параметр addr игнорируется.

PTRACE_PEEKTEXT, PTRACE_PEEKDATA, PTRACE_PEEKUSER: эти опции позволяют считывать данные из памяти дочернего процесса и пользовательского пространства. PTRACE_PEEKTEXT И PTRACE_PEEKDATA читают данные из памяти и вообще ведут себя одинаково. PTRACE_PEEKUSER читает из пользовательского пространства дочернего процесса. Читается машинное слово и помещается во временную структуру данных, а далее с помощью put_user(), что копирует строку из сегмента памяти ядра в сегмент памяти процесса, данных пишется в параметр data и 0 возвращается, если все ок.

В случае PTRACE_PEEKTEXT/PTRACE_PEEKDATA, параметр addr содержит адрес позиции в памяти пользовательского пространства, из которой будут читаться данные. В случае с PTRACE_PEEKUSER addr представляет собой смещение нужного машинного слова в пользовательском пространстве; параметр data игнорируется.

PTRACE_POKETEXT, PTRACE_POKEDATA, PTRACE_POKEUSER: данные опции аналогичны описанным выше. Отличие в том, что эти используются для записи данных (параметр data) в пользовательское пространстве (user space) отслеживаемого процесса. В случае с PTRACE_POKETEXT и PTRACE_POKEDATA копируется машинное слово из data в память дочернего процесса по адресу addr.

В случае с PTRACE_POKEUSER мы пытаемся модицицировать данные по некоторым адресам в структуре task_struct процесса. Поскольку необходимо поддерживать целостность ядра, надо быть очень осторожными. После всех проверок в ptrace разрешается изменить строго определенные части task_struct. Параметр addr здесь является смещением в польовательском пространстве дочернего процесса. PTRACE_SYSCALL, PTRACE_CONT: используются для пробуждения заснувшего (остановленного) процесса. PTRACE_STSCALL заставляет дочерний процесс остановиться после следующего системного вызова. PTRACE_CONT позволяет дочернему процессу продолжить выполнение. В обоих случаях код выхода (exit code) устанавливается ptrace() и содержится в data. Все это происходит только если сигнал/код выхода валидны. ptrace() сбрасывает бит single step дочернего процесса, устанавливает/сбрасывает бит syscall trace и пробуждает процесс, параметр addr игнорируется.

PTRACE_SINGLESTEP: делает тоже самое, что PTRACE_SYSCALL, с той разницей, что дочерний процесс останавливается после каждой инструкции. Устанавливается бит single step. Параметр data содержит код выхода, addr игнорируется.

PTRACE_KILL: уничтожает дочерний процесс. Происходит это так: ptrace() проверяет, мертвый ли он уже или нет. Если живой, код выхода становится равным sigkill. Сбрасывается бит single step.

2.2. Более зависимые от оборудования вызовы

Значения request, обсуждавшиеся выше, не зависели от архитектуры и реализации системы. Значения, описанные ниже, позволяют записывать/считывать регистры дочернего процесса. Эти операции непосредственно связаны с оборудованием и архитектурой. Множество регистров включает регистры общего назначения, регистры с плавающей точкой, с расширенной плавающей точкой. Эти опции, для которых требуется прямое взаимодействие между регистрами/сегментами памяти системы, обсуждаются ниже.

PTRACE_GETREGS, PTRACE_GETFPREGS, PTRACE_GETFPXREGS: эти опции позволяют получить доступ к регистрам общего назначения, плавающей точки, расширенной плавающей точки дочернего процесса. Данные считываются в data родительского процесса. Делаются обычные проверки. После этого, данные из регистра копируются в место, указанное параметром data с помощью getreg() и put_user() функций; параметр addr игнорируется.

PTRACE_SETGEGS, PTRACE_SETFPREGS, PTRACE_SETFPXREGS: эти значения параметра request позволяют управляющему процессу устанавливать значения реистров: общего назначения, плавающей точки, расширенной плавающей точки дочернего процесса. Существуют некоторые ограничения. Некоторые вообще нельзя изменять. Данные, записываемые в регистр, берутся и зdata родительского процесса. Параметр addr игнорируется.

2.3. Возвращаемые значения ptrace()

Успешный вызов ptrace() возвращает ноль. Ошибки возвращают -1, и устанавливают errno. Поскольку возвращаемое значение успешного PEEKDATA/PEEKTEXT может быть равным -1, лучше проверять errno. Ошибки бывают следующие:

В действительности трудно различить причины EIO и EFAULT. Они возвращаются для практически идентичных ошибок.

3. Небольшой пример

Не отчаивайтесь, если описание параметров показалось вам слегка суховатым. Больше о них ни слова. ;) Вот небольшой примерчик. Родительский процесс подсчитывает количество инструкций, выполненных тестовой программой, запущенной дочерним процессом.

Программа выводит содержимое данной директории.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>


int main(void)
{
        long long counter = 0;  /*  machine instruction counter */
        int wait_val;           /*  child's return value        */
        int pid;                /*  child's process id          */

        puts("Please wait");

        switch (pid = fork()) {
        case -1:
                perror("fork");
                break;
        case 0: /*  child process starts        */
                ptrace(PTRACE_TRACEME, 0, 0, 0);
                /* 
                 *  must be called in order to allow the
                 *  control over the child process
                 */ 
                execl("/bin/ls", "ls", NULL);
                /*
                 *  executes the program and causes
                 *  the child to stop and send a signal 
                 *  to the parent, the parent can now
                 *  switch to PTRACE_SINGLESTEP   
                 */ 
                break;
                /*  child process ends  */
        default:/*  parent process starts       */
                wait(&wait_val); 
                /*   
                 *   parent waits for child to stop at next 
                 *   instruction (execl()) 
                 */
                while (wait_val == 1407 ) {
                        counter++;
                        if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0) != 0)
                                perror("ptrace");
                        /* 
                         *   switch to singlestep tracing and 
                         *   release child
                         *   if unable call error.
                         */
                        wait(&wait_val);
                        /*   wait for next instruction to complete  */
                }
                /*
                 * continue to stop, wait and release until
                 * the child is finished; wait_val != 1407
                 * Low=0177L and High=05 (SIGTRAP)
                 */
        }
        printf("Number of machine instructions : %lld\n", counter);
        return 0;
}

Запустите любимый редактор (vim! - прим. переводчика) и напишите программу. Затем запустите ее командами:

		cc file.c

		./a.out

Выведется количество инструкций, требуемых для вывода содержимого данного директория. Перейдите в другой и запустите программу там: будет ли разница? (если машина медленная, потребуется какое-то время).

4. Завершение

ptrace() широко используется для отладки. Также для отслеживания системных вызовов. Отладчик делает fork() и дочерний процесс создается и отслеживается родительским. Программа, которую надо отладить, выполняется дочерним процессом (в примере выше это была ls) и после каждой инструкции родитель может посмотреть значения регистров отслеживаемой программы. Я продемонстрирую другие программы, отражающие многосторонность ptrace() в других статьях данной серии. А пока пока.

Sandeep S.

(студент последнего курса Government Engineering College в Thrissur, Kerala, India. Интересуется FreeBSD, сетевыми технологиями и теоретической Computer Science).


Sign in to add a comment
Hosted by Google Code