Jasne jest, że debugowanie jądra systemu znacząco się różni od debugowanie zwykłego programu. Jądro nie jest uruchamiane pod kontrolą żadnego innego procesu, więc jego przerywanie, sprawdzanie wartości zmiennych czy wykonywanie instrukcja po instrukcji nie jest możliwe. Ponadto w przypadku błędów w jądrze nie możemy postąpić w zwykły sposób, nie możemy nawet zamknąć systemu, bo jądro może już nie reagować. Powtarzenie przeplotów prowadzących do błędów w przypadku jądra, zależnych od tak wielu czynników jest bardzo ciężkie, dlatego także należy starać się uzyskać maksimum informacji z każdego błędu napotkanego podczas testowanie jądra. W naszej prezentacji przedstawiamy różne techniki i narzędzie wspierające debugowanie jądra.
Istnieje wiele różnych narzędzi i technik umożliwiających odpluskwianie jądra. Omówimy następujące zagadnienia:
Przyjrzymy się najpierw rodzajom błędów, zwłaszcza, że nasze podstępowanie będzie różniło się w zależności od typu błędu. Podstawowe rodzaje błędów jądra to:
Część Kernel Hacking podczas konfigurowania jądra umożliwia wybranie opcji umożliwiających i wspomagających debugowanie jądra. Należy te opcje wybierać jednak tylko wtedy, gdy kompiluje się jądro przeznaczone do testowania, gdyż większość z wymienionych tu opcji spowolnia pracą jądra.
Uwaga: Opisywane poniżej opcje pochodzą z jądra 2.6.17.13, w nowszych jądrach opcji jest jeszcze więcej i część z poniższych nazw została zmieniona. Dla porównania screen z najnowszego jądra
Show timing information on printk (CONFIG_PRINTK_TIME) | dodaje informacje dotyczące czasu wypisania do printk (umożliwia analizę czasu działania i znajdowanie kodu, którego wykonanie zajmuje dużo czasu) |
Magic SysRq key (CONFIG_MAGIC_SYSRQ) | włącza obsługę klawiszy SysRq (p. niżej) |
Kernel debugging (CONFIG_DEBUG_KERNEL) | umożliwia wybranie poniższych opcji |
Kernel log buffer size (16 => 64 Kb, 17 => 128KB) (CONFIG_LOG_BUF_SHIFT) | Rozmiar bufora komunikatów jądra |
Detect Soft Lockups (CONFIG_DETECT_SOFTLOCKUP) | umożliwia wykrywanie "soft lockups", jeżeli następuje praca w trybie jądra dłużej niż 10 sekund, i nie będzie możliwości oddania procesora innym procesom, to zostanie wypisany stos wywołań, ale system pozostanie w stanie lockup |
Collect scheduler statistics (CONFIG_SCHEDSTATS) | do schedulera dołączany jest kod zbierający statystyki dotyczące jego działania, dostępne są one w /proc/schedstat |
Debug slab memory allocations (CONFIG_DEBUG_SLAB) | Włącza sprawdzanie podczas alokacji pamięci - następuje zatruwanie (poisoning) każdy bajt jest ustawiany na 0xa5 przed przekazaniem go procesowi, a przy zwalnianiu ustawiany jest na 0x6b, ułatwie to np. analizę błędów oops |
Mutex debugging, deadlock detection (CONFIG_DEBUG_MUTEXES) | Wykrywanie błędów związanych z zakleszczeniami wywołanymi przez mutexy i operacjami na mutexach |
Spinlock debugging (CONFIG_DEBUG_SPINLOCK) | Umożliwia znajdowanie błędów niezainicjalizowanie spinlocków i innych powszechnych błędów popełnianych podczas użycia spinlocków |
Sleep-inside-spinlock checking (CONFIG_DEBUG_SPINLOCK_SLEEP) | Raportowane są wywołanie funkcji, które mogą powoduję zaśnięcie, podczas posiadania spinlocka |
Compile the kernel with debug info (CONFIG_DEBUG_INFO) | jądro będzie zawierało informacje potrzebne do debugowania. Zaznaczenie tej opcji jest konieczne jeżeli będzie się korzystać z crash, kgdb, LKCD, gdb itp. |
Debug filesystem (CONFIG_DEBUG_FS) | Udostępnia debugfs, wirtualny system plików, stosowany do debugowania |
Debug VM (CONFIG_DEBUG_VM) | Włącza debugowanie obsługi pamięci wirtualnej |
Compile the kernel with frame pointers (CONFIG_FRAME_POINTER) | Dodaje pożyteczne informacje podczas debugowania z użyciem zewnętrznych debuggerów |
Compile the kernel with frame unwind information (CONFIG_UNWIND_INFO) | Dodaje pożyteczne informacje podczas debugowanie z użyciem zewnętrznych debuggerów |
Check for stack overflows (CONFIG_DEBUG_STACKOVERFLOW) | Powoduje wypisywanie ostrzeżeń, gdy zostaje mało miejsca na stosie, umożliwia znajdowanie błędów przepełnienia stosu |
Write protect kernel read-only data structures (CONFIG_DEBUG_RODATA) | Zaznacza dane kernel tylko do odczytywania jako tylko do odczytywania w tablicach stron - umożliwia wykrywanie przypadkowych zapisań do tych części danych |
Forced module unloading (CONFIG_MODULE_FORCE_UNLOAD) | umożliwia zmuszanie jądra do usuwania modułów |
Funkcja printk jest odpowiednikiem printf i umożliwia wypisywanie komunikatów. Można z niej korzystać w trybie jądra i ustalać priorytety komunikatów, który sterują ich zachowaniem. Należy zwrócić uwagę, że nie ma przecinka między priorytetem komunikatu a nim samym (tzn. priorytet jest dołączany na początku komunikatu)
Funckja printk różni się od printf w paru szczegółach - printk nie obsługuje liczb zmiennoprzecinkowych
KERN_EMERG | 0 | /* system is unusable */ |
KERN_ALERT | 1 | /* action must be taken immediately */ |
KERN_CRIT | 2 | /* critical conditions */ |
KERN_ERR | 3 | /* error conditions */ |
KERN_WARNING | 4 | /* warning conditions */ |
KERN_NOTICE | 5 | /* normal but significant condition */ |
KERN_INFO | 6 | /* informational */ |
KERN_DEBUG | 7 | /* debug-level messages */ |
W przypadku nie podania priorytetu komunikatu wartość zostanie ustawiona na default_message_level (p. poniżej)
Zachowaniem printk steruje plik /proc/sys/kernel/printk. Przyjrzyjmy się jego zawartości:
[ar237576@green06 ~]$ cat /proc/sys/kernel/printk 1 4 1 7
Cztery wartości w pliku printk to: console_loglevel, default_message_loglevel, minimum_console_level i default_console_loglevel. Wartości te wpływają na zachowanie printk() podczas wypisywania lub logowania komunikatów błędów. Komunikaty o priorytecie wyższym niż console_loglevel będą wypisywane na konsoli. Komunikaty bez jawnego priorytetu będą wypisywane z priorytetem default_message_level. minimum_console_loglevel jest najmniejszą (tj. najwyższą) wartością, którą można ustawić jako console_loglevel. default_console_loglevel jest domyślną wartością dla console_loglevel.
Uruchamiając klogd z opcją -c można ustawić wartość console_loglevel
Aby zapobiec przeciążeniu systemu przez zalew komunikatów można użyć funkcji printk_ratelimit. Kontroluje ona liczbę nadchodzących komunikatów i zwraca 0 w sytuacji, gdy jest ich zbyt wiele. Umieszcza się ją przed printk:
if (printk_ratelimit()) printk(KERN_DEBUG "Niezbyt interesujący debug\n");
Jej zachowanie kontroluje się poprzez następujące pliki:
/proc/sys/kernel/printk_ratelimit - minimalny średni odstęp między komunikatami
/proc/sys/kernel/printk_ratelimit_burst - najdłuższa dopuszczalna seria komunikatów
Zobaczmy prosty moduł obrazujący działanie printk:
#include <linux/module.h> MODULE_LICENSE("GPL"); int init_module(void) { printk(KERN_DEBUG "DEBUG\n"); printk(KERN_INFO "INFO\n"); printk(KERN_NOTICE "NOTICE\n"); printk(KERN_WARNING "WARNING\n"); printk(KERN_ERR "ERROR\n"); printk(KERN_ALERT "ALERT\n"); printk(KERN_EMERG "EMER\n"); return 0; } void cleanup_module(void){}
Zobaczmy jak printk zareaguje na zmianę wartości w pliku /proc/sys/kernel/printk
daroon:~/SO/# insmod printk.ko DEBUG INFO NOTICE WARNING ERROR ALERT EMER daroon:~/SO# rmmod printk.ko daroon:~/SO# echo "5 5 1 7" >/proc/sys/kernel/printk daroon:~/SO# insmod printk.ko WARNING ERROR ALERT EMEROczywiście wszystkie wiadomości zostały w zapisane w logach:
daroon:~/SO# cat /var/log/kern.log | tail -n 14 Nov 22 20:14:47 daroon kernel: DEBUG Nov 22 20:14:47 daroon kernel: INFO Nov 22 20:14:47 daroon kernel: NOTICE Nov 22 20:14:47 daroon kernel: WARNING Nov 22 20:14:47 daroon kernel: ERROR Nov 22 20:14:47 daroon kernel: ALERT Nov 22 20:14:47 daroon kernel: EMER Nov 22 20:14:58 daroon kernel: DEBUG Nov 22 20:14:58 daroon kernel: INFO Nov 22 20:14:58 daroon kernel: NOTICE Nov 22 20:14:58 daroon kernel: WARNING Nov 22 20:14:58 daroon kernel: ERROR Nov 22 20:14:58 daroon kernel: ALERT Nov 22 20:14:58 daroon kernel: EMER
Sprawdźmy jeszcze, jak działa printk_ratelimit
#include <linux/module.h> MODULE_LICENSE("GPL"); int init_module(void) { int i=0; while (printk_ratelimit()){ printk(KERN_DEBUG "%d\n",i); i++; } return 0; } void cleanup_module(void){}i obejrzyjmy logi:
daroon:~/SO# cat /proc/sys/kernel/printk_ratelimit 5 daroon:~/SO# cat /proc/sys/kernel/printk_ratelimit_burst 10 daroon:~/SO# insmod printk2.ko daroon:~/SO# cat /var/log/kern.log | tail -n 10 Nov 22 20:29:30 daroon kernel: 0 Nov 22 20:29:30 daroon kernel: 1 Nov 22 20:29:30 daroon kernel: 2 Nov 22 20:29:30 daroon kernel: 3 Nov 22 20:29:30 daroon kernel: 4 Nov 22 20:29:30 daroon kernel: 5 Nov 22 20:29:30 daroon kernel: 6 Nov 22 20:29:30 daroon kernel: 7 Nov 22 20:29:30 daroon kernel: 8 Nov 22 20:29:30 daroon kernel: 9
Printk zapisuje komunikaty do cyklicznego bufora, który ma długość LOG_BUF_LEN. Następnie budzone są procesy oczekujące na komunikaty - czytające z /proc/kmsg lub oczekujące na wywołaniu syslog. Obydwa sposoby dostępu są podobne - różnicą jest to, że odczytanie z /proc/kmsg powoduje pobranie danych z bufora, a syslog może zwrócić dane umożliwiając innym procesom odczytanie tego samego z bufora (uruchomienie klogd z opcją -s powoduje korzystanie z syslog, w przeciwnym wypadku klogd korzysta z /proc/kmsg). Gdy bufor zostanie zapełniony, zostaje wypełniany od początku - a stare komunikaty zostają nadpisane.
Jeśli klogd jest uruchomiony odzyskuje komunikaty z bufora i przekazuje je do syslogd, który je przetwarza zgodnie z ustawieniami w /etc/syslog.conf. Jeśli klogd nie jest uruchomiony - dane pozostają w buforze, dopóki ktoś inny ich nie odczyta lub nie zostaną nadpisane po zapętleniu bufora. Możemy rzecz jasna samemu odczytywać komunikaty z bufora korzystając z dmesg albo po prostu wykonując cat /proc/kmsg. Należy wówczas zamknąć demona klogd (lub go nie uruchamiać), by nie konkurował z naszym procesem o wiadomości.
logd można uruchomić z opcją -f plik i wówczas zapisywać będzie komunikaty do pliku. Jeśli klogd i syslogd są uruchomione to komunikaty wypisywane przez printk znajdą się w pliku wyspecyfikowanym w syslog.config (np. /var/log/kern.log ). Zobaczmy fragment przykładowego syslog.conf
*.=crit;kern.none /var/adm/critical kern.* /var/adm/kernel kern.crit /dev/console kern.info;kern.!err /var/adm/kernel-info
Z tego co powiedzieliśmy wynika już, że duża ilość wywołań printk znacznie spowalnia pracę systemu - syslogd po każdym wypisaniu komunikatu wykonuje zapisanie go na dysk. Musi tak być, bo w przypadku błędu logi były kompletne. By zmienić to zachowanie należy wpis w syslog.conf poprzedzić znakiem -.
Oto schemat ilustrujący standardowy przepływ komunikatów:
W przypadku "soft lockup", tzn. zawieszeniu pracy działania przy reagowaniu na klawiaturę, jądra udostępnia mechanizm pozwalający na kontrolę pracy systemu. Po włączeniu odpowiednej opcji podczas kompilacji jądra kombinacja klawiszy Alt, SysRq i litera spowoduje wykonanie jednej z akcji przez system. (Uwaga: poniższe układy działają dla układu klawiatury qwerty)
Set the console log level, which controls the types of kernel messages that are output to the console | 0-9 |
Immediately reboot the system, without unmounting partitions or syncing | b |
Reboot kexec and output a crashdump | c |
Send the SIGTERM signal to all processes except init (PID 1) | e |
Call oom_kill, which will kill a process that is consuming all available memory. | f |
Output a terse help document to the console | h |
Send the SIGKILL signal to all processes except init | i |
Kill all processes on the current virtual console (Can be used to kill X and svgalib programs, see below) | k |
Send the SIGKILL signal to all processes, including init | l |
Output current memory information to the console | m |
Shut off the system | o |
Output the current registers and flags to the console | p |
Switch the keyboard from raw mode, the mode used by programs such as X11 and svgalib, to XLATE mode | r |
Sync all mounted filesystems | s |
Output a list of current tasks and their information to the console | t |
Remount all mounted filesystems in read-only mode | u |
Output Voyager SMP processor information | v |
Należy zauważyć, że wybranie samej opcji b - restart bez odmontowania i synchronizacji jest niebezpieczne. Zalecanym sposobem postępowania po utracie kontroli nad systemem jest:
Regulować tym czy Magic SysRq jest włączone może poprzez plik /proc/sys/kernel/sysrq, w którym znajduje się 1, gdy jest one aktywne, a 0 w przeciwnym przypadku. Tak więc:
echo 0 > /proc/sys/kernel/sysrqwyłącza obsługę sysrq,
echo 1 > /proc/sys/kernel/sysrqwłącza obsługę sysrq.
Możliwe jest także skorzystanie z Magic SysRq poprzez trigger (/proc/sysrq-trigger)
echo t > /proc/sysrq-trigger
Zobaczmy co wypisze SysRq, gdy poprosimy o informacje o pamięci. Pamiętajmy, że przy wypisywanie korzysta z printk, tak więc zobaczymy jedynie te komunikaty, które mają odpowiedni priorytet
SysRq : Show Memory Mem-info: DMA per-cpu: CPU 0: Hot: hi: 0, btch: 1 usd: 0 Cold: hi: 0, btch: 1 usd: 0 Normal per-cpu: CPU 0: Hot: hi: 186, btch: 31 usd: 159 Cold: hi: 62, btch: 15 usd: 11 Active:77913 inactive:40999 dirty:8 writeback:0 unstable:0 free:1910 slab:6032 mapped:13772 pagetables:642 bounce:0 DMA free:2252kB min:88kB low:108kB high:132kB active:8312kB inactive:2404kB present:16256kB pages_scanned:0 all_unreclaimable? no lowmem_reserve[]: 0 492 492 Normal free:5388kB min:2792kB low:3488kB high:4188kB active:303340kB inactive:161592kB present:503876kB pages_scanned:0 all_unreclaimable? no lowmem_reserve[]: 0 0 0 DMA: 11*4kB 8*8kB 6*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 1*2048kB 0*4096kB = 2252kB Normal: 1*4kB 39*8kB 21*16kB 22*32kB 7*64kB 2*128kB 3*256kB 1*512kB 0*1024kB 1*2048kB 0*4096kB = 5388kB Swap cache: add 0, delete 0, find 0/0, race 0+0 Free swap = 2096440kB Total swap = 2096440kB Free swap: 2096440kB 131056 pages of RAM 0 pages of HIGHMEM 1886 reserved pages 74114 pages shared 0 pages swap cached 8 pages dirty 0 pages writeback 13772 pages mapped 6032 pages slab 642 pages pagetables
W linuksie dostępne są strace, ptrace i ltrace wspomagające debugowanie programów. Umożliwiają one śledzenie wywołań funkcji systemowych i funkcji bibliotecznych - dzięki temu mamy dokładniejszy wgląd w to jak programy komunikują się z jądrem.
strace umożliwia śledzenie wywołań funkcji systemowych i sygnałów otrzymywanych przez program. Niektóre z opcji, które można podać to:
-o nazwa_pliku | przekierowanie wyjścia do pliku (w przeciwnym wypadku informacje zostają wypisywane na standardowy strumień diagnostyczny) |
-f | śledzenie także procesów potomnych utworzonych poprzez fork (śledzenie jest rozpoczynane dopiero, gdy znane jest pid dziecka) |
-ff | gdy włączona jest opcja -o zapisuje raport z każdego procesów do oddzielniego pliku (nazwa.pid) |
-F | próbuje także śledzić vforka |
-i | wypisuje wskaźnik instrukcji |
-tt | wypisanie na początku czasu wywołania (z dokładnością do mikrosekund) |
-T | wypisanie na końcu czasu wykorzystanego przez wywołanie |
-p pid | przyłączenie się do działającego procesu (może zostać przerwane przez CTRL-C, wtedy strace odłączy się, a proces będzie kontynuować działanie) |
-e trace=set | śledzenie tylko zbioru wywołań np. trace=open,close,read,write |
-e trace=file | śledzenie wywołań, które jako argument przyjmują plik |
-e trace=process | śledzenie wywołań, które dotyczą zarządzania procesami (fork, wait, exec) |
Można podać także inne nazwy grup: network, signal, ipc, desc..
ltrace jest bardzo podobny do strace, ale umożliwia śledzenie wywołań funkcji bibliotecznych
Zaletą obydwu programów jest fakt, że nie musimy posiadać źródeł programów, by móc śledzić wykonywane przez nie wywołania. Obydwa programy zazwyczaj generują dużą liczbę linii. Dlatego też generowane przez nie wyjścia nie powinny być czytane, a jedynie przeszukiwane.
W przypadku strace format w jakim wypisywane są wywołania funkcji systemowych to:
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
Sygnały otrzymywane przez program są wypisywane w następującej postaci (sleep został przerwany przez SIGINT):
adam@daroon:~/SO$ strace -o plik sleep 100 adam@daroon:~/SO$ cat plik | tail -n 2 --- SIGINT (Interrupt) @ 0 (0) --- +++ killed by SIGINT +++
[ar237576@students ~]$ strace -o plik echo "Witaj strace" Witaj strace [ar237576@students ~]$ cat plik execve("/bin/echo", ["echo", "Witaj strace"], [/* 49 vars */]) = 0 brk(0) = 0x804d000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=216482, ...}) = 0 mmap2(NULL, 216482, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf7f05000 close(3) = 0 open("/lib/libc.so.6", O_RDONLY) = 3 read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\2e\1\0004\0\0\0"..., 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1266080, ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7f04000 mmap2(NULL, 1275472, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf7dcc000 mmap2(0xf7efe000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x131) = 0xf7efe000 mmap2(0xf7f01000, 9808, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf7f01000 close(3) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7dcb000 set_thread_area({entry_number:-1 -> 12, base_addr:0xf7dcb6c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 open("/dev/urandom", O_RDONLY) = 3 read(3, "\215D\23\32", 4) = 4 close(3) = 0 mprotect(0xf7efe000, 4096, PROT_READ) = 0 mprotect(0xf7f54000, 4096, PROT_READ) = 0 munmap(0xf7f05000, 216482) = 0 brk(0) = 0x804d000 brk(0x806e000) = 0x806e000 fstat64(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 30), ...}) = 0 mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0x1000) = 0xf7f39000 write(1, "Witaj strace\n", 13) = 13 close(1) = 0 munmap(0xf7f39000, 4096) = 0 close(2) = 0 exit_group(0) = ?
W jądrze dostępne są makra umożliwiające zgłoszenie błędu i sprawdzenie warunku (i w przypadku jego fałszywości zgłoszenie błędu). Są to odpowiednio BUG() i BUG_ON(warunek).
#define BUG() do { \ printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __FUNCTION__); \ panic("BUG!"); \ } while (0) #define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0)
Zanim opowiemy o oopsie, przyjrzyjmy się najpierw ustawieniom regulującym jak zachowa się kernel po błędzie:
/proc/sys/kernel/panic
Plik panic umożliwia dostęp (odczyt i zapis) do zmiennej jądra panic_timeout. Jeśli jest to zero, jądro będzie się zapętlać podczas paniki; jeśli wartość niezerowa, to określa liczbę sekund, po której jądro powinno się automatycznie przeładować.
/proc/sys/kernel/panic_on_oops
Plik ten kontroluje zachowanie jądra, kiedy wystąpi oops. Jeśli ten plik zawiera 0, to system próbuje kontynuować operację. Jeśli zawiera 1, to system czeka parę sekund (aby dać demonowi klogd czas na zapisanie wyjęcia z oops), a następnie panikuje. Jeżeli wartość w pliku /proc/sys/kernel/panic również jest niezerowa, to nastąpi restart komputera.
Oto przykładowy oops i moduł, który go wygenerował
#includeMODULE_LICENSE("GPL"); int init_module(void) { int* i=0; *i=5; return 0; } void cleanup_module(void){}
BUG: unable to handle kernel NULL pointer dereference at virtual address 00000000 printing eip: e0856002 *pdpt = 00000000165a0001 *pde = 0000000000000000 Oops: 0002 [#1] Modules linked in: oops_mod ipv6 n_hdlc ppp_synctty ppp_generic slhc ppdev lp ac battery nls_iso8859_1 ntfs fuse dm_snapshot dm_mirror dm_mod loop snd_mpu401 snd_mpu401_uart snd_seq_dummy snd_seq_oss snd_seq_midi snd_seq_midi_event snd_seq tsdev snd_intel8x0 snd_ac97_codec ac97_bus snd_pcm_oss snd_mixer_oss snd_rawmidi snd_pcm snd_seq_device parport_pc parport snd_timer analog gameport serio_raw snd i2c_sis630 button i2c_sis96x psmouse sis_agp firmware_class pcspkr evdev usbatm shpchp soundcore snd_page_alloc i2c_core rtc agpgart pci_hotplug ext3 jbd ide_cd cdrom ide_disk ohci_hcd sis5513 generic floppy sis900 mii ide_core usbcore thermal processor fan CPU: 0 EIP: 0060:[] Not tainted VLI EFLAGS: 00010246 (2.6.23.1 #1) EIP is at init_module+0x2/0xd [oops_mod] eax: 00000000 ebx: d54a094c ecx: 00000000 edx: ffffffff esi: d54a0800 edi: d54a096c ebp: e0856300 esp: d5447edc ds: 007b es: 007b fs: 0000 gs: 0033 ss: 0068 Process insmod (pid: 3682, ti=d5446000 task=ddcb6f10 task.ti=d5446000) Stack: c0130dcc 00000000 00000000 00000001 ffffffff 000031f5 000003e8 e0856214 e0ba7204 e0ba7200 ddcb6f10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 Call Trace: [<c0130dcc>] sys_init_module+0x11b0/0x1286 [<c0103c26>] sysenter_past_esp+0x5f/0x89 ======================= Code: <c7> 05 00 00 00 00 05 00 00 00 c3 c3 90 90 04 00 00 00 14 00 00 00 EIP: [<e0856002>] init_module+0x2/0xd [oops_mod] SS:ESP 0068:d5447edc
Oops zawiera między innymi:
klogd będzie próbował przetłumaczyć adresy jądra na symbole z /proc/kallsyms i /boot/System.map. klogd jednak ich nie aktualizuje. Można temu zaradzić na dwa sposoby - uruchomić klogd z opcją -p powodującą ładowanie symboli przy załadowaniu modułu lub wysłać do klogd sygnał SIGUSR1 lub SIGUSR2. /proc/kallsyms jest uaktualniany dynamicznie w trakcie ładowania modułów, System.map zawiera stałe dla jądra. Możemy w nich ręcznie odszukać znaczenie adresów i znaleźć offset kolejnej instrukcji, która miała być wykonana
daroon:~/SO# cat /proc/kallsyms | less e0ba7080 ? __mod_vermagic5 [oops_mod] e0856300 d __this_module [oops_mod] e085600d t cleanup_module [oops_mod] e0856000 t init_module [oops_mod] 00000000 a af_inet6.c [ipv6]
Możemy teraz zdeasemblować nasze pliki i znaleźć instrukcję, która spowodowała błąd. Możemy to zrobić dzięki znalezionemu wcześniej offsetowi
daroon:~/SO# objdump -S -d oops_mod.o oops_mod.o: file format elf32-i386 Disassembly of section .text: 00000000: int init_module(void) { int* i=0; *i=5; return 0; } 0: 31 c0 xor %eax,%eax MODULE_LICENSE("GPL"); int init_module(void) { int* i=0; *i=5; 2: c7 05 00 00 00 00 05 movl $0x5,0x0 9: 00 00 00 return 0; } c: c3 ret 0000000d : void cleanup_module(void){} d: c3 ret
tar -xf linux-xx.xx.xx.tar.bz2
make defconfig ARCH=um
make menuconfig ARCH=um
, wszystko powinno jednak działać z domyślnychmake linux ARCH=um
i tu jest zwykle problem ...
make modules ARCH=um
, nie ma ich zbyt wielu, nie wszystkie moduły dostępne na i386 będą dostępne do wyboru w UML make clean ARCH=um
lub make mrproper ARCH=um
, to ostatnie przywraca też zmiany w pliku konfiguracyjnym. dd if=/dev/zero of=plik bs=rozmiar_bloku count=ilosc_bloków
potem wystarczy założyć na nim system plików odpowiedniego typu (oczywiście obsługiwanego przez jądro) dla ext2 mkfs.ext2 plik
. Potem trzeba jeszcze umieścić tam odpowiednie pliki i katalogi. Trzeba dodać odpowiedni plik /etc/fstub w którym dodamy wpis typu /dev/ubda / auto defaults 1 1
, opisujący urządzenie, gdzie widoczny będzie obraz dysku(sudo) mount -o loop root_fs katalog/
, w ten sposób uzyskujemy dostęp do obrazu.
Aby zainstalować moduły wykonujemy z głównego katalogu źródeł jądra make modules_install INSTALL_MOD_PATH=ścieżka_do_katalogu_z_zainstalowanym_obrazem ARCH=um
umount katalog
), jeśli dwa systemy używałyby go jednocześnie mógłby łatwo być uszkodzony wewnętrzny system plików obrazu. mount none /host -t hostfs
zamontuje katalog hosta "/" w folderze /host w UMLmount none /host -t hostfs -o katalog
gdb linux
argumenty przekazywane do UML-a nie należy wpisywać w linii poleceń, ale jako argumenty polecenia w gdb - runatt pid_UML
handle SIGSEGV pass nostop noprint
.
Sygnał USR1 był kiedyś używany przez UML do wewnętrznej komunikacji,
stąd polecenie to było istotne. Przy ustawieniach z tej prezentacji
pozostawiamy obsługę sygnałów niezmienioną. gdb program plik_core
. Aby włączyć zapisywanie plików core należy wpisać w bashu ulimit -c max_rom_w_MB
. Poza tym pliki core zapisywane są w formacie ELF, można więc próbować je odczytać przy pomocy objdump i readelf. ddd linux
TARGET = nowymodul MAIN_OBJ = nowymodul.o OBJS = $(MAIN_OBJ) MDIR = drivers/misc ROOTF = /home/kornel/Desktop/soUML/kat #ścieżka do zamontowanego obrazu pliku EXTRA_CFLAGS = -DEXPORT_SYMTAB -g # na wszelki wypadek chcemy wkompilowac informacje dla debuggera CURRENT = 2.6.22 #zamiast uname -r, KDIR = $(ROOTF)/lib/modules/$(CURRENT)/build # dodamy tą ścieżkę na początek PWD = $(shell pwd) DEST = $(ROOTF)/lib/modules/$(CURRENT)/kernel/$(MDIR) obj-m +=$(OBJS) default: make -C $(KDIR) SUBDIRS=$(PWD) modules $(MAIN_OBJ): $(OBJS) $(LD) $(LD_RFLAG) -r -o $@ $(OBJS) install: sudo cp -v $(TARGET).ko $(DEST) #do pisania po obrazie potrzeba uprawnień clean: -rm -f *.o *.ko .*.cmd .*.flags *.mod.c -include $(KDIR)/Rules.makeMożna zauważyć, że po kompilacji i instalacji modułów jądra zostały utworzone linki do dobrych źródeł jądra w katalogu root_fs/lib/modules/wersja_jadra. Korzystamy z tego przy kompilacji.
make ARCH=um
potem make install
WARNING: vmlinux(.got+0x8858084): Section mismatch: reference to .init.text: ...
, jednak wydaje się ono być niegroźne.depmod -a
*(struct module *)_mod
wybrać displayadd-symbol-file ścieżka_do_pliku_modul_ko_na_hoście powyższy_adres
wget ftp://oss.sgi.com/projects/kdb/download/v4.4/kdb-v4.4-2.6.17-common-1.bz2 wget ftp://oss.sgi.com/projects/kdb/download/v4.4/kdb-v4.4-2.6.17-i386-1.bz2 bunzip2 kdb-v4.4-2.6.17-common-1 bunzip2 kdb-v4.4-2.6.17-i386-1 cd linux-2.6.17.13/ patch -p1 < ../kdb-v4.4-2.6.17-common-1 patch -p1 < ../kdb-v4.4-2.6.17-i386-1 make linuxPotem pozostaje dodać odpowiednie rzeczy do
/boot
i zrobić update-grub
.wget http://kgdb.linsyssoft.com/downloads/kgdb-2/linux-2.6.15.5-kgdb-2.4.tar.bz2 tar -jxvf linux-2.6.15.5-kgdb-2.4.tar.bz2 cd linux-2.6.15.6/ patch -p1 < ../linux-2.6.15.5-kgdb-2.4/core-lite.patch patch -p1 < ../linux-2.6.15.5-kgdb-2.4/core.patch patch -p1 < ../linux-2.6.15.5-kgdb-2.4/i386-lite.patch patch -p1 < ../linux-2.6.15.5-kgdb-2.4/i386.patch patch -p1 < ../linux-2.6.15.5-kgdb-2.4/8250.patch make menuconfig make
---
zamiast [*]
). Warto zaznaczyć opcję "Console messages through gdb", co pozwoli czytać komunikaty debuggowanego jądra od razu w gdb (chyba że tego nie chcemy), a także "simple selection of KGDB serial port" i ustawić numer portu na 0. Inne ustawienia są opcjonalne.arch/i386/boot/bzImage
do /boot
na maszynie testowej (ja po prostu sklonowałem sobie wirtualną maszynę, pod linuxem chyba można podmontować system plików hosta, pozostaje też przesyłanie przez ssh)make linux-kpkg
) i zainstalować go na testowej/boot/grub/menu.lst
(lub odpowiednio dla lilo), dodając nowe jądro z parametrem kgdbwait
gdb ./vmlinux
i połączyć go z łączem maszyny testowej przy pomocy komendy target remote