ptrace – fundament debugowania i śledzenia procesów w Linux

Wprowadzenie

W świecie Linuksa istnieje wiele narzędzi do debugowania i monitorowania procesów. Jednak u podstaw większości z nich leży jedno wywołanie systemowe: ptrace.

To właśnie ono umożliwia debuggerowi zatrzymanie programu, odczytanie jego rejestrów, ustawienie breakpointów czy śledzenie wywołań systemowych.

Co to jest ptrace?

ptrace (process trace) to system call, który pozwala jednemu procesowi (tzw. tracer) kontrolować inny proces (tracee). Najważniejsze możliwości:

  • Odczyt i zapis pamięci procesu – można podejrzeć zmienne, stos czy dane w heapie.
  • Odczyt i modyfikacja rejestrów CPU – np. zmiana wartości licznika instrukcji.
  • Kontrola wykonania – zatrzymywanie, wznawianie, pojedyncze kroki.
  • Śledzenie wywołań systemowych – np. wszystkie open(), read(), write().
  • Monitorowanie sygnałów – np. SIGSEGV, SIGKILL.

Dlaczego warto znać ptrace?

  • Debugowanie – to fundament działania gdb.
  • Analiza bezpieczeństwa – pozwala na sandboxing i kontrolę procesów.
  • Reverse engineering – śledzenie zachowania binarek bez kodu źródłowego.
  • Tworzenie własnych narzędzi – np. mini-strace w C.

Podstawowa składnia

Wywołanie systemowe:

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

Najczęściej używane wywołania:

  • PTRACE_TRACEME – proces zgadza się być śledzony.
  • PTRACE_ATTACH – dołączenie do istniejącego procesu.
  • PTRACE_SYSCALL – zatrzymywanie przy każdym syscallu.
  • PTRACE_PEEKDATA / PTRACE_POKEDATA – odczyt/zapis pamięci.
  • PTRACE_GETREGS / PTRACE_SETREGS – odczyt/zapis rejestrów.

Przykład: prosty tracer w C

#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    pid_t child = fork();
    if (child == 0) {
        ptrace(PTRACE_TRACEME, 0, NULL, NULL);
        execl("/bin/ls", "ls", NULL);
    } else {
        int status;
        wait(&status);
        ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        while (!WIFEXITED(status)) {
            wait(&status);
            printf("Syscall!\n");
            ptrace(PTRACE_SYSCALL, child, NULL, NULL);
        }
    }
}

Ten kod uruchamia ls i wypisuje komunikat przy każdym syscallu.

Przykłady użycia w praktyce

  • strace – śledzi wszystkie wywołania systemowe procesu.
  • gdb – debugger, który dzięki ptrace może zatrzymywać program i odczytywać pamięć.
  • ltrace – śledzi wywołania funkcji bibliotecznych.
  • Sandboxing – np. ograniczanie procesów w środowisku testowym.

Wyzwania i ograniczenia

  • Bezpieczeństwo – ptrace może być używany do ataków (np. odczytanie pamięci procesu).
  • Wydajność – intensywne śledzenie syscalli spowalnia program.
  • Uprawnienia – wymaga zwykle roota lub odpowiednich capabilities.

Podsumowanie

ptrace to fundament debugowania w Linuksie. Choć na co dzień korzystamy z narzędzi takich jak gdb czy strace, warto wiedzieć, że wszystkie one bazują na tym jednym system callu. Zrozumienie ptrace pozwala tworzyć własne narzędzia, lepiej rozumieć działanie systemu i skuteczniej diagnozować problemy.

About the author

Autor "BIELI" to zapalony entuzjasta otwartego oprogramowania, który dzieli się swoją pasją na blogu poznajlinuxa.pl. Jego wpisy są skarbnicą wiedzy na temat Linuxa, programowania oraz najnowszych trendów w świecie technologii. Autor "BIELI" wierzy w siłę społeczności Open Source i zawsze stara się inspirować swoich czytelników do eksplorowania i eksperymentowania z kodem.