internetul Windows. Android

Linux - SysCalls. Apeluri de sistem în Linux

Acest material este o modificare a articolului de la Vladimir Meshkov, publicată în revista "Administrator de sistem"

Acest Matreal este copii ale articolelor din Vladimir Meshkov cu Zhuranala "Administrator de sistem". Aceste articole pot fi găsite în conformitate cu legăturile de mai jos. Câteva exemple ale textelor sursă ale programelor au fost modificate, îmbunătățite, modificate. (Exemplul 4.2 este puternic modificat, deoarece a trebuit să interceptați un adresă de sistem ușor diferită) URL: http://www.samag.ru/img/uploadd/p.pdf http://www.samag.ru/img/uploadd / A3. PDF.

Aveți întrebări? Atunci tu aici: [E-mail protejat]

  • 2. Modul de kernel descărcabil
  • 4. Exemple de interceptare a apelurilor de sistem bazate pe LKM
    • 4.1 Interzicerea creației de catalog

1. General Wang pe arhitectura Linux

Cel mai comun val vă permite să vedeți un model de sistem pe două niveluri. nucleu<=> Progs în centru (stânga) localizat nucleul sistemului. Kernelul interacționează direct, cu o parte hardware a computerului, izolând programe aplicate de la caracteristicile arhitecturii. Kernel-ul are un set de servicii oferite de aplicații. Serviciile de kernel includ operațiunile de intrare / ieșire (deschidere, citire, scriere și gestionare fișiere), crearea și gestionarea proceselor, sincronizării și interacțiunii interprocesare. Toate aplicațiile solicită servicii de kernel prin apeluri de sistem.

Al doilea nivel este aplicațiile sau sarcinile, atât funcționalitatea și aplicarea sistemului, care determină interfața utilizatorului Linux. Cu toate acestea, în ciuda eterogenității externe a aplicațiilor, schemele de interacțiune cu kernelul sunt aceleași.

Interacțiunea cu kernelul are loc prin interfața standard de apel de sistem. Interfața apelului de sistem este un set de servicii de kernel și definește formatul solicitărilor de servicii. Procesul solicită serviciul prin intermediul sistemului Apelarea unei proceduri specifice de nucleu, similară cu cea mai obișnuită la funcția de bibliotecă. Kernel-ul în numele procesului efectuează o cerere și returnează datele necesare procesului.

În exemplu, programul deschide fișierul, citește datele de la acesta și închide acest fișier. În același timp, operația de deschidere (deschisă), citiți (citiți) și închiderea fișierului este efectuată de kernel la cererea sarcinii și funcția Open (2), citită (2) și închideți (2 ) Funcția sunt apelurile de sistem.

/ * Sursa 1.0 * / #include Principal () (int fd; char buf; / * Deschideți fișierul - obținem un link (descriptor de fișiere) fd * / fd \u003d deschis ("fișier1", o_rdonly); / * Considerăm la Buffer Buf 80 Caractere * / Citiți (FD, BF, Sizeof (BUF)); / * Închideți fișierul * / Închidere (FD);) / * EOF * / Lista completă a sistemului OS Linux Apeluri de a fi în /usr/include/asm/unistd.h fişier. Să luăm în considerare acum mecanismul de efectuare a apelurilor de sistem acest exemplu. Compilator, după ce a întâmpinat funcția Open () pentru a deschide fișierul, îl convertește la un cod de asamblare, furnizând numărul de apel al sistemului corespunzător acestei funcții, iar parametrii acestuia către registrele procesorului și apelarea ulterioară a întreruperii 0x80. Următoarele valori sunt încărcate în registrele procesorului:

  • registrul EAX este un număr de apel al sistemului. Deci, pentru ocazia noastră, numărul apelului de sistem este de 5 (vezi __nr_open).
  • registrul EBX este primul parametru de funcții (pentru Open () este un pointer la un șir care conține numele fișierului deschis.
  • În Registrul ECX - al doilea parametru (drepturile de acces al fișierelor)
Al treilea parametru este încărcat în registrul EDX, în acest caz lipsește. Pentru a efectua apelul sistemului în OS Linux, se utilizează funcția System_Call, care este definită (în funcție de arhitectura în acest caz i386) în fișierul / brevet / Blingux/i386/Src/entry.s. Această caracteristică este punctul de intrare pentru toate apelurile de sistem. Kernel-ul reacționează la întreruperea 0x80 prin referire la funcția System_Call, care, de fapt, este un handler de întrerupere 0x80.

Pentru a vă asigura că suntem pe drumul cel bun, luați în considerare codul Open () în Biblioteca Sistemului Libc:

# Gdb -q /lib/libc.so.6 (GDB) disas deschis de asamblare cod pentru Funcție deschisă: 0x000c8080 : Sunați la 0x1082be.< __i686.get_pc_thunk.cx > 0x000c8085. : Adăugați $ 0x6423B,% ECX 0x000C808B : CMPL $ 0x0.0x1a84 (% ecx) 0x000c8092 : Jne 0xc80b1. 0x000c8094. : Push% ebx 0x000c8095 : MOV 0x10 (% esp, 1),% EDX 0x000C8099 : MOV 0xC (% ESP, 1),% ECX 0x000C809D : MOV 0x8 (% esp, 1),% EBX 0x000C80A1 : MOV $ 0x5,% EAX 0x000C80A6 : INT $ 0x80 ... Nu este greu de observat în ultimele rânduri, parametrii sunt transferați în registrele EDX, ECX, EBX, iar numărul de apel al sistemului este plasat în ultimul registru eAX, așa cum deja știm 5.

Și acum să ne întoarcem la luarea în considerare a mecanismului de apel al sistemului. Deci, kernelul numește un handler de întrerupere 0x80 - funcția System_Call. SYSTEM_CALL plasează copii ale registrelor care conțin parametrii de apel din Stek utilizând Macro Save_all și comanda de apel determină funcția de sistem dorită. Tabelul de indicator al funcțiilor kernelului care implementează apelurile sistemului este localizat în matricea SyS_Call_table (vezi ARCH / I386 / Kernel / Entry.S). Numărul apelului de sistem care se află în Registrul EAX este un indice în această matrice. Astfel, dacă există o valoare de 5 în EAX, funcția de kernel sys_open () va fi cauzată. De ce aveți nevoie de o macro Save_all? Explicația de aici este foarte simplă. Deoarece aproape toate funcțiile sistemului de kernel sunt scrise pe C, ei caută parametrii lor în stivă. Și parametrii sunt plasați pe stivă folosind Save_all! Valoarea returnată de apelul este stocată în Registrul EAX.

Acum, să aflăm cum să interceptăm apelul de sistem. Ne va ajuta în acest mecanism de module de kernel încărcate.

2. Modul de kernel descărcabil

Modul de kernel descărcabil (reducerea în general acceptată a modulului kernel LKM) - codul programului efectuat în spațiul de kernel. Caracteristica principală LKM este capacitatea de încărcare și descărcare dinamică fără a fi nevoie să reporniți întregul sistem sau de corelarea miezului.

Fiecare LKM este alcătuit din două funcții de bază (minim):

  • funcția de inițializare a modulului. Chemat când se încarcă LKM în memorie: INT INITOR_MODULE (VOID) (...)
  • funcția de descărcare a modulului: VOID CLEANUP_MODLE (VOID) (...)
Să dăm un exemplu de cel mai simplu modul: / * sursa 2.0 * / #include INT INITOR_MODULE (VOID) ("HELLO WORLD \\ N"); RETURN 0;) VOID CLEANUP_MODULE (VOD) (imprimk ("BYE \\ N");) / * EOF * / Compilează și încărcați modulul. Descărcarea modulului în memorie este efectuată de comanda INSMOD și vizualizați modulele încărcate prin comanda LSMOD: # GCC -C -DMODOLE -I / USR / SRC / Linux / Include / SRC-2.0.c / Linux / Include / Include / Include SRC-2.0.C # INSMOD SRC-2.0.O Avertisment: Încărcarea SRC-2.0 .O va afecta kernelul: Nu este încărcat modul de licență SRC-2.0 încărcat, cu avertismente # dmesg | Coada -n 1 Hello World # LSMOD | GREP SRC SRC-2.0 336 0 (neutilizat) # RMMod SRC-2.0 # dmesg | Coada -n 1 paye

3. Algoritmul pentru apelul interceptat bazat pe LKM

Pentru a implementa un modul, interceptarea unui apel de sistem, este necesar să se determine algoritmul de interceptare. Algoritmul este următorul:
  • salvați indicatorul la provocarea originală (sursă) pentru ao restabili
  • creați o caracteristică care implementează un nou apel de sistem
  • În tabelul de apel SYS_CALL_TABLE, efectuați un apel de înlocuire, adică, configurați indicatorul corespunzător la un nou apel de sistem
  • la sfârșitul lucrării (la descărcarea modulului), restaurați apelul original utilizând un indicator salvat anterior.
Pentru a afla ce apeluri de sistem sunt utilizate de aplicația utilizator, permite urmărirea. Prin urmărire, puteți determina ce apel de sistem ar trebui să fie interceptat pentru a prelua controlul aplicației. # LTRACE -S ./SRC-1.0 ... Deschis ("File1", 0, 01 Sys_open ("File1", 0, 01) \u003d 3<... open resumed>) \u003d 3 citiți (3, Sys_read (3, "123 \\ n", 80) \u003d 4<... read resumed> "123 \\ n", 80) \u003d 4 Închide (3 Sys_close (3) \u003d 0<... close resumed>) \u003d 0 ... Acum avem suficiente informații pentru a începe să învățăm exemple de implementare a modulelor care interceptează apelurile sistemului.

4. Exemple de interceptare a apelurilor de sistem bazate pe LKM

4.1 Interzicerea creației de catalog

La crearea unui director, se numește funcția Kernel Sys_MKDIR. Ca parametru, șirul conținut de numele directorului creat. Luați în considerare codul care interceptează apelul corespunzător al sistemului. / * Sursa 4.1 * / #include #Include. #Include. / * Exportați tabelul de apel al sistemului * / extern vid * sys_call_table; / * Determinați indicatorul pentru a salva apelul original * / int (* orig_mkdir) (const char * calea); / * Creați propria provocare a sistemului. Apelul nostru nu face nimic, returnează doar valoarea zero * / int propriey_mkdir (const char * calea) (retur 0;) / * În timpul inițializării modulului, salvăm indicatorul la apelul original și înlocuim apelul sistemului * / INT_MODULE ( Void) (orig_mkdir \u003d sys_call_table; sys_call_table \u003d ovn_mkdir; imprimk ("sys_mkdir înlocuit \\ n"); returnați (0);) / * Când încărcați, restaurați apelul original * / VODUL CLEANUP_MODULE (SAYS_CALL_TABLE \u003d ORIG_MKDIR; Imprimk ("sys_mkdir mutat înapoi \\ n");) / * EOF * / Pentru a obține un modul obiect, vom executa următoarea comandă și vom efectua o serie de experimente pe sistem: # GCC -C -Dmodul -i / usr / SRC / Linux / Include / SRC-3.1.C # dmesg | Coada -n 1 sys_mkdir a fost înlocuit # testul MKDIR # LS-Tald Test LS: Test: Nu există un astfel de fișier sau director # RMMod SRC-3.1 # Dmesg | Coada -n 1 sys_mkdir sa mutat înapoi # Mkdir Test # LS -Ald Test Drwxr-XR-XR 2 Rădăcină rădăcină 4096 2003-12-23 03:46 Testați modul în care vă puteți asigura că comanda "Mkdir" nu funcționează sau mai degrabă nimic se întâmplă. Pentru a restabili performanța sistemului, este suficient să descărcați modulul. Ce se face mai sus.

4.2 Ascunderea intrării de fișiere în catalog

Definim ce apel de sistem este responsabil pentru citirea conținutului catalogului. Pentru a face acest lucru, scrieți un alt fragment de testare care se ocupă de directorul curent: / * sursa 4.2.1 * / #include #Include. INT Main () (Dir * D; struct DRYTOR * DP; D \u003d OpenDir ("."); DP \u003d Readdir (d); retur 0;) / * EOF * / Obținem un fișier executabil și alergând urmărirea: # GCC -O SRC-3.2.1 SRC-3.2.1.c # lTrace -s ./SRC-3.2.1 ... OpenDir ("" Sys_open (". \u003d 0x0806A5F4 sys_brk (null) \u003d 0x0806A5F4 sys_brk (0x0806b000) \u003d 0x0806b000<... opendir resumed>) \u003d 0x08049648 Readdir (0x08049648 SYS_GETDENTS64 (3, 0x08049678, 4096, 0x40014400, 0x4014C2C0) \u003d 528<... readdir resumed>) \u003d 0x08049678 ... ar trebui să acordați atenție ultimului șir. Conținutul directorului este citit de funcția Getdents64 (Getdents sunt posibile în alte nuclee). Rezultatul este menținut ca o listă a structurilor structuri ale structurilor, iar funcția însuși returnează lungimea tuturor intrărilor din director. Pentru noi, două domenii ale acestei structuri sunt interesante:
  • d_RECLEN - Dimensiune scriere
  • d_name - Numele fișierului
Pentru a ascunde fișierul de intrare a fișierului (cu alte cuvinte, pentru a fi invizibil), trebuie să interceptați apelul SYS_getDents64, să găsiți intrarea corespunzătoare și să o ștergeți în lista structurilor rezultate. Luați în considerare codul care efectuează această operațiune (autorul codului original - Michal Zalewski): / * sursa 4.2.2 * / #include #Include. #Include. #Include. #Include. #Include. #Include. #Include. extern vid * sys_call_table; int (* orig_getddens) (U_INT FD, STRUCTOR DIRENT * DIRP, Numărul U_INT); / * Determinați-vă sistemul de sistem * / int propriul_getddens (U_INT FD, Strut Directiv * Dirp, U_int Count) (nesemnat int tmp, n; int t; struct DINNT64 (int d_ino1, d_ino2; int d_off1, d_off2; nesemnat scurt d_reclen; nesemnat char d_type; char d_name;) * DIRP2, * DIRP3; / * Numele fișierului pe care dorim să-l ascundem * / char ascuns \u003d "File1"; / * Determinați lungimea intrărilor din directorul * / TMP \u003d (* orig_getddens) ( FD, DIRP, contor); dacă (TMP\u003e 0) (/ * Selectați memoria pentru structura în spațiul kernelului și copiați conținutul directorului * / DIRP2 \u003d (struct DIRENT64 *) KMALLOC (TMP, GFP_KERNEL); copy_from_user (DIRP2, DIRP, TMP); / * Folosim cea de-a doua structură și salvați valoarea lungimii de înregistrare în * / Dirp3 \u003d Dirp2; T \u003d Tmp3 \u003d Dirp2; t \u003d TMP; / * Să începem să căutăm pentru * / În timp ce fișierul (t\u003e 0) (/ * citiți prima lungime de înregistrare și determinați lungimea de înregistrare rămasă în directorul * / n \u003d dirp3-\u003e d_reclen; t- \u003d n; / * verificăm dacă numele fișierului nu a coincis de la Intrarea curentă cu * / dacă STRSTR ((Char *) & (Dirp3-\u003e D_name), (char *) & Ascundeți! \u003d null) (/ * dacă este cazul, purtați înregistrarea și calculați noua valoare a lungimii de înregistrare în directorul * / Memcpy (DIRP3, (char *) Dirp3 + Dirp3-\u003e D_RECLEN, T); Tmp - \u003d n; ) / * Poziționați indicatorul la următoarea intrare și continuați căutarea * / Dirp3 \u003d (struct DIRT64 *) ((char *) Dirp3 + Dirp3-\u003e D_RECLEN); ) / * Revenim rezultatul și eliberăm memoria * / copy_to_user (DIRP, DIRP2, TMP); Kfree (Dirp2); ) / * Returnați valoarea lungimii de înregistrare în directorul * / Return TMP; ) / * Funcțiile de inițiere și descărcare ale modulului au o vizualizare standard * / INT_MODULE (VOID) (orig_getdents \u003d sys_call_table; sys_call_table \u003d Ovn_getDents; retur 0;) VOID CLEANUP_MODULE () (sys_call_table \u003d origine_getddens;) / * EOF * / Compiling Acest cod, rețineți cum dispare "File1", care trebuia să dovedească.

5. Metoda de acces direct / Dev / KMEM Metoda de acces direct

Luați în considerare primul teoretic, cum să interceptați metoda de acces direct la spațiul de adrese al kernelului și apoi să procedați la implementarea practică.

Accesul direct la spațiul de adrese al kernelului oferă fișierul dispozitiv / dev / kmem. Acest fișier afișează toate spațiul de adresă virtuală disponibil, inclusiv swap-urile (zona swap). Pentru a lucra cu fișierul KMEM, funcțiile standard ale sistemului sunt utilizate - Open (), citiți (), scrieți (). Deschiderea metoda standard / dev / kmem, ne putem referi la orice adresă din sistem, setând-o ca o schimbare în acest fișier. Această metodă a fost dezvoltată de Silvio Cesare.

Accesarea funcțiilor sistemului se efectuează prin încărcarea parametrilor funcției la registrele procesorului și apelul ulterior la întreruperea software-ului 0x80. Manipulatorul acestei întreruperi, funcția sistem_call, plasează parametrii de apel în Stek, recuperează adresa numitului funcției de sistem din tabelul Sys_Call_table și transferă controlul la această adresă.

Având acces complet la spațiul de adrese al kernelului, putem obține toate conținutul tabelului de apel al sistemului, adică Adresele tuturor funcțiilor sistemului. Prin schimbarea adresei oricărei provocări a sistemului, noi, implementăm astfel interceptarea IT. Dar, pentru aceasta, trebuie să cunoașteți adresa tabelului sau, cu alte cuvinte, compensarea în fișierul / Dev / KMEM, prin care se află acest tabel.

Pentru a determina adresa tabelului sys_call_table, trebuie mai întâi să calculați adresa funcției System_Call. Deoarece această caracteristică este un handler de întrerupere, să ne uităm la modul în care întreruperile sunt procesate în modul protejat.

În modul real, procesorul la înregistrarea întreruperii este denumit tabelul vectorilor de întrerupere, care este întotdeauna la începutul memoriei și care conține adrese de formare a programelor de manipulare a întreruperii. În modul protejat, tabelul vectorilor de întrerupere analogică este tabelul descriptorului de întrerupere (IDT, tabel descriptor de întrerupere), amplasat în sistemul de operare al modului securizat. Pentru ca procesorul să contacteze acest tabel, adresa sa ar trebui să fie descărcată în Registrul IDTR (Registrul de tabel descriptor de întrerupere, înregistrarea tabelului descriptorului de întrerupere). Tabelul IDT conține constructori de manipulatori de întrerupere în care, în special, adresele lor sunt incluse. Aceste descriptori sunt numite gateway-uri (supape). Procesorul, înregistrând întreruperea, gateway-ul este preluat din IDT de la IDT, determină adresa manipulatorului și o transmite la control.

Pentru a calcula adresa funcției System_Call din tabelul IDT, trebuie să eliminați gateway-ul de întrerupere INT $ 0x80 și de la IT - adresa de manipulator corespunzător, adică Adresa funcției sistem_call. În funcția System_Call, accesul la tabelul System_Call_table este efectuat de comanda CALL.<адрес_таблицы>(,% EAX, 4). După ce a găsit o aspect (semnătură) a acestei comenzi în fișierul / dev / kmem, vom găsi adresa tabelului de apel al sistemului.

Pentru a determina aspectul, utilizați debuggerul și dezasamblați funcția System_Call:

# Gdb -q / usr / src / linux / vmlinux (GDB) disas sistem_call halda de cod de asamblare pentru funcții sistem_call: 0xc0194Cbc : Push% eAX 0xc0194Cbd : CLD 0xc0194Cbe. : Push% es 0xc0194cbf : Push% ds 0xc0194cc0 : Push% eAX 0xc0194cc1 : Push% ebp 0xc0194cc2 : Push% EDI 0xC0194CC3 : Push% ESI 0xC0194CC4 : Push% EDX 0xC0194CC5 : Push% ECX 0xC0194CC6 : Push% ebx 0xc0194cc7 : MOV $ 0x18,% EDX 0xC0194CCC : MOV% EDX,% DS 0xC0194CCE : MOV% EDX,% ES 0xC0194CD0 : MOV $ 0xFFFFE000,% EBX 0xC0194CD5 : și% ESP,% EBX 0xC0194CD7 : Testb $ 0x2.0x18 (% ebx) 0xc0194CDB : JNE 0xC0194D3C. 0xc0194CDD. : CMP $ 0x10E,% EAX 0xC0194CE2 : Jae 0xc0194d69. 0xc0194CE8. : Apel * 0xc02cbb0c (,% EAX, 4) 0xc0194Cef : MOV% EAX, 0x18 (% esp, 1) 0xc0194CF3 : Capătul nopului de asamblare. Row "Apel * 0xc02cbbb0c (,% EAX, 4)" și apel la tabelul sys_call_table. Valoarea 0xC02CBB0c este adresa de tabel (cel mai probabil veți avea alții). Avem apariția acestei echipe: (GDB) x / xw sistem_call + 44 0xc0194CE8 : 0x0c8514ff Am găsit aspectul comenzii de apel la tabelul sys_call_table. Este egal cu \\ xff \\ x14 \\ x85. Următoarele 4 octeți pentru aceasta este adresa tabelului. Puteți verifica acest lucru introducând comanda: (GDB) x / xw sistem_call + 44 + 3 0xc0194CEB : 0xc02CBB0C Astfel, găsirea secvenței \\ xft \\ x14 \\ x85 în fișierul / dev / kmem și luând în considerare următoarele 4 octeți din spatele acestuia, obținem adresa tabelului de apel sys_call_table de sistem. Cunoscând adresa ei, putem obține conținutul acestui tabel (adrese ale tuturor funcțiilor sistemului) și putem schimba adresa oricărei provocări a sistemului, interceptarea acestuia.

Luați în considerare un pseudocod care efectuează operațiunea de interceptare:

Citigaddr (Old_sysCall, SCR + SYS_CALL * 4, 4); WriTAADDR (New_SySCall, SCR + SYS_CALL * 4, 4); Caracteristica READADDR Citește adresa apelului de sistem din tabelul de apel al sistemului și îl salvează în variabila Old_SySCall. Fiecare intrare în tabelul sys_call_table ocupă 4 octeți. Adresa dorită este localizată pe offsetul SCT + SYS_CALL * 4 în Flag / Dev / Kmem (aici SCT - SYS_CALL_TABLE Adresa adresei tabelului, SYS_CALL - Numărul de secvență al apelului de sistem). Funcția WriTAADDR suprascrie adresa de adresă de adresă SYS_Call a adresei noi_sysCall și toate accesul la apelul SYS_Call va fi deservit de această funcție.

Uite, totul este simplu și obiectivul este realizat. Cu toate acestea, să ne amintim că lucrăm în spațiul de adrese al utilizatorului. Dacă plasați o nouă funcție de sistem în acest spațiu de adresă, atunci când apelați la această funcție, vom obține un mesaj de eroare frumos. Prin urmare, ieșirea - un nou apel de sistem trebuie plasat în spațiul de adrese al kernelului. Pentru a face acest lucru, este necesar: obțineți un bloc de memorie în spațiul kernel-ului, plasați un nou apel de sistem în acest bloc.

Selectați memoria în spațiul de kernel utilizând funcția KMALloc. Dar pentru a provoca o funcție directă a kernelului din spațiul de adresă al utilizatorului nu poate fi utilizat pentru a utiliza următorul algoritm:

  • cunoașterea adresei tabelului sys_call_table, obținem adresa unui apel de sistem (de exemplu, sys_mkdir)
  • determinați funcția care apelează la funcția KMALLOC. Această caracteristică returnează un indicator la blocul de memorie din spațiul de adrese al kernelului. Să numim această funcție get_kmallloc
  • salvați primul sistem de sistem N Byte SYS_MKDIR, unde n este dimensiunea funcției Get_kmallloc
  • suprascrieți primul Call Syte Sys_MKDIR Funcția Get_kmallloc
  • realizăm accesul la apelul sistemului SYS_MKDIR, care rulează astfel pentru a executa funcția Get_kmallloc
  • realizăm primul octet al apelului SYS_MKDIR apel
Ca rezultat, la dispoziția noastră va fi un bloc de memorie situat în spațiul kernelului.

Dar pentru a implementa acest algoritm, avem nevoie de adresa funcției KMALloc. Este posibil să o găsiți în mai multe moduri. Cea mai ușoară este să luați în considerare această adresă din fișierul sistem.map sau să determinați cu ajutorul debuggerului GDB (Print & KMALLOC). Dacă kernelul este activat în kernel, adresa KMALloc poate fi definită utilizând funcția Get_Kernel_syms (). Această opțiune va fi discutată mai jos. Dacă suportul modulelor de kernel lipsește, atunci adresa funcției KMALLOC va veni la căutarea de către Openerul de echipe KMALLOC - similar cu modul în care a fost făcut pentru tabelul sys_call_table.

Funcția KMALLOC are doi parametri: dimensiunea memoriei solicitate și specificatorul GFP. Pentru a căuta aspectul, folosim debuggerul și dezasamblează orice funcție a kernelului în care există un apel la funcția KMALloc.

# Gdb -q / usr / src / linux / vmlinux (GDB) disas inter_module_register de cod de asamblare pentru funcția inter_module_register: 0xc01a57b4 : Push% EBP 0xC01A57B5 : Push% EDI 0xC01A57B6 : Push% ESI 0xC01A57B7 : Push% ebx 0xc01A57b8 : sub 0x10,% esp 0xc01a57bbb : MOV 0x24 (% esp, 1),% ebx 0xc01A57bf : MOV 0x28 (% esp, 1),% ESI 0xC01A57C3 : MOV 0x2C (% esp, 1),% EBP 0xc01A57C7 : MOVL $ 0x1f0.0x4 (% esp, 1) 0xc01A57CF : MOVL $ 0x14, (% esp, 1) 0xc01A57D6 : Apelați 0xc01Bea2a. ... Nu contează ce face funcția, principalul lucru în el este ceea ce avem nevoie - apelați funcția KMALloc. Acordăm atenția la ultima linie. Mai întâi în stack (registrul ESP indică setul de parametri de stack) sunt încărcați și apoi funcția este numită. Specificatorul GFP ($ 0x1f0x4 (% esp, 1) este încărcat pentru prima dată în stack. Pentru versiunile de 2.4.9 și mai sus, această valoare este 0x1f0. Vom găsi aspectul acestei comenzi: (GDB) x / xw inter_module_register + 19 0xc01A57C7. : 0x0424444C7 Dacă găsim acest aspect, putem calcula adresa funcției KMALLOC. Primul trimmer, adresa acestei funcții este argumentul instrucțiunii apelurilor, dar acest lucru nu este așa. Spre deosebire de funcția System_Call, instrucțiunea nu este adresa KMALLOC, ci o trecere la el în raport cu adresa curentă. Să ne asigurăm de acest lucru prin definirea apelului Cald 0xC01Bea2a: (GDB) x / xW inter_module_register + 34 0xC01A57D6 : 0x01924FE8 Primul octet este egal cu E8 - acesta este capodul instrucțiunii apeluri. Vom găsi valoarea argumentului acestei comenzi: (GDB) x / xw inter_module_register + 35 0xc01A57D7 : 0x0001924F Acum, dacă vom plânge adresa curentă 0xc01A57D6, Offset 0x0001924F și 5 octeți ai comenzii, atunci obținem adresa dorită a funcției KMALLOC - 0xc01Bea2a.

În acest scop, calculele teoretice și, folosind metoda de mai sus, vor intercepta apelul sistemului sys_mkdir.

6. Exemplu de interceptare prin mijloace / dev / kmem

/ * Sursa 6.0 * / #include #Include. #Include. #Include. #Include. #Include. #Include. #Include. / * Numărul de apel al sistemului pentru interceptare * / #define _sys_mkdir_ 39 #define kmem_file "/ dev / kmem" #define max_syms 4096 / * Descrierea formatului de înregistrare IDTR * / STRUCT (limită scurtă nesemnată; nesemnat int;) __Attribute__ (( Ambalate)) IDTR; / * Descrierea formularului de gateway a gateway-ului IDT * / STOCT (nesemnat scurt1, nesemnat SEL, fără caractere nesemnate; Steaguri; nesemnate scurt2;) __Attribute__ ((stimulate)) IDT; / * Descrierea structurii Funcției Get_kmalloc * / STOCT KMA_STRUC (Ulinong (* KMALLOC) (UINT, INT); // - KMALLOC IN INT FUNCTION; // - Dimensiunea memoriei pentru INT Steaguri; // - Steagul pentru otravă\u003e 2.4.9 \u003d 0x1f0 (GFP) Ulong MEM;) __Attribute__ ((ambalate)) KMALLC; / * Funcție care evidențiază doar unitatea de memorie din spațiul de adresă al nucleului * / int get_kmallloc (struct kma_struc * k) (k-\u003e mem \u003d k-\u003e kmalloc (k-\u003e dimensiune, k-\u003e steaguri); retur 0 ;) / * Funcția care returnează adresa funcției (trebuie să căutați KMALLOC) * / ulong get_sym (char * n) (struct gonel_sym tab; int numitys; int i; numityms \u003d get_kernel_syms (null); dacă ( numity\u003e max_syms || numitys< 0) return 0; get_kernel_syms(tab); for (i = 0; i < numsyms; i++) { if (!strncmp(n, tab[i].name, strlen(n))) return tab[i].value; } return 0; } /* Наша новая системная функция, ничего не делает;) */ int new_mkdir(const char *path) { return 0; } /* Читает из /dev/kmem с offset size данных в buf */ static inline int rkm(int fd, uint offset, void *buf, uint size) { if (lseek(fd, offset, 0) != offset){ printf("lseek err\n"); return 0; } if (read(fd, buf, size) != size) return 0; return size; } /* Аналогично, но только пишет в /dev/kmem */ static inline int wkm(int fd, uint offset, void *buf, uint size) { if (lseek(fd, offset, 0) != offset) return 0; if (write(fd, buf, size) != size) return 0; return size; } /* Читает из /dev/kmem данные размером 4 байта */ static inline int rkml(int fd, uint offset, ulong *buf) { return rkm(fd, offset, buf, sizeof(ulong)); } /* Аналогично, но только пишет */ static inline int wkml(int fd, uint offset, ulong buf) { return wkm(fd, offset, &buf, sizeof(ulong)); } /* Функция для получения адреса sys_call_table */ ulong get_sct(int kmem) { ulong sys_call_off; // - адрес обработчика // прерывания int $0x80 (функция system_call) char *p; char sc_asm; asm("sidt %0" : "=m" (idtr)); if (!rkm(kmem, idtr.base+(8*0x80), &idt, sizeof(idt))) return 0; sys_call_off = (idt.off2 << 16) | idt.off1; if (!rkm(kmem, sys_call_off, &sc_asm, 128)) return 0; p = (char *)memmem(sc_asm, 128, "\xff\x14\x85", 3) + 3; printf("call for sys_call_table at %08x\n",p); if (p) return *(ulong *)p; return 0; } /* Функция для определения адреса функции kmalloc */ ulong get_kma(ulong pgoff) { uint i; unsigned char buf, *p, *p1; int kmemz; ulong ret; ret = get_sym("kmalloc"); if (ret) { printf("\nZer gut!\n"); return ret; } kmemz = open("/dev/kmem", O_RDONLY); if (kmemz < 0) return 0; for (i = pgoff+0x100000; i < (pgoff + 0x1000000); i += 0x10000){ if (!rkm(kmemz, i, buf, sizeof(buf))) return 0; p1=(char *)memmem(buf,sizeof(buf),"\x68\xf0\x01\x00",4); if(p1) { p=(char *)memmem(p1+4,sizeof(buf),"\xe8",1)+1; if (p) { close(kmemz); return *(unsigned long *)p+i+(p-buf)+4; } } } close(kmemz); return 0; } int main() { int kmem; // !! - пустые, нужно подставить ulong get_kmalloc_size; // - размер функции get_kmalloc !! ulong get_kmalloc_addr; // - адрес функции get_kmalloc !! ulong new_mkdir_size; // - размер функции-перехватчика!! ulong new_mkdir_addr; // - адрес функции-перехватчика!! ulong sys_mkdir_addr; // - адрес системного вызова sys_mkdir ulong page_offset; // - нижняя граница адресного // пространства ядра ulong sct; // - адрес таблицы sys_call_table ulong kma; // - адрес функции kmalloc unsigned char tmp; kmem = open(KMEM_FILE, O_RDWR, 0); if (kmem < 0) return 0; sct = get_sct(kmem); page_offset = sct & 0xF0000000; kma = get_kma(page_offset); printf("OK\n" "page_offset\t\t:\t0x%08x\n" "sys_call_table\t:\t0x%08x\n" "kmalloc()\t\t:\t0x%08x\n", page_offset,sct,kma); /* Найдем адрес sys_mkdir */ if (!rkml(kmem, sct+(_SYS_MKDIR_*4), &sys_mkdir_addr)) { printf("Cannot get addr of %d syscall\n", _SYS_MKDIR_); perror("er: "); return 1; } /* Сохраним первые N байт вызова sys_mkdir */ if (!rkm(kmem, sys_mkdir_addr, tmp, get_kmalloc_size)) { printf("Cannot save old %d syscall!\n", _SYS_MKDIR_); return 1; } /* Перепишем первые N байт, функцией get_kmalloc */ if (!wkm(kmem, sys_mkdir_addr,(void *)get_kmalloc_addr, get_kmalloc_size)) { printf("Can"t overwrite our syscall %d!\n",_SYS_MKDIR_); return 1; } kmalloc.kmalloc = (void *) kma; //- адрес функции kmalloc kmalloc.size = new_mkdir_size; //- размер запращевоемой // памяти (размер функции-перехватчика new_mkdir) kmalloc.flags = 0x1f0; //- спецификатор GFP /* Выполним сис. вызов sys_mkdir, тем самым выполним нашу функцию get_kmalloc */ mkdir((char *)&kmalloc,0); /* Востановим оригинальный вызов sys_mkdir */ if (!wkm(kmem, sys_mkdir_addr, tmp, get_kmalloc_size)) { printf("Can"t restore syscall %d !\n",_SYS_MKDIR_); return 1; } if (kmalloc.mem < page_offset) { printf("Allocated memory is too low (%08x < %08x)\n", kmalloc.mem, page_offset); return 1; } /* Оторбразим результаты */ printf("sys_mkdir_addr\t\t:\t0x%08x\n" "get_kmalloc_size\t:\t0x%08x (%d bytes)\n\n" "our kmem region\t\t:\t0x%08x\n" "size of our kmem\t:\t0x%08x (%d bytes)\n\n", sys_mkdir_addr, get_kmalloc_size, get_kmalloc_size, kmalloc.mem, kmalloc.size, kmalloc.size); /* Разместим в пространстве ядра наш новый сис. вызво */ if(!wkm(kmem, kmalloc.mem, (void *)new_mkdir_addr, new_mkdir_size)) { printf("Unable to locate new system call !\n"); return 1; } /* Перепишем таблицу sys_call_table на наш новый вызов */ if(!wkml(kmem, sct+(_SYS_MKDIR_*4), kmalloc.mem)) { printf("Eh ..."); return 1; } return 1; } /* EOF */ Скомпилируем полученый код и определим адреса и размеры функций get_kmalloc и new_mkdir. Запускать полученое творение рано! Для вычисления адресов и размеров воспользуемся утилитой objdump: # gcc -o src-6.0 src-6.0.c # objdump -x ./src-6.0 > Dump Deschideți fișierul dump și găsiți datele de interes pentru noi: 080485A4 g f .text 00000032 get_kmallloc 080486b1 g f .Text 0000000a nou_mkdir Acum vom face această valoare în programul nostru: Ulong get_kmalloc_size \u003d 0x32; Ulong get_kmalloc_addr \u003d 0x080485A4; Ulong nou_mkdir_size \u003d 0x0a; Ulong nou_mkdir_addr \u003d 0x080486b1; Acum recomandați programul. După ce o conduceți la execuție, vom intercepta provocarea sistemului SYS_MKDIR. Toate apelurile către apelul SYS_MKDIR vor fi acum deservite de funcția New_MKDIR.

Sfârșitul hârtiei / EOP

Operabilitatea codului din toate secțiunile a fost verificată pe kernel 2.4.22. La pregătirea raportului, au fost utilizate materiale de pe site

Cel mai adesea, codul de apel al sistemului cu numărul __nr_xxx definit în /usr/include/asm/unistd.h.pot fi găsite în codul sursă al kernel-ului Linux în funcție sys_xxx.(). (Tabelul de apel pentru i386 poate fi găsit în /usr/src/linux/arch/i386/kernel/entry.s..) Există multe excepții din această regulă, în principal datorită faptului că majoritatea apelurilor vechi ale sistemului sunt înlocuite cu noi, cu ceva fără nici un sistem. Pe platformele cu auto-emulare, cum ar fi Parisc, Sparc, Sparc64 și Alpha, există multe apeluri suplimentare de sistem; Pentru MIPS64 există, de asemenea, un set complet de apeluri de sistem pe 32 de biți.

De-a lungul timpului, dacă este necesar, modificările au avut loc în interfața unor apeluri de sistem. Unul dintre motivele acestor modificări a fost creșterea dimensiunii structurilor sau a valorilor scalare ale apelului de sistem transmis. Din cauza acestor schimbări asupra unor arhitecturi (și anume, diverse grupuri de apeluri similare ale sistemului au apărut pe vechiul 32 biți i386) (de exemplu, trunchia.(2) și trunchate64.(2)), care îndeplinește aceleași sarcini, dar diferă în funcție de mărimea argumentelor lor. (După cum sa menționat deja, nu afectează aplicațiile: Funcțiile Wolved Glibc îndeplinesc unele acțiuni pentru a porni apelul corect al sistemului, ceea ce oferă compatibilitate pe ABI pentru fișierele binare vechi.) Exemple de apeluri de sistem care au mai multe versiuni:

* În prezent există trei diverse versiuni stat.(2): sys_stat.() (un loc __Nr_oldstat.), sys_newstat.() (un loc __Nr_stat.) I. sys_stat64.() (un loc __Nr_stat64.), acesta din urmă este folosit în acest moment. Situație similară S. lSTAT.(2) și fstat.(2). * În mod similar, definit __Nr_oldolduname., __Nr_olduname. și __Nr_unames. Pentru apeluri sys_olduname.(), sys_uname.() I. sys_newuname.(). * În Linux 2.0 a apărut o nouă versiune vM86.(2), se numesc versiuni noi și vechi ale procedurilor nucleare sys_vm86old.() I. sys_vm86.(). * În Linux 2.4 are o nouă versiune gellimit.(2) se numesc versiuni noi și vechi ale procedurilor nucleare sys_old_getellimit.() (un loc __Nr_getellimit.) I. sys_getellimit.() (un loc __Nr_ugetellimit.). * În Linux 2.4 ID-uri de utilizator și grupuri de utilizator crescute de la 16 la 32 de biți. Pentru a susține această modificare, au fost adăugate mai multe apeluri de sistem (de exemplu, chown32.(2), getuid32.(2), getGroups32.(2), setresuid32.(2)), eliminarea apelurilor timpurii cu aceleași nume, dar fără sufix "32". * În Linux 2.4 Suport adăugat pentru accesul la fișiere mari (dimensiunile și compensarea nu se potrivesc 32 de biți) în aplicații pe arhitecturi pe 32 de biți. Pentru a face acest lucru, a fost necesar să se facă modificări ale apelurilor de sistem care să funcționeze cu dimensiuni și deplasări pe fișiere. Au fost adăugate următoarele apeluri de sistem: fcntl64.(2), getdents64.(2), stat64.(2), sTATFS64.(2), trunchate64.(2) și analogii lor care se ocupă de descriptorii de fișiere sau legăturile simbolice. Aceste sisteme solicită abolirea apelurilor vechi de sistem, care, cu excepția apelurilor "stat", sunt, de asemenea, numite, dar nu au un sufix "64".

Pe platforme noi cu numai acces la fișiere pe 64 de biți și 32 de biți (de exemplu, Alpha, IA64, S390X, X86-64), există o singură versiune de apeluri de sistem pentru UID / GID și accesul la fișiere. Pe platformele (de obicei, platforme pe 32 de biți) în care există apeluri * 64 și * 32, alte versiuni sunt depășite.

* Apeluri rT_SIG * Adăugat la kernel 2.2 pentru a susține semnale suplimentare în timp real (a se vedea semnal.(7)). Aceste sisteme solicită abolirea apelurilor vechi cu aceleași nume, dar fără prefixul "RT_". * În provocările sistemului sELECTAȚI(2) și mMAP.(2) Sunt utilizate cinci sau mai multe argumente, ceea ce a provocat determinarea metodei de transmitere a argumentelor la i386. În ancheta acestui fapt, în timp ce alte arhitecturi sună sys_select.() I. sys_mmap.() corespunde cu __Nr_select. și __Nr_mmap., pe i386 ele corespund old_Select.() I. old_mmap.() (Proceduri care utilizează un indicator la blocul de argument). În prezent, nu mai există o problemă cu transferul a mai mult de cinci argumente și este __Nr__newsect.care corespunde exact sys_select.(), și aceeași situație cu __Nr_mmap2..

Mult o mulțime de walrus, este timpul să vorbim.
L. Carolle (Citat pe B. Strawbook)

În loc de administrare.

Pe tema dispozitivului intern al kernelului Linux, în general, diferitele sale subsisteme și provocări ale sistemului, în special, au fost scrise și rescrise prin ordin. Probabil, fiecare autor de auto-respect ar trebui să scrie acest lucru, la fel cum fiecare programator de auto-respect trebuie să-și scrie propriul manager de fișiere :) Deși nu sunt un profesionist IT-Raiter și, în general, îmi fac marcajele exclusiv în beneficiul său în primul rând să nu uitați prea repede. Dar, dacă cineva va veni la îndemână notele mele de călătorie, desigur, voi fi fericit doar. Ei bine, în general, terciul nu strică uleiul, deci poate fi chiar posibil să scrie sau să descrie ceva pe care nimeni nu-l deranjează să menționeze.

Teorie. Ce sunt apelurile de sistem?

Când este neinițiată explica ce fel de (sau OS) sunt, de obicei, ei spun următoarele: Computerul însuși este o bucată de fier, dar software-ul este că, datorită căruia este posibil să beneficiezi de această bucată de fier de fier . Rude, desigur, dar, în general, ceva corect. Probabil că aș spune provocările OS și sistemul. De fapt, în diferite apeluri de sistem pot fi implementate în moduri diferite, numărul acestor callalii se poate retrage, dar într-un fel sau altul, într-unul sau altul, mecanismul de apel al sistemului este în orice sistem de operare. În fiecare zi, utilizatorul este în mod clar sau care nu funcționează în mod explicit cu fișiere. Desigur, poate deschide în mod explicit un fișier pentru editare în cuvântul său preferat MS "E sau Notepad" E și poate pur și simplu rulați jucăria, imaginea executabilă a cărei, de asemenea, este stocată și în dosar, care, în Rotiți-vă, trebuie să deschideți și să citiți fișierele executabile de bootloader. La rândul său, jucăria poate deschide și citi zeci de fișiere în procesul de lucru. Bineînțeles, fișierele nu pot citi numai, ci și să scrie (nu întotdeauna, dar aici nu este vorba de împărțirea drepturilor și accesul discret :)). Toate acestea se îndreaptă spre miez (situația din sistemul de operare micronică poate fi diferită, dar acum vom avea tendința neobișnuit de obiectul discuției noastre - Linux, așa că ignor acest moment). Generarea unui nou proces este, de asemenea, serviciul furnizat de kernelul OS. Toate acestea sunt minunate, cum ar fi procesoare moderne Lucrați la frecvențele benzilor Gigahertz și constau din multe milioane de tranzistori, dar ce urmează? Da, că dacă nu ar exista alt mecanism, cu care aplicațiile personalizate ar putea efectua unele destul de obișnuite și, în același timp, lucrurile necesare ( de fapt, aceste acțiuni triviale sunt efectuate la orice, nu este de acord, nu o aplicație utilizator, ci miezul OS - AVT.), atunci sistemul de operare a fost doar un lucru în sine - absolut inutil sau dimpotrivă, fiecare aplicație de utilizator ar fi trebuit să devină sistem de operarePentru a servi independent toate nevoile dvs. Drăguț, nu-i așa?

Astfel, am abordat definiția unui apel de sistem în prima aproximare: un apel de sistem este un tip de serviciu pe care kernelul OS oferă o cerere de utilizator la cererea acestuia din urmă. Un astfel de serviciu poate fi deschiderea deja menționată a fișierului, crearea, citirea, scrierea, crearea unui nou proces, obținând un identificator de proces (PID), montare sistemul de fișiere, Opriți sistemul, în cele din urmă. În viața reală, apelurile de sistem sunt mult mai mult decât este listat aici.

Ce arată un apel de sistem și ce este? Ei bine, de la ceea ce sa spus mai sus, devine clar că apelul de sistem este o subrutină de bază, având o privire corespunzătoare. Cei care au avut experiență de programare în cadrul Win9x / Dos, îmi amintesc probabil întreruperea int 0x21 cu toate (sau cel puțin unele) numeroase funcții. Cu toate acestea, există o singură caracteristică mică cu privire la toate apelurile sistemului UNIX. Prin acord, funcția care implementează apelul de sistem poate primi argumente n sau nu le acceptă deloc, dar într-un fel sau altul, funcția trebuie să returneze valoarea INT. Orice valoare non-negativă este interpretată ca o execuție reușită a funcției de apel al sistemului și, prin urmare, este cel mai mare apel de sistem. Valoarea este mai mică decât zero este o caracteristică a erorii și, în același timp, conține un cod de eroare (codurile de eroare sunt definite în InCOP / ASM-generic / Errno-Base.h și includ / ASM-generic / Errno.h titluri ). În gateway-ul Linux pentru apelurile de sistem până la recent a existat o întrerupere int 0x80, în timp ce în Windows (până la versiunea XP Service Pack 2, dacă nu mă înșel), această gateway este 0x2E întrerupe. Din nou, în kernelul Linux, până de curând, toate apelurile de sistem au fost procesate de funcția System_Call (). Cu toate acestea, după cum sa dovedit mai târziu, mecanismul clasic al apelurilor de sistem de curgere prin intermediul gateway-ului 0x80 conduce la o scădere semnificativă a performanței procesorului. Intel Pentium. 4. Prin urmare, metoda de obiecte partajate dinamice virtuale a venit să înlocuiască mecanismul clasic (DSO - Dynamic Shared Obiect de obiect. Nu se iau în considerare pentru traducerea corectă, dar DSO este că utilizatorii Windows sunt cunoscuți numiți DLL - Bibliotecă descărcabilă și componentă dinamică ) - VDSO. Care este diferența dintre o nouă metodă de la clasic? Pentru a începe cu, înțelegem cu metoda clasică care lucrează prin poarta 0x80.

Sistemul clasic de servicii de serviciu în Linux.

Întrerupe în arhitectura x86.

După cum sa menționat mai sus, mai devreme pentru întreținerea aplicațiilor utilizator uzate 0x80 (int 0x80). Funcționarea sistemului de arhitectură IA-32 este gestionată de întreruperi (strict vorbind, aceasta se referă la toate sistemele bazate pe X86). Când apare un anumit eveniment (un nou timer TIK, orice activitate pe un dispozitiv, erori - diviziune la zero, etc.), se generează o întrerupere. Întreruperea (întreruperea) este numită astfel încât, de obicei, întrerupe cursul normal al codului. Întreruperile sunt acceptate pentru a împărți pe hardware și software (întreruperi hardware și software). Întreruperea hardware sunt întreruperi care sunt generate de dispozitive sistemice și periferice. Dacă aveți nevoie de un dispozitiv, veți atrage atenția kernel-ului de operare (dispozitivul) generează un semnal pe linia de interogare a întreruperii (linia de solicitare IRQ - interruptură). Acest lucru duce la faptul că semnalul corespunzător este generat pe anumite intrări ale procesorului, pe baza cărora procesorul și face soluția să întrerupă fluxul de instrucțiuni și să transfere controlul la dispozitivul de întrerupere a întreruperii, care află deja ce sa întâmplat și ce trebuie făcut. Hardware-ul întrerupe asincron prin natura lor. Aceasta înseamnă că întreruperea poate apărea în orice moment. in afara de asta dispozitiv perifericProcesorul în sine poate produce întreruperi (sau, mai precis, excepții hardware - excepții hardware - de exemplu, diviziunea deja menționată pe zero). Acest lucru se face pentru a notifica sistemul de operare cu privire la apariția unei situații anormale, astfel încât OS să poată lua un fel de acțiune ca răspuns la apariția unei astfel de situații. După procesarea întreruperii, procesorul revine la finalizarea programului întrerupt. Întreruperea poate fi inițiată de aplicația de utilizator. O astfel de întrerupere se numește software. Software-ul întrerupe, spre deosebire de hardware, sincron. Acestea., Atunci când chemați o întrerupere care a cauzat că codul său este suspendat până când întreruperea este servită. Când părăsiți manipulatorul de întrerupere, revine la o adresă pe distanțe lungi salvată mai devreme (când sună o întrerupere) pe stivă la următoarele instrucțiuni după instrucțiunile de apel de întrerupere (int). Manipulatorul de întrerupere este un rezident (în mod constant în memorie) al codului. De regulă, acesta este un program mic. Deși, dacă vorbim despre kernelul Linux, atunci nu există întotdeauna un handler de întrerupere atât de mic. Handler de întrerupere este determinat de vector. Vectorul nu este altceva decât adresa (segment și offset) a codului de pornire, care ar trebui să proceseze întreruperea cu acest indice. Lucrul cu întreruperile este semnificativ diferit în modul real și în modul protejat (modul protejat) al procesorului (amintiți-vă că aici și apoi înțelegem procesoarele Intel și compatibil cu acestea). Într-un mod de procesor real (neprotejat), agenții de întrerupere sunt determinați de vectorii lor care sunt întotdeauna stocați la începutul memoriei. Selectarea adresei dorite din tabelul vectorilor are loc în index, care este și numărul de întrerupere. Suprascrierea unui vector cu un index definit poate fi atribuit pentru a întrerupe propriul său manipulare.

În modul protejat, stivuitorii de întrerupere (gateway-uri, porți sau supape) nu mai sunt determinate utilizând tabelul vectorilor. În loc de acest tabel, tabelul supapei este utilizat sau, mai corect, tabelul de întrerupere - IDT (tabel descriptori de întrerupere). Acest tabel este format de kernel, iar adresa sa este stocată în registrul procesorului IDTR. Acest registru nu este disponibil direct. Lucrul cu acesta este posibil numai prin utilizarea instrucțiunilor LIDT / SIDT. Primul dintre aceste (LIDT) încarcă valoarea IDTR specificată în operand și a fi adresa de bază Întrerupeți mesele de decriptare, secunde (SIDT), stochează adresa tabelului situat în IDTR la operandul specificat. La fel ca o selecție de informații despre segmentul din tabelul descriptorii descriptorilor, un segment al descriptorului segmentului apare, de asemenea, servind o întrerupere în modul protejat. Protecția memoriei este acceptată procesoare Intel. Începând cu CPU I80286 (nu este destul de sub forma în care este acum prezentată, dacă numai pentru că 286 a fost un procesor pe 16 biți - prin urmare, Linux nu poate funcționa la aceste procesoare) și I80386 și, prin urmare, procesorul produce în mod independent toate eșantioanele necesare și Prin urmare, a devenit puternic să se aprofundeze în toate subtilitățile modului protejat (și anume, Linux funcționează în modul protejat) Nu vom. Din păcate, nici timpul, nici capacitățile nu ne permit să ne oprim pentru o lungă perioadă de timp pe mecanismul de procesare a întreruperii într-un mod sigur. Da, acesta nu a fost scopul când scrieți acest articol. Toate informațiile menționate aici pe activitatea procesatorilor familiali H86 sunt destul de superficiale și sunt date doar pentru a ajuta un pic mai bine să înțeleagă mecanismul de funcționare a apelurilor sistemului de kernel. Ceva pe care îl puteți învăța direct din codul Coder, deși, pentru o înțelegere completă a ceea ce se întâmplă, este recomandabil să se familiarizeze chiar și cu principiile regimului protejat. Codul de cod care completează valorile inițiale (dar nu se instalează!) IDT, situat în ARCH / i386 / Kernel / Head.S: / * * setup_idt * * Setează un IDT cu 256 de intrări care indică * ignore_int, întrerupeți porțile. Nu se încarcă efectiv * IDT - care se poate face numai după ce paginarea a fost activată * și kernel-ul sa deplasat la pagina_offset. Întreruperi * sunt activate în altă parte, putem fi relativi * Sigur totul este OK. * * Avertisment:% ESI Este trăit peste această funcție. * / 1.Setup_idt: 2. Lea ignore_int,% EDX 3. Movl $ (__ kernel_cs<< 16),%eax 4. movw %dx,%ax /* selector = 0x0010 = cs */ 5. movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ 6. lea idt_table,%edi 7. mov $256,%ecx 8.rp_sidt: 9. movl %eax,(%edi) 10. movl %edx,4(%edi) 11. addl $8,%edi 12. dec %ecx 13. jne rp_sidt 14..macro set_early_handler handler,trapno 15. lea \handler,%edx 16. movl $(__KERNEL_CS << 16),%eax 17. movw %dx,%ax 18. movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ 19. lea idt_table,%edi 20. movl %eax,8*\trapno(%edi) 21. movl %edx,8*\trapno+4(%edi) 22..endm 23. set_early_handler handler=early_divide_err,trapno=0 24. set_early_handler handler=early_illegal_opcode,trapno=6 25. set_early_handler handler=early_protection_fault,trapno=13 26. set_early_handler handler=early_page_fault,trapno=14 28. ret Câteva comentarii privind codul: Codul dat este scris pe soiurile Assembler AT & T, astfel încât cunoștințele asamblorului dvs. în notația sa obișnuită de informații nu pot fi confuze. Cea mai importantă diferență în ordinea operanilor. Dacă este definită comanda pentru notația Intel - "Baterie"< "источник", то для ассемблера AT&T порядок прямой. Регистры процессора, как правило, должны иметь префикс "%", непосредственные значения (константы) префиксируются символом доллара "$". Синтаксис AT&T традиционно используется в Un*x-системах.

În exemplul dat în liniile 2-4, se stabilește adresa de gestionare a tuturor întreruperilor implicite. Manipulatorul implicit este funcția IGNORE_INT, care nu face nimic. Prezența unui astfel de ștecher este necesară pentru procesarea corectă a tuturor întreruperilor în această etapă, deoarece alții nu mai sunt încă (trad, capcane (capcane) sunt ușor mai mici în codul - despre capcane, consultați Referința manuală a arhitecturii Intel Sau ceva de genul asta, aici nu vom atinge capcanele). În linia 5, tipul supapei este setat. În linia 6, descoperim adresa tabelului nostru IDT în registrul indexului. Tabelul trebuie să conțină 255 de intrări, câte 8 octeți fiecare. În liniile 8-13, completăm întreaga masă ca aceleași valori setate de registrele EAX și EDX - adică, aceasta este o supapă de întrerupere care se referă la Handler Ignore_int. Mai jos, definim un macro pentru a instala capcane (capcane) - rânduri 14-22. În linia 23-26 utilizând macro-ul menționat mai sus. Am stabilit capcane pentru următoarele excepții: Divizia La Zero (0), Early_illegal_OPCODE - Instrucțiuni de procesoare necunoscute (6), Early_Protection_fault - Protecția Memorilor (13), Start_page_fault - Page difuzare eșec (14). În paranteze sunt date "întrerupe" numere generate de o situație de urgență adecvată. Înainte de a verifica tipul procesorului în ARCH / I386 / Kernel / Head.S, tabelul IDT este setat de apelul SETUP_IDT: / * * Start System 32-Bit Configurare. Trebuie să re-realizăm unele dintre lucrurile făcute * în modul pe 16 biți pentru operațiile "reale". * / 1. Call Setup_idt ... 2. Call Check_x87 3. LGDT Early_GDT_DESCR 4. LIDT IDT_DERSCRDupă ce a aflat tipul (CO) al procesorului și efectuarea tuturor acțiunilor pregătitoare din liniile 3 și 4, încărcăm mesele GDT și IDT care vor fi utilizate la primii pori ai kernelului.

Apeluri de sistem și int 0x80.

De la întreruperi se vor întoarce la provocările sistemului. Deci, ceea ce este necesar pentru a servi procesul care solicită un fel de serviciu? Pentru a începe, este necesar să se deplaseze de la inelul 3 (nivelul CPL \u003d 3) pe cel mai afectat nivel 0 (inelul 0, CPL \u003d 0), deoarece Codul Core este situat în segmentul cu cele mai înalte atracții. În plus, codul de funcționare este necesar ca procesul să servească. Este pentru aceasta, se utilizează gateway-ul 0x80. Deși apelurile sistemului sunt destul de multe, pentru tot ce utilizează un singur punct de intrare - int 0x80. Manipulatorul în sine este setat la apelul ARCH / I386 / Kernel / Traps.C :: Trap_init () Funcție: Void __init cap_init (vid) (... set_system_gate (syscall_vector, & sistem_call); ...)Suntem în Trap_init () mai interesați de această linie. În același fișier, puteți privi la codul funcției Set_System_Gate () de mai sus: Static vid __init set_system_gate (nesemnat int n, void * addr) (_set_gate (n, nastape_trap | destinape_dpl3, addr, __kernel_cs);)Aici puteți vedea că supapa pentru întreruperea 0x80 (și anume, această valoare este determinată de Syscall_vector Macro - puteți crede defecțiunea :)) este instalată ca o capcană (capcană) cu nivelul atributelor DPL \u003d 3 (inelul 3) , adică Această întrerupere va fi prinsă când sunați din spațiul utilizatorului. Problema cu tranziția de la inelul 3 în inelul 0 T.O. Rezolvat. Funcția _set_gate () este definită în fișierul antet include / asm-i386 / desc.h. Pentru mai multe curioase, codul este arătat, fără excelență, cu toate acestea: Static inline vid _set_gate (int poarta, int de tip nesemnat, void * addr, nesemnat scurt seg) (__u32 A, b; pachet_gate (& A & B, (nesemnat lung) addr, seg, tip, 0); write_idt_entry (Idt_table , Poarta, a, b);)Să revenim la funcția Trap_init (). Se numește din funcția Start_Kernel () în INI / Main.C. Dacă vă uitați la codul Trap_init (), este clar că această caracteristică corespunde unor valori ale manipulatoarelor actualizate de tabel IDT care au fost utilizate în stadiile incipiente ale inițializării kernelului (recepționate_page_fault, mai devreme_divide_opcode, mai devreme_protection_faulde) sunt înlocuit cu cele care vor fi utilizate deja în acest proces. Lucrările de kernel. Deci, practic am ajuns la esență și deja știm că toate apelurile de sistem sunt procesate uniform - prin intermediul gateway-ului INT 0x80. Ca un handler pentru int 0x80, ca din nou, este văzut din complotul de mai sus al codului ARCH / I386 / Kernel / Traps.C :: Trap_init (), stabilește funcția System_Call ().

sistem_call ().

Codul funcției System_Call () se află în fișierul arc / i386 / kernel / intrare.s și arată astfel: # Sistem de apel Handler Stub Intrare (System_Call) Ring0_int_frame # poate să se relaxeze în spațiul utilizatorului oricum Pushl% eAX # Save_ex cfi_adjust_cfa_offset 4 Save_all get_thread_info (% ebp) # Funcționarea apelului în funcțiune / emulare / * Notă, _TIF_SECCOMPS este un pic numărul 8 , deci are nevoie de testw și nu testb * / testw $ (_ tif_syscall_emu | _tif_sysccomp | _tif_sysccomp | _tif_syccomp | _TIF_SYCCOMP | _TIF_SYCCOMP | _TIF_SYCCOMP | _TIF_SYSCOMP | _TIF_SYSCOMP | _TIF_SYSCOMP | JNZ SYSCALL_TRACE_ENTRY CMPL $ (NR_SYSCALLS),% EAX JAE SYSCALL_BADSYS SYSCALL_CALL: apel * sys_call_table (,% EAX , 4) MOVL% EAX, PT_EAX (% ESP) # Depozitați valoarea de returnare ...Codul nu este afișat complet. După cum puteți vedea, primul sistem_call () configurează un stivă pentru a lucra în inelul 0, economisește valoarea transmisă prin EAX la stivă, salvează toate registrele la stivă, primește datele apelantului și verifică dacă valoarea transmisă a Apelul sistemului nu merge limitele tabelului de apel al sistemului și, în final, utilizând valoarea transmisă în EAX ca un argument, sistem_call () exerciții la un sistem real de ieșire a sistemului, pe baza căruia elementul tabelului se referă indexul la EAX. Acum amintiți-vă vechea masă bună a vectorilor de întrerupere din modul real. Nimic nu reamintește? În realitate, bineînțeles, totul este mai complicat. În particular, apelul de sistem trebuie să copieze rezultatele din stiva de kernel la stiva de utilizator, să treacă codul de returnare și mai multe lucruri. În cazul în care argumentul specificat în EAX nu se referă la un apel de sistem existent (valoarea este peste interval), mergeți la eticheta SYSCALL_BADSYS. Aici, în stiva de deplasare, care ar trebui să fie valoarea EAX, este introdusă valoarea -enosys - apelul sistemului nu este implementat. Această execuție sistem_call () este finalizată.

Tabelul de apel al sistemului este localizat în fișierul arc / i386 / kernel / syscall_table.s și are o vizualizare destul de simplă: Intrare (sys_call_table) .long sys_restsart_syscall / * 0 - Apel de sistem "Setup ()" vechi, utilizat pentru repornire * / .long sys_exit .long sys_fork .long sys_read .long sys_write .long sys_open / * 5 * / .long sys_close .long Sys_waitpid .long sys_creat ...Cu alte cuvinte, întreaga masă nu este altceva decât o serie de adrese de funcții situate în ordinea numărului de numere de apel de sistem pe care aceste funcții sunt servite. Tabelul este o gamă obișnuită a cuvintelor duble ale motorului (sau cuvinte pe 32 de biți - care le place mai mult). Codul de parte a apelurilor de servire a funcțiilor este localizat în partea dependentă de platformă - Arch / i386 / kernel / sys_i386.c și o parte care nu depinde de platforma din kernel / sys.c.

Acesta este cazul provocărilor sistemului și a supapei 0x80.

Noul mecanism de procesare a apelurilor de sistem în Linux. Sysenter / Sysexit.

După cum sa menționat destul de repede, sa dovedit că utilizarea unei metode tradiționale de procesare a apelurilor bazate pe GAITA 0x80 se împinge la pierderea performanței pe procesoarele Intel Pentium 4. Prin urmare, Linus Torvalds a implementat un nou mecanism în kernel, pe baza Instrucțiunile Sysenter / Sysexit și concepute pentru a crește productivitatea kernelului pe mașinile echipate cu procesorul Pentium II și mai mare (este de la procesoarele Intel Pentium II + care sunt susținute de instrucțiunile Sysenter / Sysexit). Care este esența unui nou mecanism? Destul de ciudat, dar esența rămâne aceeași. Execuția sa schimbat. Conform documentației Intel, instrucțiunea Sysenter face parte din mecanismul "apelurilor rapide ale sistemului". În special, această instrucțiune este optimizată pentru o tranziție rapidă de la un nivel de atracții la altul. Dacă este mai precis, accelerează tranziția la inelul 0 (inelul 0, CPL \u003d 0). În același timp, sistemul de operare trebuie să pregătească procesorul să utilizeze instrucțiunea Sysenter. Această setare se efectuează o dată la încărcarea și inițializarea kernel-ului OS. Când sunați Sysenter stabilește registrele procesorului în conformitate cu registrele dependente de mașini instalate anterior. În particular, se stabilesc registrul de segment și registrul indicatorului de instrucțiuni - CS: EIP, precum și segmentul stivei și indicatorul vârfului stivei - SS, ESP. Tranziția la noul segment al codului și offset este realizat de la inelul 3 la 0.

Instrucțiunea Sysexit efectuează acțiuni inverse. Ea produce o tranziție rapidă de la nivelul atracțiilor 0 pe al 3-lea (CPL \u003d 3). În acest caz, registrul segmentului de cod este setat la valoarea segmentului 16+ CS stocată în registrul dependent de mașină al procesorului. Registrul EIP este introdus în conținutul registrului EDX. SS este introdusă în SS 24 și valorile CS înregistrate mai devreme în registrul procesorului dependent de mașină în timpul pregătirii contextului pentru funcționarea instrucțiunii Sysenter. ESP a intrat în conținutul registrului ECX. Valorile necesare pentru funcționarea instrucțiunilor Sysenter / Sysexit sunt stocate la următoarele adrese:

  1. SYSENTER_CS_MSR 0x174 este un segment de cod în care este introdus valoarea segmentului în care este introdus codul de selectare a apelului sistemului.
  2. SYSENTER_ESP_MSR 0x175 este un pointer în partea de sus a stivei pentru un handler de apeluri de sistem.
  3. SYSENTER_EIP_MSR 0x176 - Pointer la offset în interiorul segmentului de cod. Specifică codul de pornire al manipulatorului apelului sistemului.
Aceste adrese se referă la registrele dependente de model care nu au nume. Valorile sunt înregistrate în registrele dependente de model utilizând instrucțiunea WMSRR, în timp ce EDX: EAX trebuie să conțină o parte teribilă și mai mică a cuvântului mașinii cu 64 de biți, respectiv, ECX ar trebui să fie enumerate adresa la care înregistrarea vor fi înregistrate. În Linux, adresele de registre dependente de model sunt definite în fișierul antet includ / ASM-I368 / MSR-index.h după cum urmează (până la versiunea 2.6.22 cel puțin au fost definite în fișierul antet includ / ASM-I386 / MSR.H, vă voi reaminti că considerăm mecanismul de apeluri de sistem pe exemplul kernelului Linux 2.6.22): #Define msr_ia32_sysenter_cs 0x00000174 #Define msr_ia32_sysenter_esp 0x00000175 #define msr_ia32_sysenter_eip 0x00000176Codul principal responsabil pentru instalarea registrelor dependente de model este în fișierul ARCH / i386 / sysenter.c și arată astfel: 1. VOID ELENAL_SEP_CPU (Void) (2. int cpu \u003d get_cpu (); 3. struct tss_struct * tss \u003d & per_cpu (init_tss, cpu); 4. dacă (boot_cpu_shas (x86_FEATY_SEP)) (5. PUT_CPU (); 6 . Întoarcere;) 7. tss-\u003e x86_tsss.ss1 \u003d __kernel_cs; 8. tss-\u003e x86_tss.essp1 \u003d Sizeof (struct tss_struct) + (nesemnat lung) TSS; 9. MSR_IA32_SSENTER_CS, __kernel_C, 0); 10. WMSR (MSR_IA32_SYSENTER_ESP, TSS-\u003e X86_TSS.ESP1, 0); 11. WMSRR (MSR_IA32_SYSENTER_EIP (nesemnat) Sysenter_entry, 0); 12. Put_cpu ();)Aici, în variabila TSS, obținem adresa structurii care descrie segmentul de stat al sarcinii. TSS (segmentul de stat al sarcinii) este utilizat pentru a descrie contextul sarcinii și face parte dintr-un mecanism de sprijin hardware multitasking pentru arhitectura X86. Cu toate acestea, Linux practic nu utilizează comutarea hardware a contextului sarcinii. Conform documentației Intel, trecerea la o altă sarcină se face fie prin executarea instrucțiunilor de tranziție internă (JMP sau apel), referindu-se la segmentul TSS sau pe descriptorul supapei GDT în GDT (LDT). Un registru de procesor special, invizibil pentru programator - TR (Registrul de sarcini - Registrul de probleme) conține selectorul descriptorului sarcinii. La încărcarea acestui registru, registrele de bază invizibile de software și o limită asociată cu TR sunt, de asemenea, descărcate.

În ciuda faptului că Linux nu utilizează comutarea hardware a contextelor sarcinilor, kernelul este forțat să seteze înregistrarea TSS pentru fiecare procesor instalat în sistem. Acest lucru se datorează faptului că atunci când procesorul trece de la modul de utilizator în modul kernel, acesta preia adresa stivei de kernel de la TSS. În plus, TSS este necesar pentru a controla accesul la porturile I / O. TSS conține o hartă a drepturilor de acces la porturi. Pe baza acestei cărți, devine posibilă controlul accesului la porturi pentru fiecare proces care utilizează instrucțiuni in / out. Aici TSS-\u003e X86_TSS.ESP1 indică stiva de kernel. __Kernel_cs indică în mod natural segmentul codului de coder. Deplasarea EIP indică adresa funcției Sysenter_entry ().

