This is an old revision of the document!


Laborator 2: Exerciții

Pregătirea laboratorului

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:

  • pregătirea scheletului de laborator
  • compilarea modulelor de Kernel
  • copierea modulelor pe mașina virtuală
  • pornirea mașinii virtuale și testarea modulelor

Pregătirea scheletului de laborator

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

Numele laboratorului curent este kernel_modules.

Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea <lab_name>/<task_name> variabilei LABS.

Scheletul este generat în directorul tools/labs/skels.

Compilarea modulelor

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
...

Copierea modulelor pe mașina virtuală

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ă.

Testarea modulelor

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 

Pentru dezvoltarea laboratorului, este recomandat să folosim trei terminale sau, mai bine, trei tab-uri de terminal. Pentru a deschide un nou tab de terminal folosim combinația de taste Ctrl+Shift+t. Cele trei tab-uri de terminal îndeplinesc următoarele roluri:

  1. În primul tab de terminal dezvoltăm modulul de kernel: editare, compilare, copiere în directorul dedicat pentru mașina virtuală QEMU. Lucrăm în directorul aferent rezultat în urma generării scheletului de laborator, din ~/so2/linux/tools/labs/skels.
  2. În al doilea tab de terminal pornim mașina virtuală QEMU și apoi testăm modulul de kernel: încărcare/descărcare modul, rulare teste. Lucrăm în directorul aferent mașinii virtuale: ~/so2/linux/tools/labs.
  3. În al treilea tab de terminal pornim minicom sau un server UDP care să primească mesajele de netconsole. Nu contează în ce director ne aflăm. Folosim comanda
    student@asgard:~$ nc -l -p 6000 -u

Citiți cu atenție toate precizările unui exercițiu înainte de a începe rezolvarea acestuia.

Linux

0. [0.5p] Intro

Term search: Folosiți cscope sau LXR pentru a localiza în sursele nucleului Linux locul de definiție al următoarelor simboluri:

  • macro-urile module_init și module_exit. Ce realizează cele două macro-uri? Ce reprezintă init_module respectiv cleanup_module?
  • variabila ignore_loglevel. La ce este folosită această variabilă?

Pentru cscope parcurgeți secțiunea cscope din primul laborator.

Dacă aveți probleme în folosirea 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

Atunci când folosiți cscope, va trebui să vă aflați în directorul so2/linux/ din directorul home al utilizatorului student.

Atunci când căutați o structură folosind cscope în Vim folosiți doar numele structurii. Adică pentru a căuta definiția structurii struct module folosiți comanda

vim -t module

sau, în Vim, construcția

:cs f g module

Pentru a merge la definiția unui simbol direct când porniți vim, folosiți 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).

1. [1p] Module

Pentru a putea lucra cu modulele de kernel, vom realiza următorii pași, descriși și mai sus în pagină:

  1. Vom genera scheletul de laborator, rulând comanda
    LABS=kernel_modules make skels

    în directorul tools/labs. Această comandă copiază fișierele necesare laboratorului în directorul tools/labs/skels.

  2. Vom compila modulele de kernel. Adică vom rula, în directorul tools/labs, comanda
    make build

    . Această comandă compilează modulele din toate exercițiile din laborator.

  3. Vom copia modulele compilate în mașina virtuală. Adică vom folosi comanda make copy.
  4. Vom porni mașina virtuală. Adică vom rula, din cadrul directorului aferent mașinii virtuale (adică ~/so2/linux/tools/labs/) comanda
    make 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.

Până la rezolvarea exercițiului 3, o să primiți o eroare de compilare la modului 3-error-mod. Puteți evita problema ștergând directorul skels/kernel_modules/3-error-mod/.

Apoi urmați pașii uzuali în foosirea unui modul de kernel:

  1. Încărcați modulul în kernel.
  2. Listați modulele din kernel și verificați existența modulului curent.
  3. Descărcați modulul din kernel.
  4. Vizualizați mesajele afișate de modul la încărcarea, respectiv descărcarea din nucleu folosind comanda dmesg.

Parcurgeți secțiunea Testarea modulelor.

La descărcarea unui modul din kernel poate fi precizat doar numele modulului (fără extensie).

2. [1p] Printk

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ă.

Citiți secțiunea Printk Debugging din laborator și urmăriți precizările referitoare la folosirea funcției 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. [1p] Error

Dacă ați șters directorul 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.

4. [1p] Sub-Module

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ă.

5. [1.5p] Kernel Oops

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.

Parcurgeți secțiunea Compilarea modulelor de kernel din laborator.

Compilați modulul asociat și încărcați-l în kernel. Identificați adresa de memorie la care a apărut oops-ul.

Parcurgeți secțiunea Exemplu de kernel oops din laborator.

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.

Folosiți informația din /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!

Parcurgeți secțiunile objdump și addr2line din laborator.

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.

6. [1p] Parametri Module

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.

7. [1.5p] Proc Info

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.

În kernel-ul Linux un proces este descris de structura struct task_struct.

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.

8. [2.5p] Kprobes

[0.5p] Intrați în directorul 8-kprobes/ din arhiva de resurse a laboratorului. Urmăriți fișierul sursă kprobes.c. Modulul folosește jprobes pentru a urmări apelul do_execve_common, aferent apelului de sistem exec.

Compilați și încărcați în mașina virtuală modulul de kernel rezultat (kprobes.c). Urmăriți mesajele de debug și comparați cu procesele existente în sistem.

Este chiar exmplul din laborator din secțiunea Kprobes.

[2p] Creați un modul care analizează valoarea de retur a funcției _do_fork. La întoarcerea din funcție, afișați valoarea de retur, numele și pid-ul procesului părinte și pid-ul procesului curent.

Revedeți secțiunea Kretprobes.

Puteți urmări și exemplul de cod din sursele nucleului din samples/kprobes/kretprobe_example.c.

Urmăriți comentariile marcate cu TODO din cadrul fișierului cod sursă kprobes.c.

Pentru a afișa adresa de retur în handler-ul de kretprobe (adică în funcțion my_ret_handler) folosiți funcția regs_return_value.

Pentru a obține structura de tip struct task_struct * aferentă procesului părinte al procesului curent folosiți construcția current->parent.

Procesul interceptat de kretprobe este shell-ul. În mașina virtuală procesul său părinte este procesul init/busybox (cu PID-ul 1). Valoarea întoarsă de apelul _do_fork afișată în cadrul handler-ului kretprobe este PID-ul procesului copil proaspăt creat, așa cum se întâmplă și în user space în cazul apelului fork pentru procesul părinte.

Puteți folosi comanda pstree -p pentru a afișa ierarhia de procese a sistemului și pentru a verifica astfel, informațiile afișate de handler-ul de kretprobe în modulul de kernel. Puteți, deasemenea, confirma acest lucru verificând pid-ul procesului din proba anterioară ( do_execveat_common).

Extra: Pentru acasă

1. [1KP] KDB

Intrați în directorul 9-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.

Procesele din sistem sunt structurate într-o listă circulară.

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.

Porniți de la un modul existent.

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

Să includeți header-ele în care sunt definite structurile necesare.

4. [2KP] Dynamic Debugging

Intrați în directorul 10-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.

Soluții

so2/laboratoare/lab02/exercitii.1519682467.txt.gz · Last modified: 2018/02/27 00:01 by ionel.ghita
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0