#define TR_SETUP 0 #define TR_WRITE 5 #define TR_RESUME 7 int addr; main(argc,argv) int argc; char *argv[]; { int i,pid; sscanf(argv[1],"%x",&addr); if ((pid = fork() == 0) { ptrace(TR_SETUP,0,0,0); execl("trace","trace",0); exit(); } for (i = 0; i < 32, i++) { wait((int *) 0); /* записать значение i в пространство процесса с * идентификатором pid по адресу, содержащемуся в * переменной addr */ if (ptrace(TR_WRITE,pid,addr,i) == -1) exit(); addr += sizeof(int); } /* трассируемый процесс возобновляет выполнение */ ptrace(TR_RESUME,pid,1,0); } |
Рассмотрим две программы, приведенные на Рисунках 11.2 и 11.3 и именуемые trace и debug, соответственно. При запуске программы trace с терминала массив data будет содержать нулевые значения; процесс выводит адрес массива и завершает работу. При запуске программы debug с передачей ей в качестве параметра значения, выведенного программой trace, происходит следующее: программа запоминает значение параметра в переменной addr, создает новый процесс, с помощью функции ptrace подготавливающий себя к трассировке, и запускает программу trace. На выходе из функции exec ядро посылает процессу-потомку (назовем его тоже trace) сигнал SIGTRAP (сигнал прерывания), процесс trace переходит в состояние трассировки, ожидая поступления команды от программы debug. Если процесс, реализующий программу debug, находился в состоянии приостанова, связанного с выполнением функции wait, он "пробуждается", обнаруживает наличие порожденного трассируемого процесса и выходит из функции wait. Затем процесс debug вызывает функцию ptrace, записывает значение переменной цикла i в пространство данных процесса trace по адресу, содержащемуся в переменной addr, и увеличивает значение переменной addr; в программе trace переменная addr хранит адрес точки входа в массив data. Последнее обращение процесса debug к функции ptrace вызывает запуск программы trace, и в этот момент массив data содержит значения от 0 до 31. Отладчики, подобные sdb, имеют доступ к таблице идентификаторов трассируемого процесса, из которой они получают информацию об адресах данных, используемых в качестве параметров функции ptrace.
Использование функции ptrace для трассировки процессов является обычным делом, но оно имеет ряд недостатков.
Киллиан [Killian 84] описывает другую схему трассировки процессов, основанную на переключении файловых систем (см. главу 5). Администратор монтирует файловую систему под именем "/proc"; пользователи идентифицируют процессы с помощью кодов идентификации и трактуют их как файлы, принадлежащие каталогу "/proc". Ядро дает разрешение на открытие файлов, исходя из кода идентификации пользователя процесса и кода идентификации группы. Пользователи могут обращаться к адресному пространству процесса путем чтения (read) файла и устанавливать точки прерываний путем записи (write) в файл. Функция stat сообщает различную статистическую информацию, касающуюся процесса. В данном подходе устранены три недостатка, присущие функции ptrace. Во-первых, эта схема работает быстрее, поскольку процесс-отладчик за одно обращение к указанным системным функциям может передавать больше информации, чем при работе с ptrace. Во-вторых, отладчик здесь может вести трассировку совершенно произвольных процессов, а не только своих потомков. Наконец, трассируемый процесс не должен предпринимать предварительно никаких действий по подготовке к трассировке; отладчик может трассировать и существующие процессы. Возможность вести отладку setuid-программ, предоставляемая только суперпользователю, реализуется как составная часть традиционного механизма защиты файлов.