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]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]
[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]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]
[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]
Plik asemblera ma postać:
[FONT=FreeSans, sans-serif]A wynik jest taki:[/FONT]
[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]
[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]
[/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]
[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]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: