Jak wywołać wywołanie systemowe przez sysenter w wbudowanym zestawie?
Jak zaimplementować wywołanie systemowe używając sysenter / syscall bezpośrednio w x86 Linux? Czy ktoś może pomóc? Byłoby jeszcze lepiej, gdybyś mógł również pokazać kod dla platformy amd64.
Wiem, że w x86 możemy użyć
__asm__(
" movl $1, %eax \n"
" movl $0, %ebx \n"
" call *%gs:0x10 \n"
);
Przekierowanie do sysenter pośrednio.
Ale jak możemy kodować używając sysenter/syscall bezpośrednio do wywołania systemowego?
I find some material http://damocles.blogbus.com/tag/sysenter /. Ale nadal trudno jest zrozumieć Wynocha.
1 answers
Pokażę Ci, jak wykonać wywołania systemowe, pisząc program, który zapisuje Hello World!
na standardowe wyjście za pomocą wywołania systemowego write()
. Oto źródło programu bez implementacji rzeczywistego wywołania systemowego:
#include <sys/types.h>
ssize_t my_write(int fd, const void *buf, size_t size);
int main(void)
{
const char hello[] = "Hello world!\n";
my_write(1, hello, sizeof(hello));
return 0;
}
Widzisz, że nazwałem swoją funkcję wywołania systemowego my_write
, aby uniknąć kolizji nazw z "normalnym" write
, dostarczanym przez libc. Reszta odpowiedzi Zawiera źródło my_write
dla i386 i amd64
I386
Wywołania systemowe w Linuksie i386 są implementowane przy użyciu 128 wektora przerwań, np. przez wywołanie int 0x80
w kodzie assembly, oczywiście po uprzednim ustawieniu parametrów. Możliwe jest wykonanie tego samego przez SYSENTER
, ale w rzeczywistości wykonanie tej instrukcji jest osiągane przez vdso praktycznie odwzorowane na każdym uruchomionym procesie. Ponieważ SYSENTER
nigdy nie było przeznaczone jako bezpośrednie zastąpienie API int 0x80
, nigdy nie jest bezpośrednio wykonywane przez aplikacje w userland - zamiast tego, gdy aplikacja musi uzyskać dostęp do kodu jądra, wywołuje wirtualnie zmapowaną procedurę w VDSO (do tego służy call *%gs:0x10
w Twoim kodzie), która zawiera cały kod obsługujący instrukcję SYSENTER
. Jest tego dość dużo, ponieważ instrukcja naprawdę działa.
Jeśli chcesz przeczytać więcej na ten temat, spójrz na ten link. Zawiera dość krótki przegląd technik stosowanych w jądrze i vdso.
#define __NR_write 4
ssize_t my_write(int fd, const void *buf, size_t size)
{
ssize_t ret;
asm volatile
(
"int $0x80"
: "=a" (ret)
: "0"(__NR_write), "b"(fd), "c"(buf), "d"(size)
: "cc", "edi", "esi", "memory"
);
return ret;
}
As you widać, że używanie API {[8] } jest stosunkowo proste. Numer syscall trafia do rejestru eax
, podczas gdy wszystkie parametry potrzebne do syscall trafiają odpowiednio do ebx
, ecx
, edx
, esi
, edi
, i ebp
. Numery wywołań systemowych można uzyskać odczytując plik /usr/include/asm/unistd_32.h
. Prototypy i opisy funkcji są dostępne w drugiej części podręcznika, więc w tym przypadku write(2)
. Ponieważ jądro może niszczyć praktycznie każdy z rejestrów, umieszczam wszystkie Pozostałe GPRs na liście clobber, a także cc
, ponieważ rejestr eflags
również może się zmienić. Należy pamiętać, że lista clobber zawiera również parametr memory
, co oznacza, że instrukcja wymieniona na liście instrukcji odwołuje się do pamięci (poprzez parametr buf
).
Amd64
Sprawy wyglądają zupełnie inaczej na architekturze AMD64, która zawiera nową instrukcję o nazwie SYSCALL
. Bardzo różni się od oryginalnej instrukcji SYSENTER
i zdecydowanie dużo łatwiejsze w użyciu z aplikacji userland-faktycznie przypomina normalną CALL
, a dostosowanie starej int 0x80
do nowej SYSCALL
jest dość trywialne.
W tym przypadku numer wywołania systemowego jest nadal przekazywany do rejestru rax
, ale rejestry używane do przechowywania argumentów znacznie się zmieniły, ponieważ teraz powinny być używane w następującej kolejności : rdi
, rsi
, rdx
, r10
, r8
i r9
. Jądro może niszczyć zawartość rejestrów rcx
i r11
(służą do zapisywania niektórych innych rejestrów przez SYSCALL
).
#define __NR_write 1
ssize_t my_write(int fd, const void *buf, size_t size)
{
ssize_t ret;
asm volatile
(
"syscall"
: "=a" (ret)
: "0"(__NR_write), "D"(fd), "S"(buf), "d"(size)
: "cc", "rcx", "r11", "memory"
);
return ret;
}
Zauważ, że praktycznie jedyną rzeczą, która wymagała zmiany, były nazwy rejestrów i faktyczna Instrukcja używana do wykonania połączenia. Dzieje się tak głównie dzięki listom wejść/wyjść dostarczanym przez rozszerzoną składnię wbudowanego montażu gcc, która automagicznie dostarcza odpowiednie instrukcje ruchu potrzebne do wykonania listy instrukcji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-07 20:15:45