User Mode Linux to bardzo ciekawe podejście do wirtualizacji, jak wszyscy wiemy polega na uruchamianiu "linuxa w linuxie". Takie podejście niesie wiele możliwości, a co dla nas interesujące - rozsądną i rozbudowaną możliwość debugowania. Skoro UML to zwyły proces w nadrzędnym linuxie, możemy go debugować jak zwykłe programy! (prawie)
Zaleca się aby debugowanie przeprowadzać na UML'u działającym w trybie SKAS (niesie to większe możliwości, głównie w kwesti podłączania się z gdb do działającego UML'a). Jak wiadomo, aby to było możliwe, jądro hosta (systemu na którym odpalamy UML'a) musi na to pozwalać. Należy zatem spatchować jądro hosta odpowienim patchem skas, oraz przekompilować to jądro.
wget http://kernel.org/pub/linux/kernel/v2.6/linux-2.6.17.13.tar.gz tar zxvf linux-2.6.17.13.tar.gz cd linux-2.6.17.13Wszędzie poniżej będę zakładał, że jestem w tym folderze (linux-2.6.17.13)
make menuconfig ARCH=um
UML-specific options -> Tracing thread support yes -> Separate Kernel Address Space support yes -> Host filesystem yes Character Devices -> stderr console yes -> Virtual serial line yes -> null channel support yes -> port channel support yes -> pty channel support yes -> tty channel support yes -> xterm channel support yes Block Devices -> Virtual block devices yes Kernel Hacking -> Show timing information on printks yes -> Kernel debugging yes -> Compile the kernel with debug info yes -> Compile the kernel with frame pointers yes -> Enable ptrace proxy yes
wget http://heanet.dl.sourceforge.net/sourceforge/user-mode-linux/root_fs_slack8.1.bz2 bunzip2 root_fs_slack8.1.bz2
mkdir mnt sudo mount root_fs_slack8.1 mnt/ -o loop sudo vim mnt/etc/fstab # zamieniamy odwolanie do "/dev/ubd/0" na "/dev/ubd0", zapisujemy i wychodzimy sudo umount mnt
gdb --args ./linux ubd0=root_fs_slack8.1 gdb# run
gdb# handle SIGSEGV pass nostop noprint gdb# handle SIGUSR1 pass nostop noprintTeraz możemy już kontynuować uruchamianie gdb, ale zanim to zrobimy postawmy jakiegoś breakpointa. Gdy jądro dojdzie do kodu na którym chcemy się zatrzymać - przeskoczymy do gdb:
gdb# break start_kernel gdb# continue
Dodatkowo możemy w dowolnym momencie wykonywania kodu jądra "przeskoczyć" do gdb zatrzymująć się dokładnie w na tej instrukcji, którą wykonywał nasz UML!
kill -INT <pid_umla>gdzie <pid_umla> to najmniejszy z pid'ów procesów o nazwie zawierającej linux w naszym sytemie "hoście".
Najpierw potrzebujemy skompilować moduły i zainstalować je na root filesystemie UML'a.
mount root_fs_slack8.1 mnt -o loop make modules ARCH=um make modules_install INSTALL_MOD_PATH=mnt ARCH=um
Aby działały moduły pod UML'em z jądrem 2.6 potrzebujemy paczki module-init-tools oraz glibc-2.3.6. Instalujemy je oczywiście z poziomu UML'a:
installpkg glibc-2.3.6-i486-6.tgz installpkg module-init-tools-3.2.tar.gz
gdb# break kernel/module.c:1775 gdb# continueTeraz gdy zaladujemy jakis modul (np. poleceniem modprobe) zatrzymamy się na naszym breakpoincie. Dalej wpisujemy co nastepuje:
gdb# print ((struct module *) _mod)->module_core gdb# add-symbol-file HOST_PATH_TO_KO <to_co_zwrocil_print> gdb# break jakas_funkcja_z_modulu gdb# continuePowyzsze operacje powoduja dodanie do gdb informacji o nowo zaladowanym module (o jego kodzie zrodlowym, miejscu gdzie zostal zaladowany do pamieci - czyli niezbedne rzeczy). Napis HOST_PATH_TO_KO zamieniamy na ścieżkę do pliku *.ko odpowiadającego ładowanemu modułowi (ścieżka w systemie plików hosta). Zatrzymamy się jak dojdzie do wykonywania funkcji z modułu na której postawiliśmy break'a.
int stop_here = 1; ... while (stop_here) sleep(1);Gdy uruchomimy UML'a (oczywiście wszystko odbywa się z poziomu gdb) i dojdziemy do "naszego" miejsca w kodzie, system oczywiście wejdzie w pętle nieskończoną (tą którą zdefiniowaliśmy powyżej). Pozostaje teraz tylko "podłączyć" się do tego UML'a wysyłając sygnał INT w standardowy sposób (tak jak to było opisane powyżej) Następnie już z poziomu gdb, do którego przeskoczyliśmy robimy:
gdb# set stop_here = 1I już. Jesteśmy teraz w takiej samej sytuacji jakbyśmy byli po zatrzymaniu się na breakpoincie w danym miejscu (miejscu w którym był nasz nieskończony while).