O tym, jak napisać prosty kompilator asemblera po linuksem

RobertG

Użytkownik
Dołączył
Styczeń 3, 2007
Posty
391
[FONT=FreeSans, sans-serif]Wstęp[/FONT]

[FONT=FreeSans, sans-serif]Swego czau obmyśliłem prosty asembler, do którego był kompilowany inny język. Ten aembler był wykonywany przez maszynę wirtualną. Teraz piszę kompilator tego asemblera do pliku wykonywalnego, napisany jest w C, pod Linuksem. [/FONT]


[FONT=FreeSans, sans-serif]Przedstawię tu pokrótce, jak to działa, gdyż temat te wydaje mi się dość ciekawy, a też mało popularny. Kod jest w początkowej fazie i mało jest tam do analizowania, a jednocześnie IMHO jest na tyle dobry, że dalsze rzeczy będzie w miarę łatwo dodać.[/FONT]


[FONT=FreeSans, sans-serif]Potrzebne będą nam następujące oprogramowanie oraz wiedza z zakresu:[/FONT]

  • [FONT=FreeSans, sans-serif]podstawy asemblera, C, Makefile[/FONT]
  • [FONT=FreeSans, sans-serif]Linux z pakietami libelfg0 oraz libelfg0-dev[/FONT]
  • [FONT=FreeSans, sans-serif]git (by ściągnąć kod z repozytorium)[/FONT]
  • [FONT=FreeSans, sans-serif]objdump[/FONT]


[FONT=FreeSans, sans-serif]Projekt można ściągnąć pomocy git'a klonując repozytorium:[/FONT]
[FONT=helvetica, arial, freesans, clean, sans-serif][FONT=FreeSans, sans-serif]
[/FONT]
[/FONT]

Kod:
git clone [EMAIL="[email protected]"][email protected][/EMAIL]:RobertGawron/l33tlang.git
[FONT=FreeSans, sans-serif]Możliwości kompilatora[/FONT]
[FONT=FreeSans, sans-serif]
[/FONT]

[FONT=FreeSans, sans-serif]No na razie nie ma tego dużo:[/FONT]

  • [FONT=FreeSans, sans-serif]obsługa przerwania, które kończy działanie programu[/FONT]
  • [FONT=FreeSans, sans-serif]dodawanie[/FONT]
  • [FONT=FreeSans, sans-serif]odkładanie na stos[/FONT]
  • [FONT=FreeSans, sans-serif]procedury (których póki co nie można wywoływać)[/FONT]


[FONT=FreeSans, sans-serif]T[/FONT][FONT=FreeSans, sans-serif]eoria[/FONT]


[FONT=FreeSans, sans-serif]Pliki wykonywalne w Linuksie oparte są o pliki ELF, jest to dość stary standard, powszechny w środowiskach Uniksowych. Każdy taki plik jest plikiem binarnym, tzn. Nie otworzymy go zwykłym edytorem tekstu (vim, emacs), lecz musimy skorzystać z wyspecjalizowanego programu, który potrafi go odczytać I przedstawić nam o nim (tzn. o pliku) informacje. Takie narzędzia to m.in. readelf oraz objdump, ja wykorzystałem ten drugi. O tym, jak go wykorzystać powiem później.[/FONT]


[FONT=FreeSans, sans-serif]Plik ELF składa się z sekcji, informacje w nich zawarte są ze sobą powiązane, najważniejsze z nich to:[/FONT]

  • [FONT=FreeSans, sans-serif]symtab – tablica symboli, służy m.in. Do tego, by wskazac, w którym miejcu zaczynają się procedury, nazwy tych funkcji zawarte są w strtab[/FONT]
  • [FONT=FreeSans, sans-serif]strtab – powiązana z symtab, nazwy procedur[/FONT]
  • [FONT=FreeSans, sans-serif]text – instrukcje do wykonania dla procesora[/FONT]
[FONT=FreeSans, sans-serif]Toolchain
[/FONT]


