Differences

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

Link to this comparison view

so2:laboratoare:lab02 [2018/03/04 14:24]
anda.nicolae [Exemplu de kernel oops]
so2:laboratoare:lab02 [2020/02/20 14:16] (current)
ionel.ghita
Line 15: Line 15:
   * printk, dyndbg   * printk, dyndbg
   * objdump, addr2line, netconsole   * objdump, addr2line, netconsole
-  * KDB, Kprobes, Jprobes, Kretprobes +  * KDB
 ===== Materiale ajutătoare ===== ===== Materiale ajutătoare =====
  
Line 120: Line 119:
 Sufixul țintelor din Kbuild determină modul în care sunt folosite, astfel: Sufixul țintelor din Kbuild determină modul în care sunt folosite, astfel:
   *m (module) reprezintă o țintă pentru module de kernel încărcabile   *m (module) reprezintă o țintă pentru module de kernel încărcabile
-  *y (yes) reprezintă o țintă pentru fișiere obiect ce vor fi compilate ca apoi să fie linkate în cadrul unui modul ($(nume_modul)-y) sau în cadrul kernel-ului (obj-y)+  *y (yes) reprezintă o țintă pentru fișiere obiect ce vor fi compilate ca apoi să fie linkate în cadrul unui modul ''​($(nume_modul)-y)'' ​sau în cadrul kernel-ului ​''​(obj-y)''​
   *orice alt sufix de țintă va fi ignorat de Kbuild și nu va fi compilat   *orice alt sufix de țintă va fi ignorat de Kbuild și nu va fi compilat
  
Line 512: Line 511:
  
 <note important>​ <note important>​
