Open Source im professionellen Einsatz

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 13

Kern-Technik

,

Systemaufrufe bilden die Schnittstelle zwischen Userspace und Kernel. Dieser Artikel zeigt, wie sie funktionieren und wie man sogar eigene Systemcalls implementiert.

Das Betriebssystem verwaltet Ressourcen wie Rechenzeit, Speicher oder Geräte und stellt Dienste dafür zur Verfügung: Will eine Anwendung auf Hardware zugreifen, tut sie das nicht direkt, sondern vermittelt durch den Kernel, der im Prozessormodus mit der dafür nötigen Priorität läuft. Für die meisten Zwecke bringt das Betriebssystem passende Interface-Funktionen mit, die so genannten Systemaufrufe, die einen definierten Übergang vom User- in den Kernelmodus darstellen. Mehr als 280 Systemcalls finden sich in Kernel 2.6.6.

Durchnummeriert

Jeder Systemcall besitzt eine eindeutige Nummer, die ihm in der Headerdatei »asm/unistd.h« (siehe Listing 1) zugeordnet wird. Der Systemcall »fork«, der einen neuen Prozess erzeugt, trägt beispielsweise die Nummer 2, »write« entspricht 4 und »gettimeofday« der 78. Normalerweise ruft die C-Bibliothek Glibc hinter den Kulissen solche Systemcalls auf, wenn eine Anwendung entsprechende Funktionen benutzt. Um einen Systemcall direkt aufzurufen, muss manuell ein Software-Interrupt ausgelöst werden. Da die Programmiersprache C hierzu keine Funktion anbietet, braucht der Entwickler dafür Assembler.

Listing 1: Auszug aus »asm/unistd.h«

01 #ifndef _ASM_I386_UNISTD_H_
02 #define _ASM_I386_UNISTD_H_
03 
04 /*
05  * This file contains the system call numbers.
06  */
07 
08 #define __NR_restart_syscall      0
09 #define __NR_exit                 1
10 #define __NR_fork                 2
11 #define __NR_read                 3
12 #define __NR_write                4
13 #define __NR_open                 5
14 #define __NR_close                6
15 ...

Es gibt mehrere Software-Interrupts. Für Systemcalls kommt traditionell die Nummer 0x80 zum Einsatz, auch wenn es noch andere Wege gibt, um in den Kernel zu wechseln, siehe Kasten "Intel kann es schneller". Der Interrupt löst den Übergang in den privilegierten Kernelmodus aus. Der Kernel selbst erwartet die Nummer, die den auszuführenden Dienst kennzeichnet (also beispielsweise 4 für »write«), in einem Register, auf einer x86-Plattform im EAX. Die zum Systemcall gehörigen Parameter werden ebenfalls in Registern abgelegt. Abbildung 1 zeigt die Register eines x86-Prozessors, weitere Informationen finden sich unter[1].

Intel kann es schneller

Einen Software-Interrupt über den Befehl »int« aufrufen ist auf einem x86-Prozessor vergleichsweise Ressourcen-intensiv. Bereits seit dem Pentium II hat Intel in den Befehlssatz seiner Prozessoren daher das neue Befehlspaar »sysenter« und »sysexit« aufgenommen. Mit ihm führt der Kernel Systemcalls erheblich schneller aus.

Allerdings ist ein Wechsel vom bisherigen »int«-Mechanismus zum »sysenter«-Mechanismus nicht ohne weiteres möglich. Da es sich um die Schnittstelle zwischen User- und Kernelspace handelt, müssen sowohl die Anwendungen als auch der Kernel angepasst werden. Die größte Schwierigkeit ist aber, dass es »sysenter« nicht bei allen x86-kompatiblen Prozessoren gibt. Deshalb führt Kernel 2.6 die so genannte »vsyscall«-Page ein. In den Adressraum einer Applikation wird eine Speicherseite eingeblendet, in der abhängig vom Prozessor entweder der Code für den Software-Interrupt oder für »sysenter« zu finden ist.

Den bisherigen Aufruf »int $0x80« ersetzt der Programmierer durch einen Unterprogrammaufruf an die Adresse »0xfffe400«. Der Beispielaufruf aus Listing 2 ist mit den entsprechenden Änderungen in Listing 3 dargestellt. Wird der Code mit diesem »make hello2« übersetzt und aufgerufen, muss ebenfalls "Hello World" auf dem Bildschirm erscheinen.

Abbildung 1: Die Parameterübergabe zwischen User- und Kernelspace läuft über die Register des x86-Prozessors: Im EAX steht die Nummer des Syscall, die anderen nehmen dessen Parameter auf.

Abbildung 1: Die Parameterübergabe zwischen User- und Kernelspace läuft über die Register des x86-Prozessors: Im EAX steht die Nummer des Syscall, die anderen nehmen dessen Parameter auf.

Parameter in Registern

Listing 2 ruft in Assembler den Systemcall »write« auf. Der Filedeskriptor, die Adresse für die Ausgabe und die Anzahl der schreibende Bytes liegen nacheinander in den Registern EBX, ECX und EDX. Um aus dem Assembler-Code ein Programm zu erzeugen, reicht der Aufruf von »make hello« aus. Zum selben Ergebnis führt »gcc -o hello hello.S«.

In den seltensten Fällen schreibt der Anwendungsentwickler seinen Assembler- Code selbst. Für die wichtigsten Systemcalls gibt es in der Standard-C-Library entsprechende Funktionen, die er nur aufzurufen braucht. Aber auch wenn der Systemcall nicht über eine Bibliotheksfunktion zur Verfügung steht, ist Assembler-Programmierung kein Muss. Vielmehr stellt der Kernel einen Satz Makros bereit und die Standardbibliothek hat eine Systemcall-Funktion.

Listing 2: »hello.S«

01 .text
02 .globl main
03 main:
04   movl $4,%eax        ; //Code fuer "write" systemcall
05   movl $1,%ebx        ; //File descriptor fd (1=stdout)
06   movl $message,%ecx  ; //Adresse des Textes (buffer)
07   movl $12,%edx       ; //Laenge des auszugebenden Textes
08   int $0x80           ; //SW-Interrupt, Auftrag an das BS
09   ret
10 .data
11 message:
12   .ascii "Hello Worldn"

Die »_syscall X()«-Makros versehen einen Systemcall mit einem Funktionsnamen, den der Programmierer später verwenden kann. Das » X« steht dabei für die Anzahl der Parameter. Um beispielsweise den Systemcall »write« mit seinen drei Parametern zu nutzen, kommt das Makro »_syscall3()« zum Einsatz. Listing 4 zeigt den Aufruf.

Listing 4: Syscall mit »_syscallX«-Makro

01 #include <asm/unistd.h>
02 #include <errno.h>
03 
04 _syscall3( int, write, int, fd, char *, buffer, int,  size );
05 
06 int main( int argc, char **argv )
07 {
08         write( 1, "hello worldn", 13 );
09         return 0;
10 }

Da die »_syscall X()«-Makros intern auf die globale Variable »errno« zugreifen, muss der entsprechende Header eingebunden sein (Zeile 2). Außerdem ist sicherzustellen, dass der Compiler die Headerdateien des Kernels 2.6 verwendet, zum Beispiel durch den Aufruf:

cc -Wall -I/usr/src/linux-2.6.6/include -o  syscallX syscallX.c

Der erste Parameter des »_syscall X()«-Makros steht für den Typ des Systemcall-Rückgabewerts. Der zweite Parameter entspricht dem Namen des Systemcall, der als solcher in der Datei »asm/unistd .h« festgelegt ist. Der dortige Namensvorsatz »__NR_« ist allerdings wegzulassen; aus »__NR_write« wird damit »write«. Danach folgt - immer paarweise - die Beschreibung der einzelnen Parameter: zuerst der Typ, dann der Name. Zum Einsatz der Makros ist die Datei »asm/unistd.h« einzubinden.

Listing 3: »hello2.S«

01 .text
02 .globl main
03 main:
04   movl $4,%eax        ; //Code fuer "write" systemcall
05   movl $1,%ebx        ; //File descriptor fd (1=stdout)
06   movl $message,%ecx  ; //Adresse des Textes (buffer)
07   movl $12,%edx       ; //Laenge des auszugebenden Textes
08   call 0xffffe400     ; //Auftrag an das BS
09   ret
10 .data
11 message:
12   .ascii "Hello Worldn"

Diesen Artikel als PDF kaufen

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook