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

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

Pentru mai multe detalii despre cscope parcurgeți secțiunea cscope din primul laborator.

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.

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

Vom rula, în directorul tools/labs, comanda

make build

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

  1. Vom copia modulele compilate în mașina virtuală. Adică vom folosi comanda make copy.
  2. 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.

Apoi urmați pașii uzuali în folosirea 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.

Extra

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.

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

Soluții

so2/laboratoare/lab02/exercitii.txt · Last modified: 2019/02/25 15:18 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