-Pentru a afișa mesajele trimise cu ''​printk''​ în user space, trebuie ca nivelul folosit la apelul ''​printk''​ să fie prioritar valorii variabilei [[http://lxr.free-electrons.com/​source/​include/​linux/​printk.h?v=3.13#L38|console_loglevel]] (( Nivelul implicit setat pe consolă se poate configura din [[http://​koala.cs.pub.ro/​lxr/#​linux26/​Documentation/​sysctl/​kernel.txt#​L261 | /​proc/​sys/​kernel/​printk]];​ spre exemplu, comanda ''​echo 8 > /​proc/​sys/​kernel/​printk''​ va face ca toate mesajele din kernel să fie afișate la consolă )). Adică nivelul de logging să fie mai mic strict decât nivelul variabilei [[http://lxr.free-electrons.com/​source/​include/​linux/​printk.h?v=3.13#L38|console_loglevel]]. De exemplu, dacă variabila ''​console_loglevel''​ are valoarea ''​5''​ (specifică valorii ''​KERN_NOTICE''​) doar mesajele cu loglevel-ul mai mic strict decât ''​5''​ (adică ''​0'',​ ''​1'',​ ''​2'',​ ''​3''​ și ''​4'',​ adică ''​KERN_EMERG'',​ ''​KERN_ALERT'',​ ''​KERN_CRIT'',​ ''​KERN_ERR'',​ ''​KERN_WARNING''​) vor fi afișate (( http://​elinux.org/​Debugging_by_printing )).+Pentru a afișa mesajele trimise cu ''​printk''​ în user space, trebuie ca nivelul folosit la apelul ''​printk''​ să fie prioritar valorii variabilei [[https://elixir.bootlin.com/​linux/​v4.15.7/​source/​include/​linux/​printk.h#​L65|console_loglevel]] (( Nivelul implicit setat pe consolă se poate configura din [[http://​koala.cs.pub.ro/​lxr/#​linux26/​Documentation/​sysctl/​kernel.txt#​L261 | /​proc/​sys/​kernel/​printk]];​ spre exemplu, comanda ''​echo 8 > /​proc/​sys/​kernel/​printk''​ va face ca toate mesajele din kernel să fie afișate la consolă )). Adică nivelul de logging să fie mai mic strict decât nivelul variabilei [[https://elixir.bootlin.com/​linux/​v4.15.7/​source/​include/​linux/​printk.h#​L65|console_loglevel]]. De exemplu, dacă variabila ''​console_loglevel''​ are valoarea ''​5''​ (specifică valorii ''​KERN_NOTICE''​) doar mesajele cu loglevel-ul mai mic strict decât ''​5''​ (adică ''​0'',​ ''​1'',​ ''​2'',​ ''​3''​ și ''​4'',​ adică ''​KERN_EMERG'',​ ''​KERN_ALERT'',​ ''​KERN_CRIT'',​ ''​KERN_ERR'',​ ''​KERN_WARNING''​) vor fi afișate (( http://​elinux.org/​Debugging_by_printing )).
 </​note>​ </​note>​
  
Line 553: Line 552:
 === Dynamic debugging === === Dynamic debugging ===
  
-Depanarea dinamică [[https://​www.kernel.org/​doc/​Documentation/​dynamic-debug-howto.txt | dyndbg]] permite activarea/​dezactivarea în mod dinamic a mesajelor de debug. Spre deosebire de ''​printk'',​ oferă opțiuni mai avansate de filtrare a mesajelor pe care dorim să le afișăm - foarte util în cazul unor module complexe sau pentru depanarea subsistemelor. Astfel, se reduce semnificativ cantitatea de mesaje afișate, rămânând doar cele relevante pentru contextul depanării. Pentru activarea dyndbg, kernelul trebuie compilat cu opțiunea ''​CONFIG_DYNAMIC_DEBUG''​. Odata configurat, apelurile ''​pr_debug()'',​ ''​dev_dbg()''​ și ''​print_hex_dump_debug()'',​ ''​print_hex_dump_bytes()''​ pot fi activate dinamic per callsite.+Depanarea dinamică [[https://​www.kernel.org/​doc/​html/​v4.15/​admin-guide/​dynamic-debug-howto.html | dyndbg]] permite activarea/​dezactivarea în mod dinamic a mesajelor de debug. Spre deosebire de ''​printk'',​ oferă opțiuni mai avansate de filtrare a mesajelor pe care dorim să le afișăm - foarte util în cazul unor module complexe sau pentru depanarea subsistemelor. Astfel, se reduce semnificativ cantitatea de mesaje afișate, rămânând doar cele relevante pentru contextul depanării. Pentru activarea dyndbg, kernelul trebuie compilat cu opțiunea ''​CONFIG_DYNAMIC_DEBUG''​. Odata configurat, apelurile ''​pr_debug()'',​ ''​dev_dbg()''​ și ''​print_hex_dump_debug()'',​ ''​print_hex_dump_bytes()''​ pot fi activate dinamic per callsite.
  
 Pentru filtrarea mesajelor sau vizualizarea filtrelor existente se va folosi fișierul ''/​sys/​kernel/​debug/​dynamic_debug/​control''​ din cadrul sistemului de fișiere ''​debugfs''​ (unde ''/​sys/​kernel/​debug''​ este calea la care a fost montat debugfs). Pentru filtrarea mesajelor sau vizualizarea filtrelor existente se va folosi fișierul ''/​sys/​kernel/​debug/​dynamic_debug/​control''​ din cadrul sistemului de fișiere ''​debugfs''​ (unde ''/​sys/​kernel/​debug''​ este calea la care a fost montat debugfs).
Line 639: Line 638:
 # revenire din KDB # revenire din KDB
 kdb> go kdb> go
-</​code>​ 
- 
-==== Tracing ==== 
- 
-=== Kprobes === 
- 
-''​Kprobes''​ permite instrumentarea dinamică a oricărei funcții din kernel pentru a colecta informații de debug într-un mod cât mai puțin invaziv. Cu kprobe, putem seta breakpoints la aproape orice adresă din kernel și configura handlere ce vor fi invocate în aceste puncte. Pot fi folosite trei tipuri de probe: ''​Kprobes'',​ ''​Jprobes''​ și ''​Kretprobes''​ (sau probe de return). 
-  - O ''​Kprobe''​ poate fi inserată la orice adresă (instrucțiune) din kernel și putem specifica handlere ce vor fi apelate înainte sau după execuția instrucțiunii. 
-  - ''​Jprobe''​ va fi adăugată la entry-pointul unei funcții, utilă pentru a monitoriza argumentele funcțiilor. 
-  - O probă de return (''​Kretprobe''​) va fi apelată la întoarcerea dintr-o funcție, unde putem analiza rezultatul întors de aceasta. 
- 
-De cele mai multe ori se va realiza instrumentarea cu kprobe prin intermediul unui modul. Funcția module_init va instala una sau mai multe probe care vor fi dezactivate apoi în module_exit. Înregistrarea unei probe (e.x. ''​register_kprobe()''​) presupune specificarea adresei la care aceasta va fi inserată și a unui handler ce va fi apelat când se ajunge la adresa setată. Pentru mai multe detalii despre modul de funcționare ''​Kprobe'',​ ''​Jprobe''​ sau ''​Kretprobe'',​ expandați secțiunea de mai jos. 
- 
-<spoiler onHidden="​Afișați detalii implementare Kprobe"​ onVisible="​Ascundeți detalii implementare Kprobe"​ > 
- 
-**Kprobe** 
- 
-  - La înregistrarea unei probe, ''​Kprobes''​ face o copie a instrucțiunii de la adresa specificată (instrucțiunea pe care dorim să o instrumentăm) și înlocuiește primii bytes cu o instrucțiune de breakpoint (e.g. int3). 
-  - Când procesorul ajunge la instrucțiunea de breakpoint, se execută un trap și controlul revine la ''​Kprobes''​ (printr-un mecanism de tipul [[http://​lwn.net/​Articles/​160953/​ | notifier_call_chain]]). În acest punct, ''​Kprobes''​ va executa pre_handler-ul asociat probei. 
-  - După pre-handler,​ Kprobes execută instrucțiunea instrumentată,​ urmată post_handler-ul asociat. 
-  - Execuția continuă cu următoarea instrucțiune după cea instrumentată. 
- 
-**Jprobe** 
- 
-JProbe este implementat prin inserarea unei kprobe la entry-pointul funcției. Folosește un mecanism de mirroring pentru a permite accesul la argumentele funcției instrumentate. Se așteaptă ca handler-ul înregistrat:​ 
-  * să aibă aceeași semnătură (lista de argumente și return type) ca funcția instrumentată. 
-  * să se termine mereu cu apelarea funcției ''​jprobe_return()''​. 
- 
-Când se ajunge la un breakpoint, ''​Kprobes''​ face o copie a registrelor și a stivei, setând apoi instruction pointerul către handlerul înregistrat. Astfel, handlerul va fi apelat în aceeași stare (registre și stivă) ca funcția instrumentată. La ieșirea din handler, ''​jprobe_return()''​ restaurează conținutul inițial al stivei și al registrelor și continuă execuția cu funcția instrumentată. 
- 
-**Kretprobe** 
- 
-La înregistrarea unei probe de return, ''​Kprobes''​ instalează un kprobe la intrarea în funcția instrumentată. Acest kprobe are rolul de a salva o copie a adresei de return și de a înlocui adresa de return cu adresa unei porțiuni aleatoare de cod - o "​trambulină"​ (e.x. o instrucțiune nop). Astfel, funcția instrumentată se va întoarce în codul din "​trambulină"​. 
- 
-La inițializare,​ ''​Kprobes''​ setează un breakpoint la adresa acestei "​trambuline"​. La acest breakpoint se va apela handlerul asociat kretprobe, si se va reseta IP-ul la adresa de return salvată anterior (adresa de return inițială a funcției instrumentate). 
- 
-{{:​so2:​wiki:​kretprobe.png?​700X297}} 
- 
-</​spoiler>​ 
- 
-\\ 
- 
-** Înregistrare Kprobe ** 
- 
-Pentru înregistrarea unei ''​Kprobe''​ se va folosi o structură de tipul [[http://​lxr.free-electrons.com/​source/​include/​linux/​kprobes.h#​L73 | struct kprobe]]. Aici putem seta ''​addr''​ - adresa la care se va insera proba, ''​pre_handler''/''​post_handler'',​ metode ce vor fi apelate înainte/​după executarea instrucțiunii de la ''​addr''​ și ''​fault_handler''​ ce va fi apelat dacă apare un fault în handlerele pre/post sau la execuția instrucțiunii instrumentate. Adresa la care vrem să inserăm proba se poate specifica fie direct, fie folosind funcția [[http://​lxr.free-electrons.com/​source/​kernel/​kallsyms.c?​v=2.6.33#​L170 | kallsyms_lookup_name]] ce returnează adresa unui simbol primit ca parameru. 
- 
-<code c> 
-struct kprobe { 
-     /* location of the probe point */ 
-     ​kprobe_opcode_t *addr; 
-     /* Called before addr is executed. */ 
-     ​kprobe_pre_handler_t pre_handler;​ 
-     /* Called after addr is executed, unless... */ 
-     ​kprobe_post_handler_t post_handler;​ 
-     /* Called if executing addr or handlers causes a fault (eg. page fault). */ 
-     ​kprobe_fault_handler_t fault_handler;​ 
-     ... 
-} 
-</​code>​ 
- 
-Pentru înregistrarea/​deînregistrarea unei probe se vor apela funcțiile [[http://​lxr.free-electrons.com/​source/​kernel/​kprobes.c#​L1472 | register_kprobe]],​ respectiv [[http://​lxr.free-electrons.com/​source/​kernel/​kprobes.c#​L1577 | unregister_kprobe]]. Mai jos puteți urmări un exemplu în care este inițializată o probă de tipul ''​Kprobe''​. 
- 
-<code c> 
-static struct kprobe kp; 
-  
-/* kprobe pre_handler:​ called just before the probed instruction is executed */ 
-int handler_pre(struct kprobe *p, struct pt_regs *regs); 
-  
-/* kprobe post_handler:​ called after the probed instruction is executed */ 
-void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags); 
-  
-/* fault_handler:​ this is called if an exception is generated for any 
- * instruction within the pre- or post-handler,​ or when Kprobes 
- * single-steps the probed instruction. 
- */ 
-int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr); 
-  
-int init_module(void) 
-{ 
-         ​kp.pre_handler = handler_pre;​ 
-         ​kp.post_handler = handler_post;​ 
-         ​kp.fault_handler = handler_fault;​ 
-         ​kp.addr = (kprobe_opcode_t*) probe_addr; 
-  
-         if ((ret = register_kprobe(&​kp) < 0)) { 
-                 ​printk("​register_kprobe failed, returned %d\n", ret); 
-                 ​return -1; 
-         } 
-         ​printk("​kprobe registered\n"​);​ 
-         ​return 0; 
-} 
- 
-</​code>​ 
- 
-** Înregistrare Jprobe ** 
- 
-Pentru ''​Jprobe'',​ vom folosi o structură de tipul [[http://​lxr.free-electrons.com/​source/​include/​linux/​kprobes.h?​v=2.6.39#​L158 | struct jprobe]], al cărei câmp ''​entry''​ determină adresa handlerului ce va fi apelat la intrarea în funcția instrumentată. Pentru înregistrare/​deînregistrare se vor folosi [[http://​lxr.free-electrons.com/​source/​kernel/​kprobes.c#​L1703 | register_jprobe]]/​[[http://​lxr.free-electrons.com/​source/​kernel/​kprobes.c#​L1740 |unregister_jprobe]]. În exemplul de mai jos se folosește un modul pentru a monitoriza apelurile funcției ''​do_execveat_common''​. 
- 
-<note important>​ 
-  * Semnătura handlerului trebuie să fie aceeași cu semnătura funcției instrumentate. 
-  * Nu uitați să apelați jprobe_return() la ieșirea din handler. 
-</​note>​ 
- 
-<file C kprobes.c>​ 
-#include <​linux/​module.h>​ 
-#include <​linux/​init.h>​ 
-#include <​linux/​kernel.h>​ 
-#include <​linux/​kprobes.h>​ 
-#include <​linux/​kallsyms.h>​ 
- 
-MODULE_DESCRIPTION("​Probes module"​);​ 
-MODULE_AUTHOR("​So2rul Esforever"​);​ 
-MODULE_LICENSE("​GPL"​);​ 
- 
-/* 
- * Pre-entry point for do_execve. 
- */ 
-static int my_do_execveat_common(int fd, struct filename * filename, 
- char __user *__user *argv, 
- char __user *__user *envp) 
-{ 
- pr_info("​%s(%s) [%s] \n", __func__, filename->​name,​ current->​comm);​ 
- /* Always end with a call to jprobe_return(). */ 
- jprobe_return();​ 
- /​*NOTREACHED*/​ 
- return 0; 
-} 
- 
-static struct jprobe my_jprobe = { 
- .entry = (kprobe_opcode_t *) my_do_execveat_common 
-}; 
- 
-static int my_probe_init(void) 
-{ 
- int ret; 
- 
- my_jprobe.kp.addr = 
- (kprobe_opcode_t *) kallsyms_lookup_name("​do_execveat_common"​);​ 
- if (my_jprobe.kp.addr == NULL) { 
- pr_err("​Couldn'​t find %s to plant jprobe\n",​ "​do_execveat_common"​);​ 
- return -1; 
- } 
- 
- ret = register_jprobe(&​my_jprobe);​ 
- if (ret < 0) { 
- pr_err("​register_jprobe failed, returned %d\n", ret); 
- return -1; 
- } 
- pr_info("​Planted jprobe at %p, handler addr %p\n", 
- my_jprobe.kp.addr,​ my_jprobe.entry);​ 
- 
- return 0; 
-} 
- 
-static void my_probe_exit(void) 
-{ 
- unregister_jprobe(&​my_jprobe);​ 
- pr_info("​jprobe unregistered\n"​);​ 
-} 
- 
-module_init(my_probe_init);​ 
-module_exit(my_probe_exit);​ 
-</​file>​ 
- 
-** Înregistrare Kretprobe ** 
- 
-''​Kretprobes''​ se vor insera într-un mod similar, folosind o structură de tipul [[http://​lxr.free-electrons.com/​source/​include/​linux/​kprobes.h#​L184 | struct kretprobe]] și funcțiile [[http://​lxr.free-electrons.com/​source/​kernel/​kprobes.c#​L1811 | register_kretprobe]],​ [[http://​lxr.free-electrons.com/​source/​kernel/​kprobes.c#​L1882 | unregister_kretprobe]]. 
- 
-<code c> 
- 
-static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs); 
-  
-static struct kretprobe my_kretprobe = { 
-         ​.handler = ret_handler,​ 
-}; 
- 
 </​code>​ </​code>​
  
so2/laboratoare/lab02.txt · Last modified: 2020/02/20 14:16 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