Debugging jądra, część II

Autor: Kamil Polok

1 KGDB

1.1 Co potrzebujemy?

1.2 Jak to uruchomić?

1.3 Debugging jądra

1.4 Debugging modułów jądra

1.5 Kilka ciekawych uwag na temat KGDB

2 UML - User Mode Linux

2.1 Przykładowe zastosowania

2.2 Działanie

2.3 Jak to uruchomić?

2.4 Debugging jądra UMLowego

2.5 Ciekawostki o UML

1 KGDB



kgdb.linsyssoft.com/index.html
KGDB jest programem umożliwiającym debugging jądra linuxowego na innym komputerze.

1.1 Co potrzebujemy?

  • 2 komputery ze sprawnym portem RS-232
  • kabel nullmodem

1.2 Jak to uruchomić?

W naszym układzie 1 komputer pełni rolę maszyny deweloperskiej (development machine), a drugi maszyny testowej (test machine).
  • Maszyna testowa - na niej działa debugowane jądro, dalej będzie nazywana kam
  • Maszyna deweloperska - na niej działa GDB, dalej będzie nazywana stargate

1.2.1 Stworzenie działającego łącza przez kabel nullmodem

Po połączeniu komputerów testujemy:
root@stargate:~# touch test.txt
root@stargate:~# echo Plik testowy linia 1 >> test.txt
root@stargate:~# echo Plik testowy linia 2 >> test.txt
root@stargate:~# stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
root@kam:~# stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
root@kam:~# cat /dev/ttyS0
Tutaj maszyna kam zaczyna czekać na dane z kabla szeregowego.
root@stargate:~# cat test.txt > /dev/ttyS0
root@stargate:~# echo lol > /dev/ttyS0
Po wpisaniu powyższych komend otrzymamy na maszynie kam:
Plik testowy linia 1

Plik testowy linia 2

lol


Pojawienie się tych napisów świadczy nam o udanym połączeniu.
Oczekiwanie na kolejne dane można przerwać za pomocą CTRL+C

1.2.2 Przygotowanie źródeł jądra i KGDB

KGDB jest łatką na jądro, umożliwiającą debugging za pomocą GDB.

Uwaga: Poniższy przykład instalacji KGDB jest przeprowadzany poprzez kompilację testowanego jądra na maszynie kam, jednak lepszym pomysłem byłoby kompilowanie na maszynie stargate, a następnie skopiowanie jądra na kam, gdyż kod źródłowy i utworzone pliki będą potrzebne na tej maszynie do pracy GDB.

Jądro, na którym prezentowane są przykłady, to 2.6.17.13.
Zakładamy, że jego źródła są zainstalowane w usr/src/linux-2.6.17.13/ oraz istnieje dowiązanie symboliczne do tego katalogu /usr/src/linux
KGDB dla tej wersji jądra można pobrać z repozytorium CVS:
root@kam:~#cvs -d:pserver:anonymous@kgdb.cvs.sourceforge.net:/cvsroot/kgdb login
root@kam:~#cvs -z3 -d:pserver:anonymous@kgdb.cvs.sourceforge.net:/cvsroot/kgdb co -r linux2_6_17 .
Po wykonaniu tych komend w bieżącym katalogu pojawią się katalogi cvs i kgdb-2.
W celu zainstalowania KGDB, wykonujemy poniższe polecenia:
root@kam:~#cp -r kgdb-2 /usr/src/ 
root@kam:~#cd /usr/src/linux-2.6.17.13 
root@kam:~# for p in $(grep patch ../kgdb-2/series);do patch -p1 -si ../kgdb-2/$p;done

1.2.3 Konfiguracja i kompilacja jądra

root@kam:~#make mrproper
root@kam:~#make menuconfig
KGDB spowodowało pojawienie się kilku dodatkowych opcji.
Wchodzimy do Kernel hacking
Zaznaczamy Kernel debugging
W tym momencie pojawia się opcja KGDB: kernel debugging with remote gdb, którą zaznaczamy.
Pojawiło nam się kilka dodatkowych opcji:
KGDB: Console messages through gdb - jeśli zaznaczymy tą opcję, to komunikaty jądra zamiast na swoją konsolę trafiają na konsolę GDB.
Method for KGDB communication - należy wybrać jedną z opcji:
  • KGDB: Use only kernel modules for I/O
  • KGDB: On generic serial port (8250) - KGDB łączy się z GDB przez port szeregowy
  • KGDB: On ethernet - in kernel - KGDB łączy się z GDB przez interfejs sieciowy za pomocą protokołu UDP. Interfejs sieciowy musi wspierać NETPOLL API
KGDB: On Ethernet - moduł umożliwiający komunikację z GDB poprzez interfejs sieciowy.
Simple selection of KGDB serial port - ogranicza wprowadzane parametry portu do prędkości przesyłu i numeru portu.
Debug serial port baud rate - prędkość przesyłu danych przez port.
Serial port number for KGDB - numer portu szeregowego, na którym chcemy się połączyć.
Po zakończeniu konfiguracji można skompilować jądro:
root@kam:~#make bzImage && make modules

1.3 Debugging jądra

Jądro po kompilacji powinno znajdować się zarówno na maszynie testowej jak i tej z GDB.
Na maszynie testowej instalujemy nowe jądro i dodajemy do programu rozruchowego.
Jeśli podczas startu systemu jądro ma się zatrzymać i czekać na połączenie GDB, to przekazujemy mu parametr kgdbwait. Aby z maszyny stargate podłączyć się do debugowanego na kam jądra wykonujemy:
root@stargate:/usr/src/linux-2.6.17.13# cd /usr/src/linux-2.6.17.13
root@stargate:/usr/src/linux-2.6.17.13# stty ispeed 115200 ospeed 115200 -F /dev/ttyS0
root@stargate:/usr/src/linux-2.6.17.13# gdb vmlinux
GNU gdb 6.5
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-slackware-linux"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) target remote /dev/ttyS0
Remote debugging using /dev/ttyS0
breakpoint () at kernel/kgdb.c:1691
1691            atomic_set(&kgdb_setting_breakpoint, 0);
warning: shared library handler failed to enable breakpoint
(gdb)
W tym momencie jesteśmy gotowi do przystąpienia do debuggingu jądra

1.3.1 Krótkie wprowadzenie do GDB

Skoro korzystamy tutaj z GDB, to warto podać parę informacji na temat jego poleceń.
  • continue - (skrót - c) jądro kontynuuje pracę od miejsca, w którym się zatrzymało. Wciśnięcie CTRL+C na maszynie z GDB powoduje zatrzymanie wykonania
  • info breakpoints - wypisuje nam informacje o ustawionych punktach przerwań
  • break plik : linia lub break funkcja- (skrót - b) ustawia punkt przerwania w zadanym pliku w zadanej linii lub na zadanej funkcji. Brak podania linii lub pliku powoduje zastąpienie ich odpowiednio bieżącym plikiem lub numerem linii
  • disable n - usuwa punkt przerwania o numerze n
  • list plik : linia lub list plik : funkcja - (skrót - l) powoduje wypisanie kodu wokół linii/funkcji w danym pliku. Brak podania pliku i linii - patrz wyżej
  • step n - (skrót - l) odpowiednik step into znanego z wielu debuggerów, czyli jeśli w aktualnej linii jest funkcja, to wchodzimy do niej, a jeśli nie, to wykonujemy linię. n oznacza ile razy wykonać polecenie
  • next n - (skrót - n) odpowiednik step over znanego z wielu debuggerów, czyli wykonujemy linię (jeśli linia zawiera wywołanie funkcji, to wykonujemy funkcję bez wchodzenia do niej). n oznacza ile razy wykonać polecenie
  • backtrace - (skrót - bt) - wyświetla stos wywołań
  • print zmienna - (skrót - p) - wyświetla wartość zmiennej
  • set var zmienna=wyrażenie - ustawia wartość zmiennej na wartość wyrażenia

1.3.2 Przykładowa sesja z GDB

(gdb) step 4
[New thread 32768]
90                              status |= action->flags;
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
breakpoint () at kernel/kgdb.c:1691
1691            atomic_set(&kgdb_setting_breakpoint, 0);
(gdb) step 3
handle_IRQ_event (irq=4, regs=0xc02c5fa8, action=0xdff56440) at kernel/irq/handle.c:89
89                      if (ret == IRQ_HANDLED)
(gdb) list
84              if (!(action->flags & SA_INTERRUPT))
85                      local_irq_enable();
86
87              do {
88                      ret = action->handler(irq, action->dev_id, regs);
89                      if (ret == IRQ_HANDLED)
90                              status |= action->flags;
91                      retval |= ret;
92                      action = action->next;
93              } while (action);
(gdb) break 92
Breakpoint 1 at 0xc0129dd2: file kernel/irq/handle.c, line 92.
(gdb) c
Continuing.

Breakpoint 1, handle_IRQ_event (irq=0, regs=0xc02c5f38, action=0xc027f360) at kernel/irq/handle.c:92
92                      action = action->next;
(gdb) info breakpoints
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0xc0129dd2 in handle_IRQ_event at kernel/irq/handle.c:92
        breakpoint already hit 1 time
(gdb) disable 1
(gdb) break
Note: breakpoint 1 (disabled) also set at pc 0xc0129dd2.
Breakpoint 2 at 0xc0129dd2: file kernel/irq/handle.c, line 92.
(gdb) info breakpoints
Num Type           Disp Enb Address    What
1   breakpoint     keep n   0xc0129dd2 in handle_IRQ_event at kernel/irq/handle.c:92
        breakpoint already hit 1 time
2   breakpoint     keep y   0xc0129dd2 in handle_IRQ_event at kernel/irq/handle.c:92
(gdb) re
refresh         remote          restart         restore         return          reverse-search
(gdb) delete
breakpoints  checkpoint   display      mem          tracepoints
(gdb) help delete
Delete some breakpoints or auto-display expressions.
Arguments are breakpoint numbers with spaces in between.
To delete all breakpoints, give no argument.

Also a prefix command for deletion of other GDB objects.
The "unset" command is also an alias for "delete".

List of delete subcommands:

delete breakpoints -- Delete some breakpoints or auto-display expressions
delete checkpoint -- Delete a fork/checkpoint (experimental)
delete display -- Cancel some expressions to be displayed when program stops
delete mem -- Delete memory region
delete tracepoints -- Delete specified tracepoints

Type "help delete" followed by delete subcommand name for full documentation.
Command name abbreviations are allowed if unambiguous.
(gdb) help delete breakpoints
Delete some breakpoints or auto-display expressions.
Arguments are breakpoint numbers with spaces in between.
To delete all breakpoints, give no argument.
This command may be abbreviated "delete".
(gdb) delete breakpoints 1
(gdb) info breakpoints
Num Type           Disp Enb Address    What
2   breakpoint     keep y   0xc0129dd2 in handle_IRQ_event at kernel/irq/handle.c:92
(gdb) bt
#0  handle_IRQ_event (irq=0, regs=0xc02c5f38, action=0xc027f360) at kernel/irq/handle.c:92
#1  0xc0129e4d in __do_IRQ (irq=0, regs=0xc02c5f38) at kernel/irq/handle.c:173
#2  0xc010417c in do_IRQ (regs=0xc02c5f38) at arch/i386/kernel/irq.c:108
#3  0xc0102b16 in common_interrupt () at include/asm/current.h:9
#4  0xdff56440 in ?? ()
#5  0x00000000 in ?? ()
(gdb) l 92
87              do {
88                      ret = action->handler(irq, action->dev_id, regs);
89                      if (ret == IRQ_HANDLED)
90                              status |= action->flags;
91                      retval |= ret;
92                      action = action->next;
93              } while (action);
94
95              if (status & SA_SAMPLE_RANDOM)
96                      add_interrupt_randomness(irq);
(gdb) print retval
$1 = 0
(gdb) print action
$2 = (struct irqaction *) 0xc027f360
(gdb)quit
The program is running.  Exit anyway? (y or n) y
root@stargate:/usr/src/linux-2.6.17.13#

1.4 Debugging modułów jądra

Zwykły GDB nie umożliwia niestety debugowania modułów ładowanych do jądra. Problem ten rozwiązuje specjalnie zmodyfikowany GDB, którego można pobrać ze strony http://kgdb.linsyssoft.com/downloads/gdbmod-2.4.bz2
Po rozpakowaniu i skopiowaniu go do /usr/src/linux-2.6.17.13/ możemy zobaczyć go w akcji:
root@stargate:/usr/src/linux-2.6.17.13# ./gdbmod-2.4 vmlinux
GNU gdb 6.4
Copyright 2005 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

(gdb) target remote /dev/ttyS0
Remote debugging using /dev/ttyS0
breakpoint () at kernel/kgdb.c:1691
1691            atomic_set(&kgdb_setting_breakpoint, 0);
(gdb) set solib-search-path /usr/src/linux-2.6.17.13/drivers/scsi
(gdb) c
Continuing.
[New thread 2113]
[New thread 32768]

Program received signal SIGTRAP, Trace/breakpoint trap.
breakpoint () at kernel/kgdb.c:1691
1691            atomic_set(&kgdb_setting_breakpoint, 0);
(gdb) info sharedlibrary
From        To          Syms Read   Shared Object Library
0xe0ee5000  0xe0eec6d4  Yes         /usr/src/linux-2.6.17.13/drivers/scsi/scsi_mod.ko
(gdb) c
Continuing.
[New thread 2121]

Program received signal SIGTRAP, Trace/breakpoint trap.
breakpoint () at kernel/kgdb.c:1691
1691            atomic_set(&kgdb_setting_breakpoint, 0);
(gdb) info sharedlibrary
No shared libraries loaded at this time.
(gdb) quit
The program is running.  Exit anyway? (y or n) y
root@stargate:/usr/src/linux-2.6.17.13#

1.5 Kilka ciekawych uwag na temat KGDB

  • Istnieje możliwość uruchomienia wszystkiego na jednej maszynie, jeśli posiadamy narzędzia takie jak np. VMware.
  • KGDB nie potrafi obsługiwać punktów przerwań tylko w częściach jądra, które są przez niego używane, czyli np. funkcjach obsługi połączenia szeregowego KGDB
  • Istnieje możliwość debugowania wątków.
  • KGDB może pracować również na maszynie wieloprocesorowej.
  • KGDB od wersji 1.7 wspiera również sprzętowe punkty przerwań związane z wykonaniem, odczytem lub zapisem zawartości spod określonego adresu.

2 UML - User Mode Linux



user-mode-linux.sourceforge.net
UML jest programem umożliwiającym uruchomienie jądra linuxowego jako zwykłego programu w trybie użytkownika. W zasadzie UML jest narzędziem wirtualizacji, które pozwala uruchomić cały system wewnątrz innego systemu.

2.1 Przykładowe zastosowania

  • testowanie oprogramowania bez ryzyka uszkodzenia rzeczywistego systemu
  • tworzenie honeypotów
  • debugowanie jądra

2.2 Działanie

UML jest dołączany do jądra od wersji 2.6.0
UML nie zapewnia nam pełnej wirtualizacji sprzętowej. Jest on przeznaczony tylko do uruchamiania Linuxa w Linuxie. Sam UML jest zaimplementowany jako port Linuxa na wymyśloną architekturę UM. W nomenklaturze związanej z UML często stosuje się 2 terminy:
  • host kernel - jądro systemu, w którym uruchamiamy UML
  • guest kernel - jądro systemu uruchamianego za pomocą UML
Istnieją 2 tryby pracy UMLa:
  • TT - Tracing Thread - starszy tryb pracy UML. Jego cechy:
    • każdy proces w UML posiada odpowiadający mu proces w systemie hosta
    • istnieje specjalny wątek, który za pomocą ptrace przechwytuje wywołania systemowe procesów z UMLa, zeruje je dla systemu hosta i przekazuje do jądra UML
    • wątek śledzący podczas wykonywania przekierowań korzysta z sygnałów, co jest bardzo nieefektywne
    • każdy proces UMLa ma w swojej przestrzeni adresowej kod jądra UML z prawami do zapisu i czytania, przez co procesy z UML mogą wydostać się do systemu hosta, natomiast w przypadku honeypota intruz może wykryć obecność UMLa
  • SKAS - Seperate Kernel Adress Space - ten tryb eliminuje główne problemy trybu TT, wymaga jednak patchowania jądra w systemie hosta Jego cechy:
    • oddzielona przestrzeń adresowa jądra UML i programów uruchamianych dla niego
    • z poziomu hosta widać tylko 4 procesy, z czego jeden to jądro UML, drugi zawiera wszystkie procesy uruchomione dla tego jądra, a pozostałe są związane z obsługą IO

2.3 Jak to uruchomić?

2.3.1 Konfigurowanie jądra

