Pentru rezolvarea laboratorului, vom lucra în același director din care pornim mașina virtuală (~/so2/linux/tools/labs
).
Pașii de rezolvare sunt următorii:
Scheletul de laborator este generat din sursele din directorul tools/labs/templates
. Putem genera scheletele pentru toate laboratoarele folosind următoarea comanda:
tools/labs $ make skels
Pentru a genera scheletul pentru un singur laborator, vom folosi variabila de mediu LABS
:
tools/labs $ make clean tools/labs $ LABS=<lab name> make skels
kernel_modules
.
Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea <lab_name>/<task_name>
variabilei LABS
.
tools/labs/skels
.
Comanda make build
compilează toate modulele din directorul skels
.
student@eg106:~/so2/linux/tools/labs$ make build echo "# autogenerated, do not edit " > skels/Kbuild echo "ccflags-y += -Wno-unused-function -Wno-unused-label -Wno-unused-variable " >> skels/Kbuild for i in ./kernel_modules/9-kdb ./kernel_modules/7-list-proc ./kernel_modules/3-error-mod ./kernel_modules/1-2-test-mod ./kernel_modules/5-oops-mod ./kernel_modules/6-cmd-mod ./kernel_modules/8-kprobes ./kernel_modules/4-multi-mod; do echo "obj-m += $i/" >> skels/Kbuild; done ...
Putem copia modulele generate pe mașina virtuală folosind target-ul copy
al comenzii make, atunci când mașina virtuală este oprită.
student@eg106:~/so2/linux/tools/labs$ make copy student@eg106:~/so2/linux/tools/labs$ make boot
Alternativ, putem copia fișierele prin scp
, pentru e evita repornirea mașinii virtuale. Pentru detalii despre folosirea interacțiunea prin rețea cu mașina virtuală citiți Interacțiunea cu mașina virtuală.
Modulele generate sunt copiate pe mașina virtuală în directorul /home/root/skels/<lab_name>/<task_name>
.
root@qemux86:~/skels/kernel_modules$ ls 1-2-test-mod 3-error-mod 5-oops-mod 6-cmd-mod 7-list-proc 8-kprobes 9-kdb root@qemux86:~/skels/kernel_modules$ ls 1-2-test-mod/ hello_mod.ko
După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU (sau în minicom
) pentru a încărca și descărca modulul de kernel:
root@qemux86:~# insmod skels/<lab_name>/<task_name>/<module_name>.ko root@qemux86:~# rmmod skels/<lab_name>/<task_name>/<module_name>.ko
Ctrl+Shift+t
. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:
~/so2/linux/tools/labs/skels
.~/so2/linux/tools/labs
.student@asgard:~$ nc -l -p 6000 -u
Term search: Folosiți cscope
sau LXR pentru a localiza în sursele nucleului Linux locul de definiție al următoarelor simboluri:
module_init
și module_exit
. Ce realizează cele două macro-uri? Ce reprezintă init_module
respectiv cleanup_module
?ignore_loglevel
. La ce este folosită această variabilă?
cscope
este posibil să nu fie bine obținuți indecși de căutare de simboluri. Pentru a regenera fișierele de indexare folosiți comanda de mai jos în codul sursă al nucleului:
make ARCH=x86 cscope
cscope
, va trebui să vă aflați în directorul so2/linux/
din directorul home al utilizatorului student
.
struct module
folosiți comanda
vim -t module
sau, în Vim, construcția
:cs f g module
vim -t task_struct
. Sau, dacă ați deschis Vim și vreți ulterior să căutați un simbol după nume, puteți folosi comanda :cscope find g <symbol_name>
(unde <symbol_name>
este numele simbolului.
Dacă există mai multe rezultate (de obicei există) vă puteți deplasa între ele folosind F6
și F5
(:cnext
și :cprev
) sau deschizând o subfereastră nouă cu rezultatele, folosind :copen
. Ca să închideți subfereastra folosiți comanda :cclose
.
Dacă ați găsit mai multe match-uri și dacă ați deschis o subfereastră cu toate match-urile (folosind :copen
) și dacă sunteți în căutarea unui simbol de tip structură, este indicat să căutați în subfereastră (folosind /
– slash) caracterul {
(acoladă deschisă). Este locul în care este definit simbolul de tip structură căutat.
La fel, dacă ați găsit mai multe match-uri și dacă ai deschis o subfereastră cu toate match-urile (folosind :copen
) și dacă sunteți în căutarea locului de definiție a unui macro, este indicat să căutați în subfereastră (folosind /
– slash) după șirul define
(șirul care indică locul de definiție a unui macro).
cscope
parcurgeți secțiunea cscope din primul laborator.
Pentru a putea lucra cu modulele de kernel, vom realiza următorii pași, descriși și mai sus în pagină:
LABS=kernel_modules make skels
în directorul tools/labs
. Această comandă copiază fișierele necesare laboratorului în directorul tools/labs/skels
.
3-error-mod
. Puteți evita problema ștergând directorul skels/kernel_modules/3-error-mod/
.
Vom rula, în directorul tools/labs
, comanda
make build
. Această comandă compilează modulele din toate exercițiile din laborator.
make copy
.~/so2/linux/tools/labs/
) comandamake boot
Ne propunem să testăm pe mașina virtuală modulul din directorul 1-2-test-mod/
din scheletul laboratorului. Urmăriți fișierele din subdirector.
Urmați pașii de mai sus pentru a compila și copia modulele pe mașina virtuală, apoi testați modulul hello_mod.ko
din directorul /home/root/skels/kernel_modules/1-2-test-mod
.
Apoi urmați pașii uzuali în folosirea unui modul de kernel:
dmesg
.
La descărcarea unui modul din kernel poate fi precizat doar numele modulului (fără extensie).
Urmăriți consola mașinii virtuale. De ce mesajele nu au fost afișate direct la consola mașinii virtuale?
Inspectați fișierul cod sursă. Modificați fișierul cod sursă astfel încât mesajele să fie afișate direct la consolă.
printk
.
Pentru aceasta va trebui să editați opțiunile de bootare din fișierul tools/labs/qemu/Makefile
, și să adăugați opțiunea de boot ignore_loglevel
pe linia care începe cu append root...
Compilați modulul. Încărcați și apoi descărcați modulul din kernel. Mesajele sunt afișate la consola mașinii virtuale. Dacă ați deschis pseudo-terminalul indicat de qemu (pentru char device redirected to /dev/pts/19 (label virtiocon0)
folosiți minicom -D /dev/pts/19
) sau, mai simplu, link-ul simbolic serial.pts
(folosiți minicom -D serial.pts
) atunci veți observa mesajele și acolo.
3-error-mod/
pentru a evita eroarea de compilare, puteți genera doar scheletul pentru acest exercițiu folosind urmăroarea comandă:
LABS=kernel_modules/3-error-mod make skels
Intrați în directorul 3-error-mod/
. Obțineți modulul de kernel asociat. De ce au apărut erori de compilare? Hint: Cu ce diferă acest modul de modulul precedent?
Modificați modulul pentru a rezolva cauza apariției acelor erori. Compilați, încărcați și descărcați modulul.
Intrați în directorul 4-multi-mod/
. Inspectați fișierele sursă C: mod1.c
și mod2.c
. Exemplul este unul academic (modulul 2 conține doar definiția unei funcții folosite de modulul 1).
Modificați fișierul Kbuild
astfel încât să conducă la crearea fișierului-modul multi_mod.ko
pornind de la cele două fișiere sursă C. Hint: Citiți secțiunea Compilarea modulelor de kernel din laborator.
Compilați, încărcați și descărcați modulul. Mesajele sunt afișate corespunzător la consolă.
Intrați în directorul 5-oops-mod/
și inspectați fișierul sursă C. Observați unde va apărea problema. Adăugați în fișierul Kbuild
opțiunea -g
pentru compilare.
Compilați modulul asociat și încărcați-l în kernel. Identificați adresa de memorie la care a apărut oops-ul.
Pentru a identifica adresa, urmăriți mesajul de oops și extrageți valoarea registrului de tip pointer de cod/instrucțiuni (EIP
).
Determinați ce instrucțiune a dus la apariția oops-ului.
/proc/modules
din mașina virtuală.
Folosiți, pe mașina fizică, objdump
și/sau addr2line
. objdump
are nevoie de suport de debugging la compilare!
Descărcați modulul din kernel. Observați că operația nu funcționează pentru că au rămas referințe de la modulul de kernel în cadrul nucleului din momentul apariției oops-ului; până la eliberarea acelor referințe (lucru cvasi-imposibil în cazul unui oops) modulul nu poate fi descărcat.
Reporniti masina virtuală. Intrați în directorul 6-cmd-mod/
și inspectați fișierul sursă C cmd_mod.c
. Compilați modulul asociat și încărcați modulul în kernel pentru a vedea mesajul. Descărcați apoi modulul din kernel.
Dacă nu ați pornit, porniți și netconsole pentru a captura mesajul. La nevoie încărcați și descărcați din nou modulul. Hint: Urmăriți indicațiile legate de netconsole de la începutul secțiunii.
Fără a modifica sursele, încărcați modulul în kernel, astfel încât mesajul afișat să fie Early bird gets tired. Hints: Variabila str
poate fi modificată ca parametru transmis modulului. Accesati acest link.
Intrați în directorul 7-list-proc/
. Completați modulul astfel încât să afișeze informații despre procesul curent. Numele modulului trebuie rezultat este list_proc.ko
.
Urmăriți comentariile marcate cu TODO
. Afișați process ID-ul (PID-ul procesului) și numele executabilului. Informațiile vor fi afișate atât la încărcarea cât și la descărcarea modulului.
Folositi LXR sau cscope
pentru a afla conținutul unei structuri din nucleu (în cazul de față struct task_struct
).
Pentru a găsi câmpul structurii ce conține numele executabilului aferent, căutați șirul executable.
Pointer-ul la structura procesului ce rulează la un moment dat în kernel este dat de variabila current
(de tipul struct task_struct *
).
Pentru a folosi variabila current
va trebui să includeți header-ul în care este definită structura struct task_struct
, adică linux/sched.h
.
Compilați și încărcați modulul obținut. Descărcați modului de kernel.
Repetați apoi operația de încărcare/descărcare. Observați că diferă PID-urile proceselor afișate. Acest lucru se întâmplă pentru că la încărcarea modulului se creează un proces pornind de la executabilul /sbin/insmod
iar la descărcarea modulului se creează un proces pornind de la executabilul /sbin/rmmod
. Procesele vor fi diferite.
1. [1KP] KDB
Intrați în directorul 8-kdb/
. Activați KDB peste serială și intrați în modul KDB folosind SysRq.
Conectați-vă la pseudo-terminalul conectat la virtiocon0 folosind minicom, configurați KDB pentru a folosi portul serial hvc0 (echo hvc0 > /sys/module/kgdboc/parameters/kgdboc
) și activați-l folosind SysRq (Ctrl+O g). Analizați starea curentă a sistemului (help
pentru a vedea comenzile KDB disponibile). Continuați execuția kernelului folosind comand go
.
Inserați modulul hello_kdb
. Modulul va simula un bug la scrierea în fișierul /proc/hello_kdb_bug
. Pentru a simula un bug folosiți comanda de mai jos:
echo 1 > /proc/hello_kdb_bug
În urma rulării comenzii de mai sus la fiecare bug/panic nucleul se oprește și intră în modul debug.
Analizați stacktrace-ul și determinați codul care a generat bugul. Cum putem afla din KDB adresa la care a fost încărcat modulul?
În paralel, folosiți GDB într-o nouă fereastră pentru a vizualiza codul pornind de la informațiile din KDB.
Hint: Încărcați fișierul de simboluri. Folosiți info line
.
La scrierea în fișierul /proc/hello_kdb_break
, modulul va incrementa variabila kdb_write_address
. Intrați în KDB și setați un breakpoint pentru fiecare acces de scriere al variabilei kdb_write_address
. Reveniți în kernel pentru a declanșa o scriere folosind:
echo 1 > /proc/hello_kdb_break
2. [1KP] PS Module
Modificați modulul creat la exercițiul Proc Info pentru a afișa informații despre toate procesele din sistem la încărcarea modulului, nu doar procesul curent. Comparați apoi rezultatul obținut cu ieșirea comenzii ps
.
Macrourile de tipul for_each_...
(ca de exemplu for_each_process
) sunt utile în situațiile în care se dorește parcurgerea elementelor dintr-o listă.
Pentru înțelegerea modului de folosire a unei funcții sau a unui macro folosiți LXR sau Vim și cscope
și căutați scenarii de utilizare.
3. [1KP] Memory Info
Creați un modul de kernel care să afișeze zonele de memorie virtuală ale procesului curent; pentru fiecare zonă de memorie va afișa adresa de start și adresa de sfârșit.
Investigați structura struct task_struct, structura struct mm_struct și structura struct vm_area_struct. O zonă de memorie este indicată de o structură de tipul struct vm_area_struct.
Să includeți header-ele în care sunt definite structurile necesare.
4. [2KP] Dynamic Debugging
Intrați în directorul 9-dyndbg/
și compilați modulul dyndbg.ko
.
Familiarizați-vă cu sistemul de fișiere debugfs
montat în /debug
și analizați conținutul fișierului /debug/dynamic_debug/control
. Inserați modulul dyndbg.ko și observați noul conținut al fișierului dynamic_debug/control
.
Ce apare în plus? Rulați
grep dyndbg /debug/dynamic_debug/control
Configurați dyndbg astfel încât, la descărcarea modulului să fie afișate doar mesajele marcate ca “Important” din funcția my_debug_func
. Exercițiul va filtra doar apelurile pr_debug
- printk
fiind afișate mereu.
Specificați două moduri prin care puteți realiza filtrarea. Hint: Citiți secțiunea Dynamic debugging și analizați opțiunile dyndbg (ex. line, format).
Realizați filtrarea și revizualizați fișierul dynamic_debug/control
. Ce s-a schimbat? Cum vă dați seama ce apeluri sunt activate? Hint: Dyndbg flags. Descărcați apoi modulul și vizualizați mesajele.
5. [1KP] Dyndbg la inițializare
După cum ați observat, apelurile pr_debug pot fi activate/filtrate doar după inserarea modulului. În unele situații ar fi util să putem vizualiza și mesajele de la inițializarea modulului. Acest lucru poate fi realizat cu ajutorul unui parametru implicit (fake
) numit dyndbg ce poate fi transmis ca argument la inițializarea modulului. Prin acest parametru se pot adăuga/șterge flaguri dyndbg. Hint Recitiți ultima parte a secțiunii Dynamic debugging și observați flagurile disponibile (ex: +/- p
).
Citiți secțiunea Debug Messages at Module Initialization Time și inserați modulul astfel încât mesajele din my_debug_func
(apelată în dyndbg_init) să fie afișate și la inițializare. Warning: în mașina din laborator va trebui să folosiți insmod
în loc de modprobe
.
Fără a descărca modulul, dezactivați apelurile pr_debug. Hint: puteți șterge flagurile setate. Descărcați modulul.