Differences

This shows you the differences between two versions of the page.

Link to this comparison view

so2:laboratoare:lab05:exercitii [2016/05/02 19:15]
razvan.deaconescu [4. [2p] Citirea bufferului printr-un apel ''read'']
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ăș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:​~/​so2tree 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-vm/fsimg/​root/​modules+ 
-</​code> ​unde ''​/​path/​to/​module.ko'' ​este calea către fișierul obiect aferent modulului de kernelApoi vom porni, din directorul ''​~/so2/qemu-vm/'', ​mașina virtuală ​QEMU folosind comanda<code bash> +<code bash> 
-student@eg106:~/so2/qemu-vm$ 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 virtualePentru 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>​
 Pentru dezvoltarea laboratorului,​ este recomandat să folosim patru terminale sau, mai bine, patru 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: Pentru dezvoltarea laboratorului,​ este recomandat să folosim patru terminale sau, mai bine, patru 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:
   - Î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 decomprimării arhivei de sarcini a laboratorului.   - Î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 decomprimării arhivei de sarcini a laboratorului.
-  - Î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/​qemu-vm/''​. +  - Î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/​qemu-so2/''​. 
-  - În al treilea tab de terminal accesăm sursele nucleului Linux din directorul ''​~/​so2/​linux-3.13/''​ și folosim Vim și [[:​so2:​laboratoare/​lab01#​cscope|cscope]] pentru parcurgerea codului sursă. +  - În al treilea tab de terminal accesăm sursele nucleului Linux din directorul ''​~/​so2/​linux-4.9/''​ și folosim Vim și [[:​so2:​laboratoare/​lab01#​cscope|cscope]] pentru parcurgerea codului sursă. 
-  - În al treilea tab de terminal pornim un server UDP care să primească [[:​so2:​laboratoare:​lab02#​netconsole|mesajele de netconsole]]. Nu contează în ce director ne aflăm. Folosim comanda<​code bash>+  - În al treilea tab de terminal pornim ​[[:​so2:​laboratoare:​lab02#​minicom|minicom]] sau un server UDP care să primească [[:​so2:​laboratoare:​lab02#​netconsole|mesajele de netconsole]]. Nu contează în ce director ne aflăm. Folosim comanda<​code bash>
 student@eg106:​~$ netcat -lup 6666 student@eg106:​~$ netcat -lup 6666
 </​code>​ </​code>​
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 ​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 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 nucleuVerificaț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%dscancode=0x%x (%u%c)\n", + pr_info("IRQ %dscancode=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 șdimensiunea acestuia sunt indicate de câmpurile ​''​tmp_buf'' ​și ''​tmp_buf_idx'' ​din structura de tip dispozitiv.+ 
 +<​note ​warning
 +Nu puteț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 potriviteParola 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>​
  
so2/laboratoare/lab05/exercitii.1462205747.txt.gz · Last modified: 2016/05/02 19:15 by razvan.deaconescu
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