Funcția Sysenter_entry () este definită în fișierul ARCH / i386 / kernel / intrare și are acest tip: / * Sysenter_return indică după instrucțiunea "Sysenter" din pagina VSYSCALL. Vedeți VSYSCALL-SYSTENTY.S, care definește simbolul. * / # SYSENTER SALVER Handler intrări (Sysenter_entry) CFI_STARTPROC SIMPLE CFI_SIGNAL_FRAME CFI_DEF_CFA ESP, 0 CFI_Register ESP, EBP MOVL TSS_SYSENTER_ESP0 (% ESP),% ESP SYSENTER_PAST_ESP: / * * Nu este nevoie să urmați această secțiune IRQ-uri On / Off: Sistemul SysCall * Dezactivat IRQ-uri și aici îl activăm imediat după intrarea: * / Enable_Interrupts (CLBR_NONE) CFI_ADJUST_DS) CFI_ADJUST_DS) CFI_ADJUST_DS_OFFset SS, 0 * / Pushl% EBP CFI_ADJUST_CFA_Offset 4 CFI_rel_Offset ESP, 0 PUTHFL CFI_ADJUST_CFA_Offset 4 Pushl $ (__ user_Cs ) Cfi_adjust_cfa_offset 4 / * cfi_rel_offset cs, 0 * / / * * împingere curent_thread_info () -\u003e sysenter_return la stiva. * Este necesar un mic pic de fixup offset - 4 * 4 înseamnă cele 4 cuvinte * împinse mai sus; +8 corespunde setării esp0 copy_thread "* / pushl (ti_sysenter_return-thread_size + 8 + 4 * 4) (% esp) cfi_adjust_cfa_offset 4 cfi_rel_offset EIP, 0 / * * Încărcați potențialul al șaselea argument de la stiva de utilizator. * Atent la securitate . * / CMPL $ __ Page_Offset-3,% EBP Jae Syscall_fault 1: MOVL (% EBP),% EBP .Section __ex_table, "A" .Align 4 .long 1b, syscall_fault .Predict Push% EAX CFI_ADJUST_CFA_Offset 4 Save_all Get_Thread_Info (% EBP) / * Notă, _TIF_SECCOMPP este biți numărul 8, deci are nevoie de testw și nu testb * / testw $ (_ tif_syscall_emu | _TIF_SYCCOMP | _TIF_SCCOMP | _TIF_SYCCOMP | _TIF_SYCLAL_AUDIT), TI_FLAGS (% EBP) JNZ SYSCALL_TRACE_ENTRY CMPL $ (NR_SYSCALLS),% EAX jae syscall_badsy sunat * sys_call_table (,% eAX, 4) Movl% EAX, PT_eAx (% esp) Disable_interrupts (CLBR_ANY) TRACE_IRRUPS_OFF MOVL TI_FLAGS (% EBP),% ECX TestW $ _TIF_ALLWORK_MASK,% CX JNE SYSCALL_EXIT_WORK / * Dacă se modifică ceva trebuie să dezactiveze, de asemenea, sysexit * / movl pt_eip (% esp),% EDX MOVL PT_OLDESP (% ESP),% ECX XORL% EBP,% EBP trace_irqs_on 1: MOV PT_FS (% ESP),% FS \u200b\u200bEnable_Interrupts_Sysexit cfi_endproc .pusection .Fixup, "Ax" 2: Movl $ 0, PT_FS (% ESP) JMP 1b .section __ex_table, "a" .align 4 .LONG 1B, 2B Endprocection Endproc (Sysenter_entry)Ca și în cazul sistemului_call (), lucrarea principală este efectuată în șirul apelului * sys_call_table (,% eAX, 4). Un handler de apel specific al sistemului este numit aici. Deci, se poate vedea că un pic sa schimbat. Faptul că vectorul de întrerupere este acum înfundat în fier și procesorul ne ajută mai repede de la un nivel al atracțiilor la alta modificări doar câteva detalii ale executării cu același conținut. Adevărat, aceste schimbări nu se termină. Amintiți-vă de ce a început narațiunea. La început, am menționat despre obiectele comune virtuale. Deci, dacă mai devreme punerea în aplicare a sistemului de apel, să spunem, din biblioteca libc a arătat ca un apel de întrerupere (în ciuda faptului că biblioteca a luat unele funcții pentru a reduce numărul de întrerupătoare de context), acum datorită apelului de sistem VDSO să fie făcute aproape direct, fără participarea libcului. De asemenea, el ar putea fi efectuat direct, din nou, ca o întrerupere. Dar acum apelul poate fi solicitat ca funcție obișnuită exportată dintr-o bibliotecă dinamic (DSO). La încărcarea kernelului determină ce mecanism ar trebui și poate fi utilizat pentru această platformă. În funcție de circumstanțe, kernelul stabilește punctul de intrare la o funcție care efectuează apelul sistemului. Mai mult, funcția este exportată în spațiul utilizatorului Linux-Gate.So.1 Bibliotecă. Linux-gate.so.1 Biblioteca fizică nu există pe disc. Ea, dacă o puteți pune, este emulată de miez și există exact cât funcționează sistemul. Dacă opriți sistemul, subminează rădăcina FS dintr-un alt sistem, atunci nu veți găsi acest fișier pe rădăcina FS. De fapt, nu veți putea să o găsiți nici măcar pe un sistem de lucru. Din punct de vedere fizic, pur și simplu nu este. Acesta este motivul pentru care Linux-Gate.So.1 este altceva ca VDSO - I.E. Obiect partajat dinamic virtual. Kernel-ul afișează biblioteca dinamică în acest mod în spațiul de adrese al fiecărui proces. Asigurați-vă că acest lucru este ușor dacă executați următoarea comandă: [E-mail protejat]: ~ $ Cat / proc / auto / hărți 08048000-0804C000 R-XP 00000000 08:01 46 / CAT 0804C000-0804D000 RW-P 00003000 08:01 46 / BIN / CAT 0804D000-0806E000 RW-P 0804D000 00:00 0 ... B7FDF000-B7FE1000 RW-P 00019000 08:01 2066 /lib/ld-2.5.so BFFD2000-BFFE8000 RW-P BFFD2000 00:00 0 FFFFE000-FFFFF000 R-XP 00000000 00:00 0Aici, cea mai recentă linie este obiectul interesului: FFFFFE000-FFFFFF000 R-XP 00000000 00:00 0Din exemplul de mai sus, se poate observa că obiectul ia în memorie exact o pagină - 4096 octeți, aproape pe curțile din spate a spațiului de adrese. Să tragem un alt experiment: [E-mail protejat]: ~ $ Ldd `care pisica linux-gate.so.1 \u003d\u003e (0xfffe000) libc.so.6 \u003d\u003e /lib/tls/i686/cmov/libc.so.6 (0xb7e87000) / lib / ld-linux .So.2 (0xb7fdf000) [E-mail protejat]: ~ $ Ldd `la care gcc` linux-gate.so.1 \u003d\u003e (0xfffe000) libc.so.6 \u003d\u003e / /lib/tls/i686/cmov/libc.so.6 (0xb7e3c000) / lib / ld-linux .So.2 (0xb7f94000) [E-mail protejat]:~$ Aici tocmai am luat două aplicații. Se poate observa că biblioteca este afișată în spațiul de adresă al procesului unul cu aceeași adresă permanentă - 0xFFFFE000. Acum, să încercăm să vedem ce este stocat pe această pagină de memorie este de fapt ...

Efectuați o deplasare a paginii de memorie în cazul în care codul VDSO partajat este stocat utilizând următorul program: #include #include #include int principal (char * vdso \u003d 0xffffe000; char * tampon; fișier * f; tampon \u003d malloc (4096); dacă (! tampon) ieșire (1); memcy (tampon, VDSO, 4096) ; dacă (f \u003d fopen ("test.dump", "w + b"))) (fără tampon); ieșire (1);) FWRITE (tampon, 4096, 1, F); FCLOSE (F ); Liber (tampon); retur 0;)Strict vorbind, înainte de a putea fi făcut mai ușor, folosind echipa dd dacă \u003d / proc / sine / mem de \u003d test.dump bs \u003d 4096 Skip \u003d 1048574 Count \u003d 1Dar miezurile începând cu versiunea 2.6.22 sau poate chiar mai devreme, nu mai afișează memoria de proces pentru a fi fișier / proc / `Pid` / Mem. Acest fișier este salvat, evident, pentru compatibilitate, dar nu conține mai multe informații.

Comiteți și executați programul dat. Să încercăm să dezasamblați codul rezultat: [E-mail protejat]: ~ / Tmp $ objdump --disassemble ./test.dump ./test.dump: Format fișier fișier Elf32-i386 Dezasamblu de secțiune .Text: FFFFE400<__kernel_vsyscall>: FFFFE400: 51 Push% ECX FFFFE401: 52 Push% EDX FFFFE402: 55 Push% EBP FFFFE403: 89 E5 MOV% ESP,% EBP FFFFE405: 0F 34 Sysenter ... FFFFFFE40E: EB F3 JMP FFFFE403<__kernel_vsyscall+0x3> FFFFE410: 5D POP% EBP FFFFE411: 5A POP% EDX FFFFE412: 59 POP% ECX FFFFE413: C3 RET ... [E-mail protejat]: ~ / Tmp $Aici este poarta noastră de acces pentru apelurile de sistem, toate pe palmă. Procesul (sau, Biblioteca de sistem Libc), care numind funcția __kernel_vsysCall cade pe adresa 0xFFFFFE400 (în cazul nostru). Apoi, __kernel_vsyscall salvează conținutul registrelor ECX, EDX, EDX în stiva de procesare a ECX, EDX, am vorbit deja despre registrele de înregistrare ECX și EDX mai devreme, se utilizează mai târziu pentru a restabili stiva utilizatorului. Instrucțiunea Sysenter este executată, "Interceptarea întreruperii" și, ca rezultat, următoarea tranziție către Sysenter_entry (vezi mai sus). Instrucțiunea JMP la 0xFFFFE40E este introdusă pentru a reporni provocarea sistemului cu un număr de 6 argumente (vezi http://lkml.org/lkml/2002/12/18/). Codul postat pe pagină este în Arc / i386 / kernel / vsyscall-enter.s (sau arc / i386 / kernel / vsyscall-int80.s) pentru capcana 0x80). Deși am constatat că adresa funcției __kernel_vsyscall este permanentă, dar există o opinie că nu este. De obicei, poziția punctului de intrare în __kernel_vsysCall () poate fi găsită de vectorul Elf-AUXV folosind parametrul at_sysinfo. Vectorul Elf-AUXV conține informații transmise procesului prin stivă la pornire și conține diverse informații necesare în timpul programului. Acest vector, în special, conține variabile ale mediului înconjurător, argumente și așa mai departe ..

Iată un mic exemplu despre cum să se refere direct la funcția __kernel_vsysCall direct: #Include. int pid; Int Main () (__asm \u200b\u200b("MOVL $ 20,% EAX \\ n" "Apel *% GS: 0x10 \\ n" "MOVL% EAX, PID \\ N"); Printf ("PID:% d \\ n" , Pid); retur 0;)Acest exemplu este luat de pe pagina Manu Garg, http://www.manugarg.com. Deci, în exemplul dat, facem apelul GetPid () () (numărul 20 sau altfel __nr_getpid). Pentru a nu urca din stiva de proces în căutarea variabilei AT_SYSIINFO, folosim faptul că biblioteca de sistem libc.SO este copiată de valoarea variabilei AT_SYSInfo la unitatea de comandă a fluxului (blocul de control al firului TCB). Acest bloc de informații, de regulă, se referă la selector în GS. Presupunem că pe offset 0x10 există un parametru dorit și efectuați un apel la adresa stocată în% GS: $ 0x10.

Rezultate.

De fapt, practic, o creștere specială a performanței, chiar și cu sprijinul acestui FSCF (Fast System Call Facility), nu este întotdeauna posibil să se realizeze. Problema este că, oricum, procesul rar se referă direct la kernel. Și pentru aceasta există motive bune. Utilizarea bibliotecii libc vă permite să garantați portabilitatea programului, indiferent de versiunea kernel-ului. Și este prin biblioteca standard de sistem pe care majoritatea apelurilor de sistem merge. Chiar dacă colectați și instalați cel mai recent kernel asamblat pentru o platformă care acceptă FSCF, aceasta nu este o garanție a creșterii productivității. Faptul este că biblioteca dvs. de sistem libc.SO va fi îndreptată pentru a utiliza int 0x80 și poate face față doar tranziției lui Glibc. Interfața VDSO și __kernel_vsyscall și __kernel_vsysCall sunt acceptate în Glibc, consider că este greu să răspund.

Link-uri.

Manu Garg Page, http://www.manugarg.com
Scatter / Adunați ThouGhts de Johan Petersson, http://www.trilihium.com/johan/2005/08/linux-gate/
Vechiul înțelegere a kernelului Linux unde fără el :)
Și, desigur, codurile sursă Linux (2.6.22)