Tema 3 - Code Golf

Enunț

Să se analizeze un executabil dat prin metode statice și dinamice. Apoi, să se optimizeze programul pentru a atinge două scopuri diferite - dimensiune redusă și timp de execuție redus.

Tema are trei părți:

1. Analiză statică și dinamică - 50p

Programul trebuie analizat în mod amănunțit prin metodele învățate în laborator. Scopul este de a înțelege atât în linii mari cum funcționează și cum se folosește, cât și în detaliu. Codul sursă ”s-a pierdut”, iar aplicația a rămas în mare parte nedocumentată.

Dacă vă uitați cu atenție prin simbolurile corespunzătoare funcțiilor din binar, veți observa că o parte dintre ele au numele ”mystery” urmat de câte o cifră. Acestea trebuie analizate și documentate în mod rezumat cât și în amănunt. Aveți un sample de documentare în mod rezumat a unei funcții de test în scheletul de cod.

Documentarea amănunțită din README va descrie pentru fiecare funcție modul în care s-a făcut implementarea (fără elemente mult prea specifice de tipul ”s-a folosit registrul ecx pentru a reține lungimea șirului”).

Folosiți formatul următor pentru a documenta o funcție:

func() - scurtă descriere a funcției
@<param1>: scurtă descriere a parametrului 1
...
@<paramN>: scurtă descriere a parametrului N

Descriere amănunțită a funcției.

Return: scurtă descriere pentru ce returnează funcția

prototip: void func(param1, ...,  paramN);

Exemplu de documentare amănunțită:

    BITS 32
    global main
    section .data
    test_s: db "abcd1234XYZ", 0
    section .text
 
    mystery_test:
        push ebp
        mov ebp, esp
        mov ebx, [ebp+8]
    mystery_test_l1:
        mov al, byte [ebx]
        test al, al
        jz mystery_test_l2
        cmp al, 0x41
        jl mystery_test_l3
        cmp al, 0x7a
        jg mystery_test_l3
        xor al, 0x20
        mov byte [ebx], al
    mystery_test_l3:
        inc ebx
        jmp mystery_test_l1
    mystery_test_l2:
        leave
        ret
 
    main:
        push ebp
        mov ebp, esp
        push test_s
        call mystery_test
        add esp, 4
        leave
        ret
mystery_test() - functie ce aplica o schimbare de "case" sirului de input
@string: adresa de inceput a sirului

La o primă vedere, funcția are un singur argument. Urmărind în GDB, se pare 
că având drept input adresa string-ului ”abcd1234XYZ”, după apelul funcției
ajungem să avem ”ABCD1234xyz”, ducându-ne cu ipoteza că funcția face o
schimbare de ”case” (din lowercase în uppercase și viceversa).
Argumentul funcției trebuie să fie adresa unui șir ASCIIZ, întrucât
singura condiție de terminare a buclei este găsirea unui byte ”0x00”.
Altfel, se verifică dacă byte-ul se încadrează între 0x41 și 0x7a
(corespunzătoare caracterelor 'A', respectiv, 'z' din tabela ASCII).
Dacă nu, caracterul rămâne neafectat. Dacă da, atunci se face operația
de ”sau exclusiv” (xor) cu valoarea 0x20, procedeu prin care grupul de valori
din intervalul 0x41-0x5A se translatează în grupul 0x61-0x7A și viceversa.
Există un bug în codul de mai sus, anume că afectează și grupul de caractere
din intervalul 0x5b-0x60, ceea ce probabil nu a fost intenția programatorului.

prototip: void change_case(char *s);

Pe lângă această documentație per-funcție, îndată ce ați înțeles fiecare constituent, mai trebuie documentat și programul în sine ca un tot unitar: argumente (dacă are), I/O, funcționalități, asemeni unui man page pentru un utilitar (ex. man ls de referință).

2. Optimizare de viteză - 35p

Programul a fost scris printr-o serie de decizii neinspirate în ceea ce privește eficiența. Identificați greșelile de programare ce duc la o pierdere în viteză (iterații redundante în cadrul buclelor, apeluri redundante de funcții, exces de operații cu memoria ce ar putea fi transformate în operații pe registre șamd.) Identificați-le în partea de analiză și corectați-le astfel încât să obțineți un câștig de performanță (timp de execuție/număr de cicli de procesor cât mai mic), păstrând intactă funcționalitatea programului.

Descrieți în README ce optimizări ați implementat și ce decizii de optimizare ați luat (schimbări de algoritm, de funcții apelate, de citire/scriere date șamd.)

3. Optimizare de dimensiune - 35p

În mod similar, ne propunem să scriem un program având aceeași funcționalitate, însă binarul generat să aibă o dimensiune cât mai mică. Puteți realiza acest lucru înlocuind diverse instrucțiuni prin unele echivalente, dar cu dimensiune redusă, sau găsind și eliminând instrucțiuni redundante.

Similar cu punctul anterior, documentați în README ce optimizări de dimensiune ați implementat.

Setup

Pentru dezvoltarea temei va trebui să folosiți mașina virtuală de Linux de 32 de biți descrisă în secțiunea Mașini virtuale din pagina de resurse. Această mașină virtuală este folosită și pentru verificarea temei pe vmchecker.

Trimitere și notare

Temele vor trebui încărcate pe platforma vmchecker (în secțiunea IOCLA) și vor fi testate automat. Arhiva încărcată va fi o arhivă .zip care trebuie să conțină:

  • Fișierul README care să conțină analiza statică și dinamică a programului
  • Un Makefile care să genereze cele două executabile optimizate: `tema3_speed` și `tema3_size`
  • Sursele necesare pentru asamblare

Punctajul final acordat pe o temă este compus din:

  • 50 puncte README
  • 35 puncte se vor obține în funcție de clasamentele pe care le puteți observa în timp real pe site-ul aferent temei. Aceste puncte se vor obține în funcție de unde va clasați în cele două topuri (viteza și dimensiune). Punctajele sunt aditive (35 puncte pentru fiecare top). Formula de punctare este: puncte student de pe locul întâi în clasament: 35, puncte pentru student de pe poziția k în clasament : max(35 - k * (rezultat student de pe poziția k - rezultat primul student), 0). Prin rezultat se va înțelege dimensiune fișier încărcat sau viteza cu care acesta rulează pe testele noastre.

Temele optimizate vor fi submise unor serii de teste de funcționalitate. Nu veți primi punctaj pentru optimizări dacă tema nu păstrează aceleași funcționalități ca programul inițial.

Precizări suplimentare

  • Scheletele sunt un punct de început, însă puteți reimplementa întregul program, dacă considerați că acest lucru vă oferă un avantaj.
  • Nu aveți voie să generați programul din cod C sau să adaptați dintr-o sursă de asamblare generată dintr-o sursă C. Astfel de submisii nu vor fi luate în considerare.
  • Nu aveți voie să folosiți flag-uri de compilare sau utilitare de compresie binară ce vă oferă un avantaj fie în materie de viteză, fie de dimensiune.
  • Programul rezultant trebuie să fie un binar static, similar cu cel din arhivă. Nu trebuie să folosească referințe din biblioteci externe (i.e. puts, printf etc.)
  • Întrucât nu fac obiectul cursului și laboratorului de IOCLA, nu aveți voie să folosiți instrucțiuni specifice extensiilor (FPU/SSE/AVX).

Recomandări

Întrucât binarul nu are simboluri (funcțiile nu au nume, sunt doar adrese), este foarte dificil de lucrat doar cu objdump și gdb, recomandăm IDA Free pentru analiza statică. Vă simplifică viața prin faptul că vă permite să redenumiți variabile și funcții și să adăugați comentarii pe marginea codului, iar codul poate fi afișat sub forma unui graf din care se văd mai clar buclele.

La partea de analiză, încercați să vedeți ”big picture” și să evitați să vă pierdeți prea mult în detalii. Inspirați-vă din exemplul oferit mai sus.

Pentru optimizarea de dimensiune, puteți să vă folosiți de `rasm2` din suita radare2 pentru a găsi candidați de instrucțiuni echivalente, dar mai scurte. Exemplu:

    > rasm2 -b 32 "mov eax, 0"
    b800000000
    > rasm2 -b 32 "xor eax, eax"
    31c0

Resurse ajutătoare

Arhiva ce conține fișierele de la care puteți începe implementarea este aici.

iocla/teme/tema-3.txt · Last modified: 2019/01/10 14:03 by vladimir.diaconescu
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