1. Można by stworzyć zewnętrzny plik z funkcją w assemblerze, i kompilować go do obiektu (GNU AS), a następnie linkować z resztą projektu.
2. Jak wyżej, tylko skompilować nasm'em (Netwide Assembler) i wrzucić kod w stringa (ASCIIZ), a następnie stringa przerzutować na wskaźnik do funkcji (to może brzmieć dziwnie, ale zazwyczaj właśnie tak to robię).
3. Podpatrzeć jak gcc/g++ kompilują funkcję do assemblera, a następnie użyć inline assemblera do stworzenia funkcji.
Pierwszy punkt odrzuciłem ponieważ nie chciałem tworzyć dodatkowych plików źródłowych. Drugi punkt odpadł ponieważ kod miał być w miarę czytelny i łatwo modyfikowalny. Została więc trzecia metoda.
Podpatrzenie jak gcc/g++ kompilują jest dość trywialne - służy do tego opcja -S (zamiast pliku wykonywalnego/obiektowego zostaje utworzony plik .s z kodem w assemblerze) . W zależności czy wolimy składnie Intel czy AT&T można dodać dodatkową opcję -masm=intel (lub jej nie dodawać). Mi to osobiście bez różnicy, więc zostawiłem domyślną AT&T.
Testy przeprowadziłem na prostej funkcji gimme_five, która po prostu zwracała 5. Poniższy listing jest wynikiem kompilacji kodu C za pomocą MinGW gcc.
.globl _gimme_five
.def _gimme_five; .scl 2; .type 32; .endef
_gimme_five:
pushl %ebp
movl %esp, %ebp
movl $5, %eax
popl %ebp
ret
Najważniejsza w powyższym listingu jest deklaracja etykiety (3cia linia), czyli _gimme_five: (należy pamiętać że przy kompilacji MinGW gcc dodaje podkreślenie, linuxowe gcc tego nie robią, DJGPP również nie). Dwie linie wyżej, czyli .globl _gimme_five oraz ta z .def są opcjonalne. Pierwsza z nich jest przydatna gdy chcemy aby funkcja była widoczna w momencie linkowania (czyli gdybyśmy dodali static, to .globl by się nie pojawiło). Druga linia natomiast służy do określenia dodatkowych opcji, wpływających głównie na wygląd funkcji w pliku obiektowym [Using as (GNU Binutils version 2.17.90) - Assembler Directives] - osobiście ją całkowicie pominąłem.
By the way...
There are more blog posts you might like on my company's blog: https://hexarcana.ch/b/
Zoptymalizowany kod powyższej funkcji, wraz z koniecznymi dyrektywami assemblera wygląda następująco:
.globl _gimme_five
_gimme_five:
movl $5, %eax
ret
Przepisując to na C otrzymujemy następujący kod (trzeba dodać oczywiście deklarację funkcji, w końcu kompilator C musi wiedzieć iż taka funkcja istnieje):
int gimme_five(void);
__asm(
".globl _gimme_five\n"
"_gimme_five:\n"
" movl $5, %eax\n"
" ret"
);
Kolejną sprawą jest zastosowanie powyższego schematu w C++ - dochodzi sprawa dekoracji nazw funkcji. Funkcja int gimme_five(void) zostanie w C++ (MinGW g++) przerobiona na __Z10gimme_fivev, w związku z czym należy dodać dodatkową etykietę z dekoracjami (o tyle dobrze że mogą być dwie etykiety do jednego miejsca naraz ;>):
int gimme_five(void);
__asm(
".globl __Z10gimme_fivev\n"
".globl _gimme_five\n"
"__Z10gimme_fivev:\n"
"_gimme_five:\n"
" movl $5, %eax\n"
" ret"
);
Oczywiście równie dobrze zamiast podwajania etykiet można deklarację funkcji wrzucić w extern "C" { }.
Niestety powyższa metoda uniemożliwia korzystanie z nazw argumentów funkcji. Ale z drugiej strony to może nawet lepiej - kompilator z Microsoft przy __declspec(naked) też nie najlepiej sobie radzi z nazwanymi argumentami, a konkretniej to generuje niedziałający kod (kilka razy zdarzyło mi się szukać kilka godzin buga tylko po to by się przekonać że wygenerowany kod w asmie jest niedostosowany do braku prologu/epilogu funkcji).
Obejście tego problemu polega na stworzeniu dwóch funkcji - wrappera "naked" w assemblerze, oraz normalnej funkcji w C która by z wrappera była wywoływana. Wrapper ustawiałby argumenty/środowisko, dzięki czemu funkcja mogła by być zbudowana normalnie.
OK, tyle na teraz ;>
By the way...
There are more blog posts you might like on my company's blog: https://hexarcana.ch/b/
Update 1:
Poprawiłem terminatory linii. Bylo \r\n a powinno być \n (przy czym obie wersje powinny działać... na Windowsie; nie jestem pewien czy ruszyłyby na innych systemach).
Update 2:
Oczywiście, zamiast rozbijać linie w ten sposób:
__asm("line1 \n"
"line2 \n"
"line3");
...możemy użyć backslasha i zrobić to w ten sposób (imo wygodniejszy i czytelniejszy, chociaż do perfekcji jeszcze trochę mu brakuje):
__asm("line1 \n\
line2 \n\
line3");
Update 3:
See also: Lazy Coding - DLLExport with naked functions.
Comments:
"latwo modyfikowalne"
it does not compute :)
(żródło - http://osdev.pl/wiki/index.php/Kurs_pisania_OS_cz._III)
int gimme_five() {
__asm (
".globl gimme_five\n"
".type gimme_five, @function\n"
"gimme_five:\n" // <- o, tutaj powinno być _gimme_five :)
" mov eax,0x5\n"
" pop rbp\n"
" ret"
);
}
W przeciwnym wypadku powoduje to błąd kompilacji:
gcc -masm=intel -Wall -g -o test test.c
(...)
test.c: Assembler messages:
test.c:23: Error: symbol `gimme_five' is already defined
Przy okazji: czy jest jakiś sposób, by uniknąć ostrzeżenia kompilatora, że f-cja int gimme_five() nie zwraca zadeklarowanego typu? Bo instrukcja powrotu z f-cj znajduje się przecież we wstawce asemblerowej i kompilator tego nie uwzględnia, gdy kompilujemy program gcc z opcją -Wall
Hehe chyba trochę nie do końca dokładnie przeczytałeś post :)
Zauważ, że ja nie definiuje funkcji gimme_five w C (tj. nie daje { } po int gimmie_five()), a jedynie ją deklaruje (tj. po int gimmie_five() jest średnik).
Przy definiowaniu funkcji w taki sposób jak Ty to zrobiłeś... no cóż, w zasadzie gcc ma rację - redefiniujesz funkcję.
Bottom line: jeśli chcesz zrobić funkcję typu naked, to nie definiuj funkcji w C, a jedynie ją zadeklaruj.
Mea culpa :)
Teraz wszystko jasne. Dziękuję za sprostowanie :)
;)
Niedawno pisałem aplikację i miałem z tym problem. Rozwiązałem go w dość nieładny sposób - kod funkcji naked wrzuciłem na koniec maina* i wywoływałem ją z wstawki w środku... Pisałem w Visualu i nie wiedziałem, że jest taka opcja, by zrobić sobie funkcję naked :P Ciągle dowiaduję się czegoś nowego ;)
* przed chwilą zrobiłem test (bo nie byłem pewny :P) i okazuje się, iż w Visualu (VC++ 2010 Exp) nie wolno - w każdym razie bez szperania w ustawieniach czego oczywiście nie dokonałem ^^ - umieszczać wstawek w zasięgu globalnym - "error C2059: syntax error : '__asm'"
Pozdrawiam,
Mrowqa
Add a comment: