Содержание
Принципы работы отладчиков
Часто для того, чтобы разобраться почему код работает не так, программист прибегает к помощи отладчиков, однако, то как они устроены и каким образом действуют, известно далеко не всем. В этой статье будет дано краткое описание механизма отладки, который используется операционной системой Linux и приведён короткий пример его использования.
Для начала дадим неформальное определение. Отладчик — программа, с помощью которой можно подробно отслеживать и изменять статус выполнения другой программы.
Типы Отладчиков:
- отладчики уровня ядра ОС — компонент ядра ОС, позволяющий отлаживать ОС и процессы. Для работы отладчик использует функционал, заложенный в процессор.
- отладчики приложений — пользовательская программа, которая позволяет отлаживать другие процессы. Для работы используют API-интерфейс, который предоставляет ядро ОС.
Подробно остановимся на втором типе отладчиков, и попробуем разобраться, какой функционал необходимо предоставлять ядро ОС для их работы. Перечислим основные функции, необходимые отладчику:
- возможность стартовать и останавливать отлаживаемый процесс.
- возможность пошагового исполнения кода.
- возможность читать и писать в произвольную область памяти отлаживаемого процесса.
- чтение и изменение содержимого регистров процессора.
Системный вызов ptrace(2)
В OC Linux весь отладочный функционал доступен с использованием системного вызова ptrace(2).
Интерфейс системного вызова:
long
ptrace(enum __ptrace_request request , pid_t pid , void ∗addr , void ∗data);
Аргумент request определяет тип операции: будет ли это попытка начать отладку процесса, или это будет запрос данных по какому-либо адресу. Все прочие аргументы являются опциональными и зависят от значения request.
Начать отладку процесса можно двумя способами:
- C помощью запроса PTRACE_TRACEME текущий процесс будет отлаживаться его родителем. Любой сигнал, полученный текущим процессом, вызовет его остановку, а родительский процесс может быть оповещён об этом сигналом SIGCHLD. Затем родитель при помощи системного вызова wait(2) или подобного
- С помощью запроса PTRACE_ATTACH можно подключиться к уже существующему процессу,
передав в pid его идентификатор.
При успешном завершении всех типов запросов, за исключением PTRACE_PEEK*, ptrace возвращает 0. Если запрос завершился с ошибкой, возвращается -1, а код ошибки заносится в errno(3).
Кратко опишем некоторые типы запросов.
PTRACE_GETREGS, PTRACE_SETREGS, PTRACE_GETFPREGS , PTRACE_SETFPREGS
Получаем или изменяем основные регистры или регистры FPU. Указатель на структуру с регистрами передаётся в data.
Для основных регистров используется структура struct user_regs_struct, для регистров FPU struct user_fpregs_struct . Определения структур находятся в sys/user.h.
PTRACE_PEEK_DATA, PTRACE_PEEK_TEXT , PTRACE_POKE_DATA , PTRACE_POKE_TEXT
Получаем или изменяем данные в памяти исследуемого процесса по переданному в addr адресу. При запросе PTRACE_PEEK* в случае успеха возвращаются запрошенные данные. Так как -1 (0xffffffff) также может быть адресом, для проверки на ошибку необходимо дополнительно проверять значение errno .
PTRACE_CONT
Продолжает выполнение остановленного процесса. Если data не NULL и не SIGSTOP , значение интерпретируется как сигнал, который посылается процессу.
* PTRACE_SINGLESTEP , PTRACE_SYSCALL Продолжает выполнение остановленного процесса, как и в случае с PTRACE_CONT, но указывает, что процесс должен остановиться при переходе к следующей инструкции PTRACE_SINGLESTEP или при входе/выходе из системного вызова PTRACE_SYSCALL. Аргумент data будет интерпретирован как и в случае с PTRACE_CONT.
PTRACE_DETACH
Отменяет эффекты PTRACE_TRACEME, PTRACE_ATTACH для процесса с указанным pid, и продолжает выполнение процесса.
Важно помнить, что все запросы, перечисленные выше, работают только если отлаживаемый процесс остановлен, в чём нужно убедиться с помощью системного вызова wait(2) , иначе ptrace вернёт -1 и установит errno в ESRCH . Описание остальных запросов, не перечисленных выше, можно посмотреть в мануале ptrace(2).
Полезный софт, использующий ptrace(2)
strace(1)
Трассировщик системных вызовов, может быть очень полезен при первичной отладке чужого (или вашего) бинарника, когда нужно понять в какой именно момент всё начинает идти «не по плану»
Пример вызова:
$ strace ls
В результате запуска программы на стандартный поток ошибок валится информация о системных вызовах, которые сделал ls
strace(1) позволяет осуществлять фильтрацию по типам системных вызовов или их именам с помощью опции -e, например:
$ strace -e trace=file sh -c ls
отобразит все обращения к файлам, сделанные процессом sh
ltrace(1)
Трасировщик библиотечных функцкий. Программа работает аналогично strace(3). Однако помимо системных вызовов, показывает все вызовы функций из разделяемых библиотек.
gdb(1)
Полноценный консольный отладчик.