root@stargate:/usr/src/linux-2.6.17.13# make menuconfig ARCH=mu
W Kernel hacking należy zaznaczyć:
  • Enable kernel debugging symbols
  • Enable ptrace proxy
Pozycje te umożliwią nam potem debugowanie jądra.

Pojawiło się nam również dodatkowe menu UML-specific options
W menu tym poza domyślnymi opcjami warto zaznaczyć również:
  • Networking support - obsługa sieci
  • Host filesystem - umożliwia dostęp do plików w systemie hosta
Poza tym w Character devices pojawiły się opcje związane z portami i terminalami, które trzeba zaznaczyć jeśli chcemy z nich korzystać do połączenia się z UMLem

2.3.2 Kompilacja

root@stargate:/usr/src/linux-2.6.17.13# make ARCH=mu
W ten sposób zostało utworzone jądro UMLowe.
Do pracy jest nam jeszcze potrzebny plik zawierający system plików. Ze strony http://uml.nagafix.co.uk/ można pobrać gotowy system plików z jedną z wielu popularnych dystrybucji.

2.3.3 Uruchomienie UML

root@stargate:~/uml# ./linux-2.6.17.13 ubda=Slackware-11-root_fs mem=64M
W powyższym przykładzie zastosowano system plików z dystrybucją Slackware.

2.3.4 Instalowanie modułów jądra

Jeśli przy kompilacji jądro UMLowe miało zaznaczoną opcję Host filesystem, to możemy zainstalować moduły z poziomu samego UMLa:
root@darkstar:~# mkdir modules
root@darkstar:~# mount none modules -t hostfs -o /usr/src/linux-2.6.17.13
root@darkstar:~# cd modules
root@darkstar:~/src# make modules_install
Jeśli natomiast nie mamy tej funkcji w jądrze UML, bądź nie chcemy uruchamiać UMLa, możemy zamontować w systemie hosta system plików UMLa, pod warunkiem, że jądro systemu host ma wkompilowaną funkcję Loopback device support
root@stargate:~/uml# mkdir fs
root@stargate:~/uml# mount Slackware-11-root_fs fs -o loop
root@stargate:~/uml# cd /usr/src/linux-2.6.17.13
root@stargate:~/usr/src/linux-2.6.17.13# make modules_install INSTALL_MOD_PATH=~/uml/fs
root@stargate:~/uml# umount fs
Teraz wystarczy uruchomić UMLa.

2.4 Debugging jądra UMLowego

Ponieważ jądro UMLowe działa jako zwykły program, można je debugować za pomocą GDB lub innego zwykłego debuggera.
Sposobów rozpoczęcia debuggingu jest kilka:
  • uruchomienie UMLa z opcją debug spowoduje uruchomienie terminala z gotowym do pracy GDB
  • wysłanie sygnału USR1 do procesu śledzącego wątki
    root@stargate:~/uml# kill -USR1 pid_procesu_śledzącego
    
  • uruchomienie UMLa za pomocą GDB
    root@stargate:~/uml# gdb
    GNU gdb 6.5
    Copyright (C) 2006 Free Software Foundation, Inc.
    GDB is free software, covered by the GNU General Public License, and you are
    welcome to change it and/or distribute copies of it under certain conditions.
    Type "show copying" to see the conditions.
    There is absolutely no warranty for GDB.  Type "show warranty" for details.
    This GDB was configured as "i486-slackware-linux".
    (gdb) handle SIGSEGV pass nostop noprint
    Signal        Stop      Print   Pass to program Description
    SIGSEGV       No        No      Yes             Segmentation fault
    (gdb) handle SIGUSR1 pass nostop noprint
    Signal        Stop      Print   Pass to program Description
    SIGUSR1       No        No      Yes             User defined signal 1
    (gdb) file linux-2.6.17.13
    Reading symbols from /root/uml/linux-2.6.17.13...done.
    Using host libthread_db library "/lib/libthread_db.so.1".
    (gdb) b start_kernel()
    Breakpoint 1 at 0x804936d
    (gdb) run ubda=Slackware-11-root_fs mem=64M
    

2.5 Ciekawostki o UML

Do ciekawych możliwości UMLa należy mechanizm COW (Copy on Write), który umożliwia nam uruchomienie kilku instancji jądra UML na raz. Instancje te otwierają system plików w trybie tylko do odczytu, a zmiany zapisują do osobnego pliku.