This shows you the differences between two versions of the page.
so2:laboratoare:lab05:exercitii [2017/03/19 14:06] octavian.purdila [Laborator 5: Exerciții] |
so2:laboratoare:lab05:exercitii [2019/03/20 11:49] (current) gabriel.bercaru [1. [2.5p] Alocare de porturi I/O] - fixed typo ('kdb' instead of 'kbd') |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Laborator 5: Exerciții ====== | ====== Laborator 5: Exerciții ====== | ||
- | Pentru desfășurarea laboratorului pornim de la [[http://elf.cs.pub.ro/so2/res/laboratoare/lab05-tasks.zip|arhiva de sarcini a laboratorului]]. Descărcăm și decomprimăm arhiva în directorul ''so2/'' din directorul home al utilizatorului ''student'' de pe sistemul fizic:<code bash> | + | |
- | student@eg106:~$ cd so2/ | + | ===== Pregătirea laboratorului ===== |
- | student@eg106:~/so2$ wget http://elf.cs.pub.ro/so2/res/laboratoare/lab05-tasks.zip | + | |
- | student@eg106:~/so2$ unzip lab05-tasks.zip | + | Pentru rezolvarea laboratorului, vom lucra în același director din care pornim mașina virtuală (''~/so2/linux/tools/labs''). |
- | student@eg106:~/so2$ tree lab05-tasks | + | |
+ | 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: | ||
+ | |||
+ | <code bash> | ||
+ | tools/labs $ make skels | ||
</code> | </code> | ||
- | În cadrul directorului ''lab05-tasks/'' se găsesc resursele necesare pentru dezvoltarea exercițiilor de mai jos: fișiere schelet de cod sursă, fișiere Makefile și Kbuild, scripturi și programe de test. | ||
- | Vom dezvolta exercițiile pe sistemul fizic și apoi le vom testa pe [[:so2:resurse:masini-virtuale|mașina virtuală QEMU]]. După editarea și compilarea unui modul de kernel îl vom copia în directorul dedicat pentru mașina virtuală QEMU folosind o comandă de forma<code bash> | + | Pentru a genera scheletul pentru un singur laborator, vom folosi variabila de mediu ''LABS'': |
- | student@eg106:~/so2$ cp /path/to/module.ko ~/so2/qemu-so2/fsimg/root/modules/ | + | |
- | </code> unde ''/path/to/module.ko'' este calea către fișierul obiect aferent modulului de kernel. Apoi vom porni, din directorul ''~/so2/qemu-so2/'', mașina virtuală QEMU folosind comanda<code bash> | + | <code bash> |
- | student@eg106:~/so2/qemu-so2$ make | + | tools/labs $ make clean |
+ | tools/labs $ LABS=<lab name> make skels | ||
+ | </code> | ||
+ | |||
+ | <note important> | ||
+ | Numele laboratorului curent este ''interrupts''. | ||
+ | </note> | ||
+ | |||
+ | Similar, putem genera și scheletul pentru un singur exercițiu, atribuind valoarea ''<lab_name>/<task_name>'' variabilei ''LABS''. | ||
+ | |||
+ | <note> | ||
+ | Scheletul este generat în directorul ''tools/labs/skels''. | ||
+ | </note> | ||
+ | |||
+ | ==== Compilarea modulelor ==== | ||
+ | |||
+ | Comanda ''make build'' compilează toate modulele din directorul ''skels''. | ||
+ | |||
+ | <code bash> | ||
+ | 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 ./interrupts; do echo "obj-m += $i/" >> skels/Kbuild; done | ||
+ | ... | ||
+ | </code> | ||
+ | |||
+ | ==== 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ă. | ||
+ | |||
+ | <code bash> | ||
+ | student@eg106:~/so2/linux/tools/labs$ make copy | ||
+ | student@eg106:~/so2/linux/tools/labs$ make boot | ||
+ | </code> | ||
+ | |||
+ | 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 [[https://ocw.cs.pub.ro/courses/so2/resurse/masini-virtuale#interactiunea_cu_masina_virtuala|Interacțiunea cu mașina virtuală]]. | ||
+ | |||
+ | ==== Testarea modulelor ==== | ||
+ | |||
+ | Modulele generate sunt copiate pe mașina virtuală în directorul ''/home/root/skels/<lab_name>''. | ||
+ | |||
+ | <code bash> | ||
+ | root@qemux86:~/skels/interrupts# ls | ||
+ | kbd.ko | ||
</code> | </code> | ||
- | După pornirea mașinii virtuale QEMU vom putea folosi comenzi în fereastra QEMU pentru a încărca și descărca modulul de kernel:<code> | + | 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:<code> |
- | # insmod modules/module-name.ko | + | root@qemux86:~# insmod skels/<lab_name>/<task_name>/<module_name>.ko |
- | # rmmod module/module-name | + | root@qemux86:~# rmmod skels/<lab_name>/<task_name>/<module_name>.ko |
- | </code> unde ''module-name'' este numele modulului de kernel. | + | </code> |
<note> | <note> | ||
Line 38: | Line 92: | ||
Găsiți definițiile următoarelor simboluri în nucleul Linux: | Găsiți definițiile următoarelor simboluri în nucleul Linux: | ||
* structura ''resource''; | * structura ''resource''; | ||
- | * funcția ''request_region'' și de acolo funcția ''__requestion_region''; | + | * funcția ''request_region'' și de acolo funcția ''__request_region''; |
* funcția ''request_irq'' și de acolo funcția ''request_threaded_irq''; | * funcția ''request_irq'' și de acolo funcția ''request_threaded_irq''; | ||
* funcția ''inb'', varianta de pe arhitectura x86. | * funcția ''inb'', varianta de pe arhitectura x86. | ||
Urmăriți în codul nucelului Linux: | Urmăriți în codul nucelului Linux: | ||
- | * funcția de inițializare a driver-ului de tastatură [[http://lxr.free-electrons.com/source/drivers/input/serio/i8042.c?v=3.13#L1370|i8042_setup_kbd]] | + | * funcția de inițializare a driver-ului de tastatură [[https://elixir.bootlin.com/linux/v4.15/source/drivers/input/serio/i8042.c#L1470|i8042_setup_kbd]] |
- | * funcția de tratarea a întreruperii pentru tastatură AT sau PS/2, înregistrată cu funcția de inițializare: [[http://lxr.free-electrons.com/source/drivers/input/keyboard/atkbd.c#L367|atkbd_interrupt]] | + | * funcția de tratarea a întreruperii pentru tastatură AT sau PS/2, înregistrată cu funcția de inițializare: [[https://elixir.bootlin.com/linux/v4.15/source/drivers/input/keyboard/atkbd.c#L372|atkbd_interrupt]] |
===== [10.5p] Keylogger ===== | ===== [10.5p] Keylogger ===== | ||
Line 50: | Line 104: | ||
Obiectivul exercițiilor următoare este să creați un driver pentru a intercepta tastele apăsate (//keylogger//). Driver-ul va intercepta IRQ-ul destinat controller-ului de tastatură și va inspecta codurile tastelor primite, stocându-le într-un buffer. | Obiectivul exercițiilor următoare este să creați un driver pentru a intercepta tastele apăsate (//keylogger//). Driver-ul va intercepta IRQ-ul destinat controller-ului de tastatură și va inspecta codurile tastelor primite, stocându-le într-un buffer. | ||
- | ==== 1. [1.5p] Alocare de porturi I/O ==== | + | ==== 1. [2.5p] Alocare de porturi I/O ==== |
- | Pentru început ne propunem să alocăm spații din zona I/O pentru dispozitive hardware. Vom vedea că nu putem să alocăm spațiu pentru tastatură întrucât regiunea aferentă e deja alocată. Apoi vom aloca spațiu I/O pentru portul serial. | + | Pentru început ne propunem să alocăm spații din zona I/O pentru dispozitive hardware. Vom vedea că nu putem să alocăm spațiu pentru tastatură întrucât regiunea aferentă e deja alocată. Apoi vom aloca spațiu I/O pentru porturi care nu sunt folosite. |
- | În fișierul ''so2_kbd.c'' avem un schelet de implementare a unui driver de tastatură. Parcurgeți codul sursă din acest fișier și urmăriți funcția ''so2_kbd_init''. Codul din cadrul funcției ''so2_kbd_init'' care alocă porturile I/O (''STATUS_REG'' și ''DATA_REG'', folosind ''request_region'') este comentat pentru că ar returna o eroare. | + | În fișierul ''kbd.c'' avem un schelet de implementare a unui driver de tastatură. Parcurgeți codul sursă din acest fișier și urmăriți funcția ''kbd_init''. Observați că porturile de care avem nevoie sunt ''I8042_STATUS_REG'' și ''I8042_DATA_REG''. |
- | Decomentați liniile din cadrul funcției ''so2_kbd_init'' și returnați cod de eroare la nevoie, prin: | + | Urmăriți comentariile marcate cu ''TODO 1'' pentru a înregistra porturile de I/O și asigurați-vă că ați tratat corect erorile. De asemenea, deînregistrați porturile în funcția ''kbd_exit''. |
- | - atribuirea unui cod de eroare specific (de exemplu ''-EBUSY'') variabilei ''err''; | + | |
- | - salt (''goto'') la label-ul corespunzător (''out_unregister''). | + | |
<note tip> | <note tip> | ||
Line 64: | Line 116: | ||
</note> | </note> | ||
- | Compilați fișierul sursă ''so_kdb.c'' în modulul ''so2_kdb.ko'' folosind, pe sistemul fizic, comanda<code> | + | Compilați fișierul sursă ''kdb.c'' în modulul ''kdb.ko'' folosind, pe sistemul fizic, comanda<code> |
- | make | + | make build |
- | </code>și încărcați-l în nucleu, în mașina virtuală QEMU, folosind comanda<code> | + | </code>și, după ce l-ați copiat pe mașina virtuală, încărcați-l în nucleu, în mașina virtuală QEMU, folosind comanda<code> |
- | insmod so2_kbd.ko | + | insmod kbd.ko |
</code> | </code> | ||
- | Observați că obțineți eroare la încărcare: //%%insmod: Can't insert '.../so2_kbd.ko': Device or resource busy%%//. Acest lucru se întâmplă întrucât avem deja un driver care a întregistrat spațiul de I/O și kernel-ul poate deja accesa porturile I/O aferente. Ca să validăm că, într-adevăr, adresele aferente celor două registre (''STATUS_REG'' și ''DATA_REG'') sunt înregistrate, rulăm, în mașina virtuală QEMU comanda<code> | + | Observați că obțineți eroare la încărcare: //%%insmod: Can't insert 'kbd.ko': Device or resource busy%%//. Acest lucru se întâmplă întrucât avem deja un driver care a întregistrat spațiul de I/O și kernel-ul poate deja accesa porturile I/O aferente. Ca să validăm că, într-adevăr, adresele aferente celor două registre (''STATUS_REG'' și ''DATA_REG'') sunt înregistrate, rulăm, în mașina virtuală QEMU comanda<code> |
cat /proc/ioports | grep keyboard | cat /proc/ioports | grep keyboard | ||
</code> | </code> | ||
- | Observăm că sunt înregistate cele două porturi (''0x60'': ''DATA_REG'' și ''0x64'':''STATUS_REG'') corespunzătoare registrelor pentru tastatură. | + | Observăm că cele două porturi (''0x60'': ''DATA_REG'' și ''0x64'':''STATUS_REG'') corespunzătoare registrelor pentru tastatură sunt înregistrate de kernel la boot, deci nu vom putea să eliminăm modulul care le deține. |
- | <note important> | + | Pentru a putea testa cu succes alocarea de porturi I/O, vom //păcăli// kernelul prin înregistrarea porturilor 0x61 și 0x65. |
- | Comentați la loc liniile ce generează eroare înainte de a trece mai departe. | + | |
- | </note> | + | |
- | Pentru a folosi cu succes alocarea de porturi I/O, alocați pentru portul serial ''COM1''. Pentru portul serial ''COM1'' aveți nevoie de: | + | Folosiți funcția ''request_region'' (în cadrul funcției ''kbd_init'') pentru a aloca porturile aferente și folosiți funcția ''release_region'' (în cadrul funcției ''kbd_exit''). Apoi compilați și inserați modulul în nucleu. Verificați conținutul fișierului ''/proc/ioports'' și verificați acum că cele două porturi I/O sunt alocate driverului nostru. |
- | * adresa de start a portului serial ''COM1''; este vorba de ''0x3F8'' | + | |
- | * numărul de registre accesibile de la adresa de mai sus; este vorba de ''8'' registre | + | |
- | Folosiți funcția ''request_region'' (în cadrul funcției ''so2_kbd_init'') pentru a aloca porturile aferente și folosiți funcția ''release_region'' (în cadrul funcției ''so2_kbd_exit''). Apoi compilați și inserați modulul în nucleu. Verificați conținutul fișierului ''/proc/ioports'' și verificați acum că spațiul I/O ''0x3f8-0x3ff'' este alocat driverului nostru. | + | <code bash> |
+ | root@qemux86:~# insmod skels/interrupts/kbd.ko | ||
+ | kbd: loading out-of-tree module taints kernel. | ||
+ | Driver kbd loaded | ||
+ | root@qemux86:~# cat /proc/ioports | grep kbd | ||
+ | 0061-0061 : kbd | ||
+ | 0065-0065 : kbd | ||
+ | </code> | ||
Descărcați modulul din kernel folosind comanda<code> | Descărcați modulul din kernel folosind comanda<code> | ||
- | rmmod so2_kdb | + | rmmod kbd |
- | </code> și verificați acum conținutul fișierului ''/proc/ioports''; observți că a dispărut alocarea spațiului I/O pentru ''COM1'' (''0x3f8-0x3ff''). | + | </code> și verificați acum conținutul fișierului ''/proc/ioports''; observți că a dispărut alocarea spațiului I/O pentru cele două porturi. |
- | + | ||
- | Încărcați și descărcați de mai multe ori modulul de kernel pentru a vă asigura că totul merge cum trebuie. | + | |
- | + | ||
- | Pentru a verifica tratarea corectă a cazului de eroare, încărcați driverul de port serial din sistem, folosind comenzile:<code> | + | |
- | insmod /root/modules/serial_core.ko | + | |
- | insmod /root/modules/8250.ko | + | |
- | </code> | + | |
- | + | ||
- | Acum încercați din nou încărcarea modulului ''so2_kbd.ko''. Observăm eroare la înregistrarea spațiului de I/O care este acum adjudecat de driverul de port serial. Putem valida acest lucru urmărind ''/proc/ioports''. | + | |
- | + | ||
- | Descărcați din kernel modulele pentru portul serial, verificați că spațiul I/O pentru ''COM1'' este liber și inserați din nou modulul ''so2_kbd.ko''. Ar trebui ca totul să funcționeze. | + | |
<note important> | <note important> | ||
- | Ștergeți liniile care înregistrează și eliberează spațiul pentru portul serial ''COM1'' înainte de a trece la exercițiul următor. | + | Comentați liniile care înregistrează și eliberează cele două porturi înainte de a trece la exercițiul următor. |
</note> | </note> | ||
- | ==== 2. [1.5p] Rutina de tratare a întreruperii ==== | + | ==== 2. [2p] Rutina de tratare a întreruperii ==== |
- | Urmărim să înregistrăm o rutină de tratare a întreruperii de tastatură. Veți scrie o funcție (rutină) de tratare a înteruperii și o veți înregistra folosind ''[[http://lxr.free-electrons.com/source/include/linux/interrupt.h?v=3.13#L128|request_irq]]''. | + | Urmărim să înregistrăm o rutină de tratare a întreruperii de tastatură. Veți scrie o funcție (rutină) de tratare a înteruperii și o veți înregistra folosind ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]''. |
- | Implementarea o veți face în fișierul ''so2_kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 2: ... */%%'' și indicații despre ce trebuie să urmăriți. | + | Implementarea o veți face în fișierul ''kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 2: ... */%%'' și indicații despre ce trebuie să urmăriți. |
- | Pentru început definiți rutina de tratare a întreruperii (numită ''so2_kbd_interrupt_handle'') ca nefăcând nimic. Rutina trebuie să lase întreruperea să fie procesată de handler-ul inițial; în caz contrar, veți pierde controlul tastaturii în mașina virtuală. | + | Pentru început definiți rutina de tratare a întreruperii (numită ''kbd_interrupt_handle'') ca nefăcând nimic. Rutina trebuie să lase întreruperea să fie procesată de handler-ul inițial; în caz contrar, veți pierde controlul tastaturii în mașina virtuală. |
<note tip> | <note tip> | ||
Line 121: | Line 165: | ||
<note tip> | <note tip> | ||
- | Pentru întreruperi partajate, parametrul ''dev_id'' **nu** poate fi ''NULL''. Folosiți ''&devs[0]'', adică pointer la structura de dispozitiv de tipul ''struct so2_device_data''. Această structură va conține toate informațiile necesare pentru gestiunea dispozitivului. | + | Pentru întreruperi partajate, parametrul ''dev_id'' **nu** poate fi ''NULL''. Folosiți ''&devs[0]'', adică pointer la structura de dispozitiv de tipul ''struct kbd''. Această structură va conține toate informațiile necesare pentru gestiunea dispozitivului. |
Pentru a putea urmări ulterior înregistrarea validă a întreruperii în ''/proc/interrupts'', **nu** folosiți ''NULL'' nici pentru parametrul ''dev_name''. Puteți folosi macro-ul ''MODULE_NAME''. | Pentru a putea urmări ulterior înregistrarea validă a întreruperii în ''/proc/interrupts'', **nu** folosiți ''NULL'' nici pentru parametrul ''dev_name''. Puteți folosi macro-ul ''MODULE_NAME''. | ||
Line 130: | Line 174: | ||
</note> | </note> | ||
- | Compilați modulul și încărcați-l în nucleu. Ulterior încărcării în nucleu, puteți observa înregistrarea liniei de întrerupere prin consultarea fișierului ''/proc/interrupts''. Urmăriți linia din output aferentă //IRQ line//-ului pentru tastatură (definit în macro-ul ''I8042_KBD_IRQ''). Observați că sunt două drivere înregistrate la această linie de întreruper (deci avem o linie de întrerupere partajată): driverul inițial ''i8042'' și driverul nostru ''so2_kbd''. Descărcați modulul din nucleu. | + | Compilați modulul și încărcați-l în nucleu. Ulterior încărcării în nucleu, puteți observa înregistrarea liniei de întrerupere prin consultarea fișierului ''/proc/interrupts''. Urmăriți linia din output aferentă //IRQ line//-ului pentru tastatură (definit în macro-ul ''I8042_KBD_IRQ''). Observați că sunt două drivere înregistrate la această linie de întreruper (deci avem o linie de întrerupere partajată): driverul inițial ''i8042'' și driverul nostru ''kbd''. Descărcați modulul din nucleu. |
<note tip> | <note tip> | ||
Line 143: | Line 187: | ||
- Veți captura tastele apăsate (doar //pressed// nu și //released//)/ | - Veți captura tastele apăsate (doar //pressed// nu și //released//)/ | ||
- Veți identifica tastele ce reprezintă caractere ASCII. | - Veți identifica tastele ce reprezintă caractere ASCII. | ||
- | - Veți copia caracterele ASCII corespunzătoare tastelor apăsate și le veți stoca în buffer-ul ''buf'' din structura de tip ''struct so2_device_data'' aferentă dispozitivului. | + | - Veți copia caracterele ASCII corespunzătoare tastelor apăsate și le veți stoca în buffer-ul ''buf'' din structura de tip ''struct kbd'' aferentă dispozitivului. |
- | Implementarea o veți face în fișierul ''so2_kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 3: ... */%%'' și indicații despre ce trebuie să urmăriți. | + | Implementarea o veți face în fișierul ''kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 3: ... */%%'' și indicații despre ce trebuie să urmăriți. |
La fiecare pas compilați modulul, încărcați modulul în kernel, apăsați în mașina virtuală taste pentru a verifica funcționalitatea, și apoi descărcați modulul. | La fiecare pas compilați modulul, încărcați modulul în kernel, apăsați în mașina virtuală taste pentru a verifica funcționalitatea, și apoi descărcați modulul. | ||
Line 154: | Line 198: | ||
<note tip> | <note tip> | ||
- | Citiți valoarea registrului ''I8042_DATA_REG'' folosind [[http://lxr.free-electrons.com/source/include/asm-generic/io.h?v=3.13#L127|inb]], rețineți valoarea citită în variabila locală ''val'' și apoi returnați valoarea variabilei ''val''. | + | Citiți valoarea registrului ''I8042_DATA_REG'' folosind [[https://elixir.bootlin.com/linux/v4.15/source/include/asm-generic/io.h#L362|inb]], rețineți valoarea citită în variabila locală ''val'' și apoi returnați valoarea variabilei ''val''. |
Parcurgeți secțiunea [[:so2:laboratoare:lab05#Operații de scriere și citire a porturilor I/O|Operații de scriere și citire a porturilor I/O]] din laborator. | Parcurgeți secțiunea [[:so2:laboratoare:lab05#Operații de scriere și citire a porturilor I/O|Operații de scriere și citire a porturilor I/O]] din laborator. | ||
</note> | </note> | ||
- | Apelați funcția ''i8042_read_data()'' în handler-ul de întrerupere ''so2_kbd_interrupt_handler()'' (adică la fiecare tastă apăsată) și afișați valoarea citită din registru. | + | Apelați funcția ''i8042_read_data()'' în handler-ul de întrerupere ''kbd_interrupt_handler()'' (adică la fiecare tastă apăsată) și afișați valoarea citită din registru. |
<note tip> | <note tip> | ||
Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | ||
- | printk(LOG_LEVEL "IRQ: %d, scancode=0x%x (%u, %c)\n", | + | pr_info("IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", |
- | irq_no, scancode, scancode, scancode); | + | irq_no, scancode, scancode, pressed, ch); |
</code> | </code> | ||
unde ''scancode'' este valoarea registrului citit cu ajutorul funcției ''i8042_read_data()''. | unde ''scancode'' este valoarea registrului citit cu ajutorul funcției ''i8042_read_data()''. | ||
Line 204: | Line 248: | ||
</note> | </note> | ||
- | În handler-ul de întrerupere (''so2_kbd_interrupt_handler'') interpretați scancode-ul pentru a vedea dacă este o apăsare sau o ridicare de tastă și obțineți caracterul ASCII aferent. Afișați informațiile obținute. | + | În handler-ul de întrerupere (''kbd_interrupt_handler'') interpretați scancode-ul pentru a vedea dacă este o apăsare sau o ridicare de tastă și obțineți caracterul ASCII aferent. Afișați informațiile obținute. |
<note tip> | <note tip> | ||
Line 212: | Line 256: | ||
<note tip> | <note tip> | ||
Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | ||
- | printk(LOG_LEVEL "IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", | + | pr_info(LOG_LEVEL "IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", |
irq_no, scancode, scancode, pressed, ch); | irq_no, scancode, scancode, pressed, ch); | ||
</code> | </code> | ||
Line 224: | Line 268: | ||
=== c. [1p] Colectarea caracterelor în buffer === | === c. [1p] Colectarea caracterelor în buffer === | ||
- | Dorim să colectăm caracterele apăsate (nu și celelalte taste) într-un buffer care apoi va fi citit din user space. | + | Dorim să colectăm caracterele apăsate (nu și celelalte taste) într-un buffer circular care apoi va fi citit din user space. |
- | Actualizați handler-ul de tratare a întreruperii (''so2_kbd_interrupt_handler'') astfel încât un caracter ASCII citit să fie adăugat la sfârșitul buffer-ului aferent dispozitivului. Dacă buffer-ul este plin, caracterul va fi ignorat. | + | Actualizați handler-ul de tratare a întreruperii (''kbd_interrupt_handler'') astfel încât un caracter ASCII citit să fie adăugat la sfârșitul buffer-ului aferent dispozitivului. Dacă buffer-ul este plin, caracterul cel mai vechi o sa fie suprascris. |
<note tip> | <note tip> | ||
- | Buffer-ul aferent dispozitivului este câmpul ''buf'' din structura de tip ''struct so2_device_data'' aferentă dispozitivului. Această structură o puteți obține în cadrul handler-ului de tratare a întreruperii folosind construcția<code> | + | Buffer-ul aferent dispozitivului este câmpul ''buf'' din structura de tip ''struct kbd'' aferentă dispozitivului. Această structură o puteți obține în cadrul handler-ului de tratare a întreruperii folosind construcția<code> |
- | struct so2_device_data *data = (struct so2_device_data *) dev_id; | + | struct kbd *data = (struct kbd *) dev_id; |
</code> | </code> | ||
- | Dimensiunea curentă a buffer-ului este dată de câmpul ''buf_idx'' din structura de tip ''struct so2_device_data'' aferentă dispozitivului. Va trebui să incrementați valoarea acestei variabile la fiecare caracter adăugat în buffer. | + | Dimensiunea curentă a buffer-ului este dată de câmpul ''count'' din structura de tip ''struct kbd'' aferentă dispozitivului. Câmpul ''put_idx'' specifică următoarea poziție de scriere, iar ''get_idx'' următoarea poziție de citire. Urmăriți implementarea funcției ''put_char'' pentru a observa cum sunt adăugate datele în buffer-ul circular. |
</note> | </note> | ||
- | |||
- | <note tip> | ||
- | Capacitatea buffer-ului, peste care nu puteți scrie, este dată de macro-ul ''BUFFER_SIZE''. | ||
- | </note> | ||
- | |||
==== 4. [2p] Citirea bufferului printr-un apel ''read'' ==== | ==== 4. [2p] Citirea bufferului printr-un apel ''read'' ==== | ||
- | Pentru a avea acces la datele colectate de keylogger va trebui să le transmitem în user space. Facem acest lucru prin intermediului unui dispozitiv de tip caracter (''/dev/so2_kbd''). La citirea din acest dispozitiv de tip caracter vom primi datele din buffer-ul din kernel space în care am colectat tastele apăsate. | + | Pentru a avea acces la datele colectate de keylogger va trebui să le transmitem în user space. Facem acest lucru prin intermediului unui dispozitiv de tip caracter (''/dev/kbd''). La citirea din acest dispozitiv de tip caracter vom primi datele din buffer-ul din kernel space în care am colectat tastele apăsate. |
- | Implementarea o veți face în fișierul ''so2_kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 4: ... */%%'' în funcția ''so2_kbd_read()'' și indicații despre ce trebuie să urmăriți. | + | Implementarea o veți face în fișierul ''kbd.c''. Zonele din program în care trebuie să acționați au comentarii marcate cu ''%%/* TODO 4: ... */%%'' în funcția ''kbd_read()'' și indicații despre ce trebuie să urmăriți. |
La fiecare pas compilați modulul, încărcați modulul în kernel, apăsați în mașina virtuală taste pentru a verifica funcționalitatea, și apoi descărcați modulul. | La fiecare pas compilați modulul, încărcați modulul în kernel, apăsați în mașina virtuală taste pentru a verifica funcționalitatea, și apoi descărcați modulul. | ||
- | Este permis un singur cititor la un moment dat astfel încât protejăm codului funcției ''so2_kbd_read()'' cu un mutex. | + | Este permis un singur cititor la un moment dat astfel încât protejăm codului funcției ''kbd_read()'' cu un mutex. |
- | Întrucât nu dorim modificări la nivelul buffer-ului din partea handler-ului de întrerupere în timp ce facem citirea sa în funcția ''so2_kbd_read()'' veți folosiți un spinlock și veți dezactiva întreruperile în funcția ''so2_kbd_read''. | + | Întrucât nu dorim modificări la nivelul buffer-ului din partea handler-ului de întrerupere în timp ce facem citirea sa în funcția ''kbd_read()'' veți folosiți un spinlock și veți dezactiva întreruperile în funcția ''kbd_read''. |
<note tip> | <note tip> | ||
- | Definiți spinlock-ul în structura de dispozitiv (''struct so2_device_data'') și inițializați-l în ''so2_kbd_init''. Folosiți funcțiile [[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L321|spin_lock_irqsave]] și [[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L346|spin_unlock_irqrestore]] pentru locking cu dezactivarea întreruperilor pe parcursul lucrului cu buffer-ul în rutina ''so2_kbd_read()''. | + | Definiți spinlock-ul în structura de dispozitiv (''struct kbd'') și inițializați-l în ''kbd_init''. Folosiți funcțiile [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L338|spin_lock_irqsave]] și [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L363|spin_unlock_irqrestore]] pentru locking cu dezactivarea întreruperilor pe parcursul lucrului cu buffer-ul în rutina ''kbd_read()''. |
- | Folosiți funcțiile [[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L291|spin_lock]] si [[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L331|spin_unlock]] pentru protejarea buffer-ulu în rutina de tratare a întreruperii. | + | Folosiți funcțiile [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L308|spin_lock]] si [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L348|spin_unlock]] pentru protejarea buffer-ulu în rutina de tratare a întreruperii. |
Parcurgeți secțiunea [[:so2:laboratoare:lab05#Locking1|Locking]] din laborator. | Parcurgeți secțiunea [[:so2:laboratoare:lab05#Locking1|Locking]] din laborator. | ||
</note> | </note> | ||
- | Pentru copierea în user space veți folosi apelul [[http://lxr.free-electrons.com/source/include/asm-generic/uaccess.h?v=3.13#L265|copy_to_user]]. Întrucât nu puteți folosi ''copy_to_user'' în timp ce aveți un spinlock luat, trebuie să folosiți un buffer temporar. | + | Completați funcția ''get_char'' pentru a obține următorul caracter care trebuie citit din buffer. Aveți grijă la implementarea buffer-ului circular (să nu citiți dincolo de dimensiunea buffer-ului și de poziția de scriere). |
- | <note tip> | + | Pentru copierea în user space veți folosi apelul [[https://elixir.bootlin.com/linux/v4.15/source/arch/x86/include/asm/uaccess.h#L249|put_user]] pentru fiecare caracter în parte, obținut folosind funcția ''get_char''. |
- | Buffer-ul temporar și dimensiunea acestuia sunt indicate de câmpurile ''tmp_buf'' și ''tmp_buf_idx'' din structura de tip dispozitiv. | + | |
+ | <note warning> | ||
+ | Nu puteți folosi ''copy_to_user'' sau ''put_user'' în timp ce aveți un spinlock luat. | ||
Urmăriți în laboratorul 4 secțiunea [[:so2:laboratoare:lab04#accesul_la_spatiul_de_adresa_al_procesului|Accesul la spațiul de adresă al procesului]]. | Urmăriți în laboratorul 4 secțiunea [[:so2:laboratoare:lab04#accesul_la_spatiul_de_adresa_al_procesului|Accesul la spațiul de adresă al procesului]]. | ||
Line 274: | Line 315: | ||
</note> | </note> | ||
- | Pentru testare, va trebui să creați fișierul de tip caracter ''/dev/so2_kbd'' folosind utilitarul ''mknod'' cu majorul și minorul dispozitivului sunt definiți în macro-urile din ''so2_kbd.c'': ''SO2_KBD_MAJOR'' și ''SO2_KBD_MINOR''. Adică va trebui să rulați comanda<code> | + | Pentru testare, va trebui să creați fișierul de tip caracter ''/dev/kbd'' folosind utilitarul ''mknod'' cu majorul și minorul dispozitivului sunt definiți în macro-urile din ''kbd.c'': ''KBD_MAJOR'' și ''KBD_MINOR''. Adică va trebui să rulați comanda<code> |
- | mknod /dev/so2_kbd c 42 0 | + | mknod /dev/kbd c 42 0 |
</code> | </code> | ||
Compilați modulul pe sistemul fizic, apoi porniți mașina virtuală și încărcați modulul în nucleul mașinii virtuale. Testați folosind comanda<code> | Compilați modulul pe sistemul fizic, apoi porniți mașina virtuală și încărcați modulul în nucleul mașinii virtuale. Testați folosind comanda<code> | ||
- | cat /dev/so2_kbd | + | cat /dev/kbd |
</code> | </code> | ||
O să obțineți conținutul bufferului keylogger-ului din kernel space. | O să obțineți conținutul bufferului keylogger-ului din kernel space. | ||
- | ==== 5. [3p] Curățarea buffer-ului dacă este tastată o parolă ==== | + | ==== 5. [1.5p] Curățarea buffer-ului la scriere ==== |
- | Dorim să %%"curățăm"%% buffer-ul dacă este tastată o parolă. În acel moment buffer-ul va fi resetat: conținutul său va fi resetat și dimensiunea va fi ''0''. | + | Dorim să %%"curățăm"%% buffer-ul atunci când se scrie în device node-ul asociat driver-ului. În acel moment buffer-ul va fi resetat: conținutul său va fi resetat și dimensiunea va fi ''0''. |
- | Implementarea o veți face în fișierul ''so2_kbd.c''. În rutina de tratare a întreruperii veți reține în câmpul ''passcnt'' câte caractere din parolă au fost potrivite. Parola este definită de macro-urile ''MAGIC_WORD'' și ''MAGIC_WORD_LEN''. | + | Implementarea o veți face în fișierul ''kbd.c''. Urmăriți comentariile marcate cu ''TODO 5''. Implementați funcția ''reset_buffer'' și adăugați operația ''write'' la ''kbd_fops''. |
- | + | ||
- | Când se detectează întreaga parolă, curătați buffer-ul. Pentru curățare, reinițializați câmpul ''buf_idx'' la valoarea ''0'' și umpleți cu ''0''-uri buffer-ul. | + | |
- | + | ||
- | La primirea unui caracter nou, verificați dacă acesta corespunde cu caracterul de pe poziția curentă din parolă. În caz afirmativ, incrementați contorul; altfel, resetați counterul la 0. | + | |
<note tip> | <note tip> | ||
- | În momentul resetării buffer-ului, în handler-ul de întrerupere va trebui să folosiți spinlock-ul pentru accesul exclusiv la buffer. Este posibil ca rutina de read să fie procesată de pe alt procesor și să acceseze buffer-ul în momentul în care handler-ul de întrerupere îl modifică. Veți folosi funcțiile [[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L291|spin_lock]] și [[http://lxr.free-electrons.com/source/include/linux/spinlock.h?v=3.13#L331|spin_unlock]]. | + | În momentul resetării buffer-ului, va trebui să folosiți spinlock-ul cu dezactivarea întreruperilor pentru accesul exclusiv la buffer. |
Parcurgeți secțiunea [[:so2:laboratoare:lab05#utilizare_spinlock-uri|Utilizarea spinlock-uri]]. | Parcurgeți secțiunea [[:so2:laboratoare:lab05#utilizare_spinlock-uri|Utilizarea spinlock-uri]]. | ||
</note> | </note> | ||
- | Pentru testare, va trebui să creați fișierul de tip caracter ''/dev/so2_kbd'' folosind utilitarul ''mknod'' cu majorul și minorul dispozitivului sunt definiți în macro-urile din ''so2_kbd.c'': ''SO2_KBD_MAJOR'' și ''SO2_KBD_MINOR''. Adică va trebui să rulați comanda<code> | + | Pentru testare, va trebui să creați fișierul de tip caracter ''/dev/kbd'' folosind utilitarul ''mknod'' cu majorul și minorul dispozitivului sunt definiți în macro-urile din ''kbd.c'': ''KBD_MAJOR'' și ''KBD_MINOR''. Adică va trebui să rulați comanda<code> |
- | mknod /dev/so2_kbd c 42 0 | + | mknod /dev/kbd c 42 0 |
</code> | </code> | ||
Compilați modulul pe sistemul fizic, apoi porniți mașina virtuală și încărcați modulul în nucleul mașinii virtuale. Consultați conținutul buffer-ului folosind comanda<code> | Compilați modulul pe sistemul fizic, apoi porniți mașina virtuală și încărcați modulul în nucleul mașinii virtuale. Consultați conținutul buffer-ului folosind comanda<code> | ||
- | cat /dev/so2_kbd | + | cat /dev/kbd |
</code> | </code> | ||
- | Apăsați pe taste și apoi apăsați conținutul macro-ului ''MAGIC_WORD'' (adică ''root'') și apoi consultați din nou conținutul buffer-ului. Ar trebui să fie resetat. | + | Apăsați pe taste și apoi rulați comanda ''echo "clear" > /dev/kbd'' și apoi consultați din nou conținutul buffer-ului. Ar trebui să fie resetat. |
===== Extra ===== | ===== Extra ===== | ||
- | |||
- | ==== [1 karma] Resetarea buffer-ului în rutina de read ==== | ||
- | |||
- | Implementarea curentă este problematică pentru că rutina de tratare a întreruperii este cea care face resetarea buffer-ului, lucru care este consumator de timp și nerecomandat să fie făcut în context întrerupere. | ||
- | |||
- | O soluție este să actualizăm comportamentul astfel încât resetarea buffer-ului să se producă la apelul funcției ''so2_kbd_read()''. După ce informația este copiată în user space, buffer-ul este resetat. Nu mai resetăm în momentul tastării | ||
- | |||
- | Simplificați implementarea în așa fel încât resetarea buffer-ului să se producă în funcția ''so2_kbd_read()'' imediat după copierea datele în user space. | ||
- | |||
- | <note tip> | ||
- | În momentul în care copiați datele în buffer-ul temporare ''tmp_buf'' resetați buffer-ul principal: adică umpleți buffer-ul ''buf'' cu zero-uri și inițialiați ''buf_idx'' la ''0''. | ||
- | </note> | ||
- | |||
- | Pentru verificarea după citirea din ''/dev/so2_kbd'' veți avea buffer-ul resetat. Citiri ulterioare vor găsi buffer-ul gol. | ||
==== [3 karma] Utilizare kfifo ==== | ==== [3 karma] Utilizare kfifo ==== | ||
- | Implementați colectarea de date de la tastatură, și transmiterea lor în user space folosind [[http://lxr.free-electrons.com/source/include/linux/kfifo.h?v=3.13|API-ul kfifo]]. | + | Implementați colectarea de date de la tastatură, și transmiterea lor în user space folosind [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/kfifo.h|API-ul kfifo]]. |
<note tip> | <note tip> | ||
- | Folosiți [[http://lxr.free-electrons.com/source/samples/kfifo/?v=3.13|exemplele de utilizare a API-ului kfifo]] din codul nucleului. De exemplu [[http://lxr.free-electrons.com/source/samples/kfifo/bytestream-example.c?v=3.13|bytestream-example.c]]. | + | Folosiți [[https://elixir.bootlin.com/linux/v4.15/source/samples/kfifo|exemplele de utilizare a API-ului kfifo]] din codul nucleului. De exemplu [[https://elixir.bootlin.com/linux/v4.15/source/samples/kfifo/bytestream-example.c|bytestream-example.c]]. |
</note> | </note> | ||