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.

Author: jww, 2012-03-01

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.

 30
Author: Daniel Kamil Kozar,
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