[FONT=FreeSans, sans-serif]W skrócie to pisanie takiego kodu u mie działa tak: kompiluję kompilator, odpalam makefile, który tym kompilowanym kmpilatorem kompiluje plik asemblera. Ta kompilacja przebiega pod okiem valgrinda, bym wiedział, czy są wycieki pamięci. Skompilowany plik wynikowy sprawdzam objdumpem (sekcje z rozkazami dla procesor), linkuję (ld – standardowy linker) sprawdzając czy nie ma błędów linkwania, a na końcu wykonuję (sprawdzając czy program się nie rozsypie.[/FONT]


[FONT=FreeSans, sans-serif]By przeprpwadzić te czynności trzeba ucruchomić makefile, w katalogu /dist/Debug/GNU-Linux-x86[/FONT] [FONT=FreeSans, sans-serif]Ma on postać:[/FONT]
Kod:
all:
        valgrind ./nativecompiler ./hello-*
        objdump -D foo.o 
        ld -s -o foo ./foo.o  -lelf -I/lib/ld-linux.so.2 
        ./foo

Plik asemblera ma postać:
Kod:
main:
    push 5
    push 3
    add
    call foo
    int 0
    int 2
    ret
foo:
    ret
bar:
    ret

[FONT=FreeSans, sans-serif]A wynik jest taki:[/FONT]
Kod:
rgawron@foo:/opt/l33tlang/NativeCompiler/dist/Debug/GNU-Linux-x86$ make
valgrind ./nativecompiler ./hello-*
==13616== Memcheck, a memory error detector.
==13616== Copyright (C) 2002-2007, and GNU GPL'd, by Julian Seward et al.
==13616== Using LibVEX rev 1804, a library for dynamic binary translation.
==13616== Copyright (C) 2004-2007, and GNU GPL'd, by OpenWorks LLP.
==13616== Using valgrind-3.3.0-Debian, a dynamic binary instrumentation framework.
==13616== Copyright (C) 2000-2007, and GNU GPL'd, by Julian Seward et al.
==13616== For more details, rerun with: -v
==13616==
call foo
 ; call is not implmented
==13616==
==13616== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 13 from 1)
==13616== malloc/free: in use at exit: 352 bytes in 1 blocks.
==13616== malloc/free: 14 allocs, 13 frees, 2,164 bytes allocated.
==13616== For counts of detected errors, rerun with: -v
==13616== searching for pointers to 1 not-freed blocks.
==13616== checked 69,732 bytes.
==13616==
==13616== LEAK SUMMARY:
==13616==    definitely lost: 0 bytes in 0 blocks.
==13616==      possibly lost: 0 bytes in 0 blocks.
==13616==    still reachable: 352 bytes in 1 blocks.
==13616==         suppressed: 0 bytes in 0 blocks.
==13616== Rerun with --leak-check=full to see details of leaked memory.
objdump -D foo.o

foo.o:     file format elf32-i386

Disassembly of section .text:

00000000 <_start>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   68 05 00 00 00          push   $0x5
   8:   68 03 00 00 00          push   $0x3
   d:   59                      pop    %ecx
   e:   5b                      pop    %ebx
   f:   01 cb                   add    %ecx,%ebx
  11:   90                      nop
  12:   b8 01 00 00 00          mov    $0x1,%eax
  17:   cd 80                   int    $0x80
  19:   c9                      leave
  1a:   c3                      ret

0000001b <foo>:
  1b:   55                      push   %ebp
  1c:   89 e5                   mov    %esp,%ebp
  1e:   c9                      leave
  1f:   c3                      ret

00000020 <bar>:
  20:   55                      push   %ebp
  21:   89 e5                   mov    %esp,%ebp
  23:   c9                      leave
  24:   c3                      ret
ld -s -o foo ./foo.o  -lelf -I/lib/ld-linux.so.2
./foo
make: *** [all] Error 8
[FONT=FreeSans, sans-serif]Pytanie na znajomość asma+linuka, czy Error, który pojawia się na końcu to błąd czy nie? :)[/FONT]


[FONT=FreeSans, sans-serif]Kod
[/FONT]

[FONT=FreeSans, sans-serif]Całośc napisana jest w sposób obiektowy, w C, jeśli się z tym nie spotkaliście, to może to wyglądać trochę dziwnie :) Jakbyście chcieli o tym więcej poczytać, to tutaj jest dłuższa notka o programowaniu zorientowanym obiektowo w C.[/FONT]
[FONT=FreeSans, sans-serif]
[/FONT]
[FONT=FreeSans, sans-serif]Obiekt world tworzy środowisko dla naszego parsera (nom, on też powinien być bardziej obiektowo napisany, to swoją drogą).[/FONT]


[FONT=FreeSans, sans-serif]Parser, po koleii pobiera linijki z pliku z asemblerem, a każdą z nich parsuje (tzn mając string “push 33”, probuje zrozumieć, że ma odłożyć na stos 33, że ma takie coś ma właście skompilować). Moje założenie było takie, by kompilacja każdej linijki pliku asemblera wymagała wczytania, posiadania informacji tylko tej linijce, by nie trzeba było sprawdzać, co jest linijkę wcześniej, lub później. [/FONT]


[FONT=FreeSans, sans-serif]Obsługa przerwań asemblera, generowanie odpowiadającego im kodu znajdje się wpliku interrupts.c.[/FONT]


[FONT=FreeSans, sans-serif]Do obsługi plików ELF wykorzystałem gotową bibliotekę (napisaną w C) o nazwie libelf. AFAIK mało jest o niej dokumentacji, lecz dośc ciekawym linkiem jest ten tutorial.[/FONT]

[FONT=FreeSans, sans-serif]Biblioteka ta jest ona dość toporna w obłudze, masę rzeczy trzeba ustawić ręcznie, dlatego obudowałem ją w swój zestaw funkcji (wzorują się na powyższym linku), dzięki czemu pisząć inne fragmęty kompilatora nie musiałem sie przejmować niskopoziomowymi detalami. Ta częśc jest też napisana nieobiektowo.[/FONT]


[FONT=FreeSans, sans-serif]W całym kodzie często przekazuję funkcję jako argument inne funkcje, tworze tablice funkcji etc, jest to rozwiązanie znacznie czytelniejsze, niż siermiężne drabinki if-else. W przyszłości poprawię to I zamiast zwykłych tablic zastosuję struktury.[/FONT]
 
Ostatnia edycja:

RobertG

Użytkownik
Dołączył
Styczeń 3, 2007
Posty
391
Notka była zbyt długa na jeden post, więc tu jest ciąg dalszy:

[FONT=FreeSans, sans-serif]Dokumentacja[/FONT]
[FONT=FreeSans, sans-serif]
[/FONT]
[FONT=FreeSans, sans-serif]Ogólna znajduje się na stonie projektu.[/FONT]
[FONT=FreeSans, sans-serif]Dokumentacja techniczną można wygenerować doxygenem (o ile macie zainstalowanego), jest do tego dopisana osobna regułka w Makefile, więc wystarczy odpalić:[/FONT]
Kod:
make docs
[FONT=FreeSans, sans-serif]dokumentacja znajduje się w katalogu html.
[/FONT]


[FONT=FreeSans, sans-serif]Rzeczy do zrobienia
[/FONT]
[FONT=FreeSans, sans-serif]Jest cała masa błędów, niedoróbek, np pusta linia w pliku asemblera wywala kompilator :) Niedługo skończę wreszcie pisać wywoływanie funkcji bo już mam to wstępnie przeanalizowane. Użycie w asemblerze liczb dziesiętnych zamiast szesnastkowych to był epic fail..[/FONT]


[FONT=FreeSans, sans-serif]Plany na przyszłość
[/FONT]
wielowątkowość


[FONT=FreeSans, sans-serif]W razie pytań, uwag i sugestii piszcie.[/FONT]
 

grzonu

Były Moderator
Dołączył
Grudzień 26, 2006
Posty
1390
Bardzo dobrze ze cos takiego powstaje ;)
Pisz dalej bo warto zobaczyc efekty koncowe :)
 

codex

Użytkownik
Dołączył
Październik 30, 2012
Posty
25
odgrzewam ponieważ w temacie wszystko będzie:
- może ktoś podać namiar na jakieś objaśnienie składni opcodu IA32 ? gdzieś miałem i mi wsiąkło gdy potrzebne.
Z góry dzięki.
 

codex

Użytkownik
Dołączył
Październik 30, 2012
Posty
25
składnia zależy od asemblera.
"standardowa" jest składnia MASMu, intelowe manualy używają ją,
ale w systemach jako Linux rozpowszechniona jest składnia AT&T, uznasz o nią tu: http://asm.sourceforge.net/articles/linasm.html

Nie no składnia opcodu zależy od architektury cpu i nie ma nic wspulnego ze składnią masma, innego asemblera ani też z systemem operacyjnym. Instrukcje asemblera są przez kompilator zapisywane w postaci opkodów które mają swoją składnię, Te z kolei są w odpowiedni sposób dekodowane przez procesor. Są to podstawowe informacje niezbędne do napisania kompilatora więc myślałem, że uzyskam informację w tym temacie ...

Ale może inaczej zapytam:
Jakiś desasembler który pozwoli mi zdekodować bootsector systemu operacyjnego bym potrzebował. IDA wyświetla tylko wartości poszczególnych bajtów pliku :mad:
 

shoorick

Użytkownik
Dołączył
Lipiec 17, 2008
Posty
66
za dużo niestety, ale przez złożoność procesora. lecz mogę poradzić wziąć jedną komendę, naprzykład, MOV, i ję studijować. kiedy wyjaśnisz wszyści ję warianty, inne komendy będą już latwej zrozumieć przez podobność.
można napisać prosty program, lepej w fasmie:
napisałeś:
mov al,bl
skompilowałeś sam i przez fasm, a możesz porówniać czy podobne wyniky są (nawet kiedy niepodobne są istnieje możliwość że oba są poprawne dlatego że pewne komendy mają kilka wariantów kodowania)
albo używaj ten resurs: http://ref.x86asm.net/coder32.html
przez nego możesz deasembluwać przez ręce, naprzykład, kiedy spotkasz bajt "37h", przyciszkasz tam link "37" i trafisz do tablicy, odnaleziesz że to jest AAA opkod, i tak dalej
 

codex

Użytkownik
Dołączył
Październik 30, 2012
Posty
25
Tak dokładnie próbowałem to robić tak jak mówisz zanim napisałem na forum oczywiście ale to żmudna robota by się z tego zrobiła, poniżej przykład masz:

Kod:
format pe gui 4.0
   entry  start
   include 'win32ax.inc'
   include 'n\codehelp.inc'

section '.import' import data readable
library kernel32,'kernel32',user32,'user32.dll'
        include   'api\kernel32.inc'
        include   'api\user32.inc'

section '.noname' code readable executable
start_bis:
        ret

section '.flat' code data readable writeable executable

        table:
                int     12h
safespace        rb     100
        sizeof.table    = ($-table)

start:

        xor     eax, eax
        mov     al, [table +0]
        show    eax, 'eax'
        ret

;       WYNIKI:
;       ------------------------------------------------------------------
;       CD 10                   = INT         10h
;       CD 13                   = INT         13h
;       ------------------------------------------------------------------
;       B8                      = mov         eax, 0
;       BB                      = mov         ebx, 0
;       B9                      = mov         ecx, 0
;       BA                      = mov         edx, 0
;       ------------------------------------------------------------------
;       EB  FE                  = jmp short   label
;       FF  E0                  = jmp         eax
;       E9  FB EF FF FF         = jmp         00 40 30 00  (start_bis)
;       ------------------------------------------------------------------
;       FA                      = cli
;       FB                      = sti
;       31 C0                   = xor         eax, eax
;       31 DB                   = xor         ebx, ebx
;       ------------------------------------------------------------------
Teraz cel jest taki:
Przeanalizować co jest w bootsektorze a co powinno być czyli krótko mówiąc - czy nie ma bonusów jakichś :) .Przyznasz, że taką metodą to ja bym szybko tego nie ustalił

Co do tej tablicy -już lepiej spróbuję tą metodą, konkretny link wrzucę w zakładki bo na pewno nie raz się przyda - dzięki.

PS. Jeżeli piszesz w fasm na codzień to może wymienimy się jakimiś projektami ... wysle ci maila na PW
 
Ostatnia edycja:

shoorick

Użytkownik
Dołączył
Lipiec 17, 2008
Posty
66
z fasmem możesz zrobić "program" z jedyną linię:
Kod:
    mov al,bl

wynik:
Kod:
Offset      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F


00000000   88 D8
 
Do góry Bottom