Systemaufrufe: Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
KKeine Bearbeitungszusammenfassung
 
(4 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 109: Zeile 109:
Eine häufig verwendete Referenz für Systemaufrufe ist die Man-Seite ([https://man7.org/linux/man-pages/man2/syscalls.2.html man 2 syscall]). Eine umfassendere Liste spezifisch für den ARM64-Architektur habe ich hier erstellt: [[Übersicht der Linux ARM64 Systemaufrufen]]
Eine häufig verwendete Referenz für Systemaufrufe ist die Man-Seite ([https://man7.org/linux/man-pages/man2/syscalls.2.html man 2 syscall]). Eine umfassendere Liste spezifisch für den ARM64-Architektur habe ich hier erstellt: [[Übersicht der Linux ARM64 Systemaufrufen]]


== Fehlercodes ==
Wenn ein Systemcall ausgeführt wird (z. B. write, open, read), prüft der Kernel, ob die Anfrage gültig ist, und führt sie aus – oder gibt einen Fehlercode zurück, wenn etwas schiefgeht.


-------------------
Viele Systemcalls geben in x0 ein Ergebnis wie zum Beispiel die Anzahl von Bytes, ein Dateideskriptor, PID oder anderes zurück, wenn alles geklappt hat. Sollte allerdings ein negatives Ergebnis (errno) zurück kommen, so hat in der Regel etwas nicht funktioniert.


Wenn x0 negativ -> negieren -> dann Fehler:
Beispiel:
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h
<syntaxhighlight lang="asm">
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h
    // Datei öffnen (openat)
    mov    x0,  AT_FDCWD        // aktuelles Verzeichnis
    ldr    x1,  =filename      // Dateiname
    mov    x2,  #0              // O_RDONLY
    mov    x8,  #56            // syscall number für openat
    svc    #0


In der Parameterübergabe erwarten viele SystemCalls eine Struktur. Ein einfacher SystemCall wie nanosleep wird als C-Code wie folgt aufgerufen:
    // Rückgabewert ist jetzt in x0
  int nanosleep(const struct timespec *req, struct timespec *rem);
    cmp    x0, #0
Dieser Aufruf erwartet jeweils eine Struktur "timespec", die in x0 und x1 übergeben wird.
    b.ge   open_success        // x0 >= 0 → Erfolg
Diese Struktur ist wie folgt definiert:
struct timespec {
  time_t tv_sec; /* seconds */
   long tv_nsec; /* nanoseconds */
};
Nach Assembler:
timespec:
  timespec_tv_sec:  .dword 0
  timespec_tv_nsec: .dword 100000000
Um es der Funktion zu übergeben:
ldr x0,=timespec
ldr x1,=timespec


== Arbeiten mit Dateien ==
    // Fehlerbehandlung
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h
    neg    x1, x0              // Fehlercode positiv machen
Datei öffnen:
    // Jetzt enthält x1 z.B. 2 → ENOENT (Datei nicht gefunden)
mov x0,#-100
 
ldr x1,=Dateiname
open_success:
mov x2,#flags //O_RDONLY, O_WRONLY, =O_CREAT
    // normal weiter...
mov x3,#666
</syntaxhighlight>
mov x8,#323 //#define __NR_openat 323
 
svc 0
[[Übersicht der Fehlercodes]]
Rückgabe in x0 ->
 
adds x10,XZR,x0 //file descriptor nach x10
== Strukturen bei Systemcalls ==
bpl ok //Wenn positiv, dann hat es geklappt
einige Systemcalls in Linux (auch auf ARM64) erwarten Strukturen als Argumente – also zusammengesetzte Daten, die im Speicher bereitgestellt werden müssen und dann per Zeiger übergeben werden.
Fehler...


Datei schließen:
=== Strukturen an Systemcalls übergeben (ARM64/Linux) ===
mov x0,x10  //Beispiel x10 für file descriptor
mov x8,#118 //#define __NR_fsync 118
svc 0
mov x0,x10  //Beispiel x10 für file descriptor
mov x8,#6  //#define __NR_close   6
svc 0


Lesen aus einer Datei:
Einige Systemcalls (z. B. stat, gettimeofday, timespec, uname, poll, etc.) brauchen als Argument einen Zeiger auf eine Struktur, die im Speicher angelegt ist.
mov x0,x10    //Beispiel x10 für file descriptor
ldr x1,=buffer //Hierhin sollen die Daten geladen werden
mov x2,#256    //Max Anzahl an Bytes, die geladen werden sollen (Beispiel: 256Bytes)
mov x8,#3      //#define __NR_read   3
svc 0


Speichern in eine Datei:
Auf folgendes ist zu achten:
mov x0,x10    //Beispiel x10 für file descriptor
* Speicher für die Struktur bereitstellen (z. B. über .skip oder .space)
ldr x1,=buffer //Von hier sollen die Daten gespeichert werden
* Die Struktur ggf. initialisieren (z. B. Null setzen oder Werte eintragen)
mov x2,#256    //Anzahl an Bytes, die gespeichert werden sollen (Beispiel: 256Bytes)
* Den Zeiger zur Struktur ins richtige Register laden (z. B. x1)
mov x8,#4      //#define __NR_write   4
svc 0


Ein Beispiel in ARM64-Assembler für Linux, das zeigt, wie Sie eine Datei öffnen, die Daten lesen, bearbeiten und anschließend die bearbeiteten Daten in einer anderen Datei speichern können. Ich werde dafür Systemaufrufe (syscalls) verwenden.
=== Beispiel 1: uname-Systemcall ===
Dieser Systemcall füllt eine struct utsname mit Informationen über das System (wie uname -a in der Shell).


Überblick der Schritte:
<syntaxhighlight lang="C">
Datei öffnen (open).
// C-Definition:
Daten aus der Datei lesen (read).
struct utsname {
Daten bearbeiten (In diesem Beispiel ändern wir die Daten nicht, sie könnten aber leicht modifiziert werden).
    char sysname[65];
Neue Datei erstellen und öffnen (open).
    char nodename[65];
Daten in die neue Datei schreiben (write).
    char release[65];
Beide Dateien schließen (close).
     char version[65];
Vollständiges Beispiel in ARM64-Assembler:
     char machine[65];
.section .data
     char domainname[65]; // oft ignoriert
     filename:  .asciz "input.txt"
};
     newfilename:.asciz "output.txt"
</syntaxhighlight>
     buffer:    .space 1024  // Pufferspeicher zum Lesen der Datei
Gesamtgröße: mindestens 390 Bytes


Assembler-Beispiel: uname aufrufen und sysname (z. B. „Linux“) ausgeben
<syntaxhighlight lang="asm">
.section .bss
.section .bss
     .lcomm result, 4    // Speicher für den Rückgabewert von syscalls
utsbuf:     .skip 390            // Speicher für struct utsname
outbuf:    .skip 65              // Puffer zum Ausgeben von sysname


.section .text
.section .text
.global _start
.global _start
_start:
_start:
     // Datei öffnen (input.txt)
     // Systemcall: uname(struct utsname *buf)
     mov     x0, 0        // stdin als File Descriptor (0)
     // syscall number: 160
     ldr     x1, =filename // filename in x1 laden
 
     mov     x2, 0         // O_RDONLY (0)
    ldr     x0, =utsbuf          // Zeiger auf struct utsname
     mov    x8, 2        // Syscall-Nummer für 'open' ist 2
     mov     x8, #160              // syscall number
     svc     0            // Systemaufruf
    svc    #0
 
    // Rückgabewert in x0: 0 bei Erfolg, sonst -errno
     cmp     x0, #0
    b.ne    uname_fail
 
    // Jetzt sysname (erstes Feld der Struktur) ausgeben:
     // Es ist eine nullterminierte Zeichenkette
 
     ldr     x1, =utsbuf          // sysname ist am Anfang
    bl      print_string


     mov    x19, x0       // x0 enthält den File Descriptor
     b       done
    ldr    x1, =buffer  // Speicheradresse des Puffers in x1 laden
    mov    x2, 1024      // Maximale Anzahl von Bytes zu lesen
    mov    x8, 63        // Syscall-Nummer für 'read' ist 63
    svc    0            // Systemaufruf


     // Ergebnis der gelesenen Bytes in x0 speichern
uname_fail:
     mov     x20, x0
     // Fehlerausgabe (optional)
     ldr     x1, =failmsg
    bl      print_string


    // Datei zum Schreiben öffnen (output.txt)
done:
     ldr    x1, =newfilename // filename in x1 laden
     // exit(0)
    mov    x2, 241          // O_WRONLY | O_CREAT | O_TRUNC (241)
     mov    x0, #0
     mov    x3, 0644        // Modus 0644 (rw-r--r--)
     mov    x8, #93
     mov    x8, 2            // Syscall-Nummer für 'open' (2)
     svc    #0
     svc    0               // Systemaufruf


     mov    x21, x0          // File Descriptor für die Ausgabedatei
// Ausgabe-Funktion (x1 = Zeiger auf 0-terminierten String)
print_string:
     mov    x2, #0
count_loop:
    ldrb    w3, [x1, x2]
    cbz    w3, print_now
    add    x2, x2, #1
    b      count_loop
print_now:
    mov    x0, #1         // stdout
    mov    x8, #64        // write
    svc    #0
    ret


    // Daten in die neue Datei schreiben
.section .data
    ldr    x1, =buffer      // Speicheradresse des Puffers in x1 laden
failmsg: .asciz "uname failed\n"
    mov    x2, x20          // Anzahl der gelesenen Bytes
</syntaxhighlight>
    mov    x0, x21          // File Descriptor der Ausgabedatei
    mov    x8, 64          // Syscall-Nummer für 'write' (64)
    svc    0                // Systemaufruf


     // Eingabedatei schließen
Kompilieren & Ausführen
     mov    x0, x19          // File Descriptor der Eingabedatei
<syntaxhighlight lang="shell">
    mov    x8, 57          // Syscall-Nummer für 'close' (57)
as -o uname.o uname.s
     svc     0               // Systemaufruf
ld -o uname uname.o
./uname
</syntaxhighlight>
Erwartete Ausgabe:
<syntaxhighlight lang="shell">
Linux
</syntaxhighlight>
=== Weitere Beispiele für strukturbasierte Syscalls ===
{| class="wikitable"
|-
! Systemcall !! Struktur !! Zweck
|-
| uname || struct utsname || Systeminfo
|-
| statx || struct statx || Dateiinformation
|-
| gettimeofday || struct timeval || aktuelle Uhrzeit
|-
| nanosleep || struct timespec || schlafen für Zeitspanne
|-
| poll || struct pollfd || Warten auf Dateideskriptor
|}
 
=== Beispiel 2: nanosleep mit struct timespec ===
<syntaxhighlight lang="C">
// struct timespec {
     long tv_sec;        // ganze Sekunden
     long tv_nsec;      // Nanosekunden
// };
</syntaxhighlight>
Schlafen für 2 Sekunden:
<syntaxhighlight lang="asm">
.section .data
timespec:
     .quad 2            // tv_sec = 2
     .quad 0             // tv_nsec = 0
 
.section .text
.global _start


     // Ausgabedatei schließen
_start:
     mov    x0, x21          // File Descriptor der Ausgabedatei
    ldr    x0, =timespec     // Zeiger auf timespec
     mov    x8, 57          // Syscall-Nummer für 'close' (57)
     mov    x1, #0            // NULL für "remaining"
     svc    0               // Systemaufruf
     mov    x8, #101          // syscall: nanosleep
     svc    #0


     // Beende das Programm
     // done
     mov    x8, 93          // Syscall-Nummer für 'exit' (93)
     mov    x0, #0
     mov    x0, 0            // Rückgabewert 0
     mov    x8, #93
     svc    0               // Systemaufruf
     svc    #0
Erklärung:
</syntaxhighlight>
Datenabschnitt:
.section .data:


filename: Speichert den Namen der Eingabedatei input.txt.
== Arbeiten mit Dateien ==
newfilename: Speichert den Namen der Ausgabedatei output.txt.
Siehe hier auch das Incude an: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h
buffer: Ein Speicherbereich zum Lesen und Schreiben der Dateiinhalte.
=== Datei öffnen (openat) ===
.section .bss:
<syntaxhighlight lang="asm">
mov    x0, #-100        // AT_FDCWD (aktuelles Verzeichnis)
ldr    x1, =Dateiname    // char *pathname
mov    x2, #0            // flags z. B. O_RDONLY (0), O_WRONLY, O_CREAT usw.
mov    x3, #0            // Modus (z. B. 0666) – nur relevant bei O_CREAT
mov    x8, #56          // openat
svc    #0


Speicher zur Speicherung des Rückgabewerts von read.
// Rückgabewert (File Descriptor) in x0
Textabschnitt:
adds    x10, x0, #0      // File Descriptor in x10 speichern
Datei öffnen (input.txt):
bpl    ok                // Wenn >= 0, dann erfolgreich
// Fehlerbehandlung
</syntaxhighlight>
=== Datei schließen (close) ===
<syntaxhighlight lang="asm">
mov    x0, x10          // File Descriptor
mov    x8, #57          // __NR_close
svc    #0
</syntaxhighlight>
=== Lesen aus einer Datei (read) ===
<syntaxhighlight lang="asm">
mov    x0, x10          // File Descriptor
ldr    x1, =buffer      // Zielpuffer
mov    x2, #256          // Anzahl der Bytes
mov    x8, #63          // __NR_read
svc    #0
</syntaxhighlight>
=== In Datei schreiben (write) ===
<syntaxhighlight lang="asm">
mov    x0, x10          // File Descriptor
ldr    x1, =buffer      // Quelle
mov    x2, #256          // Anzahl der Bytes
mov    x8, #64          // __NR_write
svc    #0
</syntaxhighlight>
=== Beispiel ===
Ein Beispiel das zeigt, wie Sie eine Datei öffnen, die Daten lesen, bearbeiten und anschließend die bearbeiteten Daten in einer anderen Datei speichern können. Ich werde dafür Systemaufrufe (syscalls) verwenden.


mov x0, 0: Bereitet den Systemaufruf vor, um stdin als Dateideskriptor zu verwenden.
* Überblick der Schritte:
ldr x1, =filename: Lädt die Adresse des Dateinamens in x1.
# Datei öffnen (open).
mov x2, 0: Setzt den Modus auf O_RDONLY (nur lesen).
# Daten aus der Datei lesen (read).
mov x8, 2: Setzt die Systemaufrufnummer für open (2).
# Daten bearbeiten (In diesem Beispiel ändern wir die Daten nicht, sie könnten aber leicht modifiziert werden).
svc 0: Führt den Systemaufruf aus. Der Dateideskriptor wird in x0 zurückgegeben und in x19 gespeichert.
# Neue Datei erstellen und öffnen (open).
Daten lesen:
# Daten in die neue Datei schreiben (write).
# Beide Dateien schließen (close).


ldr x1, =buffer: Lädt die Adresse des Puffers in x1.
* Vollständiges Beispiel in ARM64-Assembler:
mov x2, 1024: Gibt an, dass bis zu 1024 Bytes gelesen werden.
<syntaxhighlight lang="asm">
mov x8, 63: Setzt die Systemaufrufnummer für read (63).
.section .data
svc 0: Führt den Systemaufruf aus. Die Anzahl der gelesenen Bytes wird in x0 zurückgegeben und in x20 gespeichert.
filename:     .asciz "input.txt"
Datei öffnen (output.txt):
newfilename: .asciz "output.txt"
buffer:       .space 1024


ldr x1, =newfilename: Lädt die Adresse des neuen Dateinamens in x1.
.section .text
mov x2, 241: Setzt den Modus auf O_WRONLY | O_CREAT | O_TRUNC (schreiben, Datei erstellen, Datei kürzen).
.global _start
mov x3, 0644: Setzt die Dateiberechtigungen auf rw-r--r--.
_start:
mov x8, 2: Setzt die Systemaufrufnummer für open (2).
    // input.txt öffnen
svc 0: Führt den Systemaufruf aus. Der Dateideskriptor wird in x0 zurückgegeben und in x21 gespeichert.
    mov    x0, #-100          // AT_FDCWD
Daten schreiben:
    ldr     x1, =filename
    mov     x2, #0            // O_RDONLY
    mov    x3, #0            // mode (nicht benötigt)
    mov    x8, #56            // openat
    svc    #0
    mov    x19, x0            // FD in x19
 
    // Lesen
    mov    x0, x19
    ldr    x1, =buffer
    mov     x2, #1024
    mov     x8, #63            // read
    svc     #0
    mov    x20, x0           // Gelesene Bytes


ldr x1, =buffer: Lädt die Adresse des Puffers in x1.
    // output.txt öffnen (schreibend, erstellen, ggf. überschreiben)
mov x2, x20: Setzt die Anzahl der zu schreibenden Bytes auf die Anzahl der gelesenen Bytes.
    mov    x0, #-100
mov x0, x21: Setzt den Dateideskriptor auf die Ausgabedatei.
    ldr     x1, =newfilename
mov x8, 64: Setzt die Systemaufrufnummer für write (64).
    mov     x2, #577          // O_WRONLY | O_CREAT | O_TRUNC (0x241)
svc 0: Führt den Systemaufruf aus.
    mov     x3, #0o644        // rw-r--r--
Dateien schließen:
    mov     x8, #56            // openat
    svc     #0
    mov    x21, x0            // FD für Ausgabe


mov x0, x19: Setzt den Dateideskriptor auf die Eingabedatei.
    // Schreiben
mov x8, 57: Setzt die Systemaufrufnummer für close (57).
    mov     x0, x21
svc 0: Führt den Systemaufruf aus.
    ldr    x1, =buffer
mov x0, x21: Setzt den Dateideskriptor auf die Ausgabedatei.
    mov     x2, x20
mov x8, 57: Setzt die Systemaufrufnummer für close (57).
    mov     x8, #64            // write
svc 0: Führt den Systemaufruf aus.
    svc     #0
Programm beenden:


mov x8, 93: Setzt die Systemaufrufnummer für exit (93).
    // Eingabedatei schließen
mov x0, 0: Setzt den Rückgabewert auf 0.
    mov     x0, x19
svc 0: Führt den Systemaufruf aus und beendet das Programm.
    mov     x8, #57            // close
Dieses Beispiel demonstriert die wesentlichen Systemaufrufe und die Datenbearbeitung in einem ARM64-Assemblerprogramm unter Linux.
    svc     #0


    // Ausgabedatei schließen
    mov    x0, x21
    mov    x8, #57
    svc    #0


== Linux Systemaufrufe ==
    // Programm beenden
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/unistd.h
    mov    x0, #0
https://github.com/ilbers/linux/blob/master/arch/sh/include/uapi/asm/unistd_64.h
    mov    x8, #93            // exit
    svc    #0


x0–x7: Parameterübergabe
</syntaxhighlight>
x8: SystemCallNummer
==== Erklärung wichtiger Flags ====
svc 0 -> Aufruf des Systemcalls
Die Flags findest du unter:
x0: Ergebniss


Wenn x0 negativ -> negieren -> dann Fehler:
[https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h fcntl.h] auf GitHub
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h
https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h


In der Parameterübergabe erwarten viele SystemCalls eine Struktur. Ein einfacher SystemCall wie nanosleep wird als C-Code wie folgt aufgerufen:
Beispiele:
  int nanosleep(const struct timespec *req, struct timespec *rem);
<syntaxhighlight lang="C">
Dieser Aufruf erwartet jeweils eine Struktur "timespec", die in x0 und x1 übergeben wird.
#define O_RDONLY        00000000
Diese Struktur ist wie folgt definiert:
#define O_WRONLY        00000001
struct timespec {
#define O_RDWR          00000002
  time_t tv_sec; /* seconds */
#define O_CREAT        00000100
  long tv_nsec; /* nanoseconds */
#define O_TRUNC        00001000
};
</syntaxhighlight>
Nach Assembler:
Kombination z. B.:
timespec:
<syntaxhighlight lang="C">
  timespec_tv_sec:  .dword 0
O_WRONLY | O_CREAT | O_TRUNC = 0x1 | 0x100 | 0x1000 = 0x1101 = 577 dezimal
  timespec_tv_nsec: .dword 100000000
</syntaxhighlight>
Um es der Funktion zu übergeben:
ldr x0,=timespec
ldr x1,=timespec

Aktuelle Version vom 10. April 2025, 07:30 Uhr

In diesem Kapitel werden wir uns mit Systemaufrufen unter Linux beschäftigen. Insbesondere werden wir:

Eine Einführung in Linux-Systemaufrufe geben, Grundlegende Systemaufrufe wie exit, read, write, open und close kennenlernen und Erklären, wie Parameter übergeben und Rückgabewerte entgegengenommen werden.

Einführung in Linux-Systemaufrufe

Systemaufrufe sind Schnittstellen, die es Programmen ermöglichen, Dienste und Funktionen des Betriebssystems in Anspruch zu nehmen. Diese können niedrigere Eingabe-/Ausgabe-Funktionen, Prozesssteuerung, Speicherverwaltung und mehr umfassen. In ARM64-Assembler verwenden wir den svc-Befehl, um Systemaufrufe auszuführen. Dabei tragen wir die Nummer des spezifischen Systemaufrufs in Register x8 ein und die Parameter in Registers x0 bis x5.

Grundlegende Systemaufrufe

exit

Dieser Systemaufruf beendet das Programm.

Syntax:

mov x8, #93   // syscall number for exit
mov x0, #0    // exit code (0 for success)
svc 0

read

Dieser Systemaufruf liest Daten von einer Datei.

Syntax:

mov x8, #63           // syscall number for read
mov x0, #file_desc    // file descriptor (0 for stdin)
mov x1, #buffer       // buffer to store the read data
mov x2, #size         // number of bytes to read
svc 0

write

Dieser Systemaufruf schreibt Daten in eine Datei.

Syntax:

mov x8, #64           // syscall number for write
mov x0, #file_desc    // file descriptor (1 for stdout)
mov x1, #buffer       // buffer with data to write
mov x2, #size         // number of bytes to write
svc 0

open

Dieser Systemaufruf öffnet eine Datei und gibt einen Datei-Deskriptor zurück.

Syntax:

mov x8, #56           // syscall number for openat
mov x0, #-100         // AT_FDCWD (current working directory)
mov x1, #filename     // pointer to the filename string
mov x2, #flags        // flags (e.g., O_RDONLY for read only)
svc 0

close

Dieser Systemaufruf schließt eine Datei.

Syntax:

mov x8, #57           // syscall number for close
mov x0, #file_desc    // file descriptor
svc 0

Übergabe von Parametern und Entgegennahme von Rückgabewerten

Die Übergabe von Parametern erfolgt über bestimmte Register:

  • x0 bis x5 für die ersten sechs Argumente
  • x8 für die Systemaufrufnummer

Rückgabewerte werden in x0 zurückgegeben.

Beispiel:

.global _start

.section .data
hello_msg: .asciz "Hello, World!\n"

_start:
    // write system call
    mov x0, #1         // file descriptor (1 for stdout)
    ldr x1, =hello_msg // buffer
    mov x2, #13        // size
    mov x8, #64        // syscall number for write
    svc 0

    // exit system call
    mov x0, #0         // exit code
    mov x8, #93        // syscall number for exit
    svc 0

In diesem Beispiel wird die Zeichenkette "Hello, World!\n" auf die Standardausgabe geschrieben und danach das Programm mit dem Rückgabewert 0 beendet.

Verweis auf alle verfügbaren Linux-Systemaufrufe

Eine vollständige Liste der verfügbaren Linux-Systemaufrufe und deren Nummern finden Sie in den entsprechenden Header-Dateien des Systems, wie z.B. in /usr/include/asm/unistd.h oder online in den offiziellen Linux-Kernel-Dokumentationen.

Eine häufig verwendete Referenz für Systemaufrufe ist die Man-Seite (man 2 syscall). Eine umfassendere Liste spezifisch für den ARM64-Architektur habe ich hier erstellt: Übersicht der Linux ARM64 Systemaufrufen

Fehlercodes

Wenn ein Systemcall ausgeführt wird (z. B. write, open, read), prüft der Kernel, ob die Anfrage gültig ist, und führt sie aus – oder gibt einen Fehlercode zurück, wenn etwas schiefgeht.

Viele Systemcalls geben in x0 ein Ergebnis wie zum Beispiel die Anzahl von Bytes, ein Dateideskriptor, PID oder anderes zurück, wenn alles geklappt hat. Sollte allerdings ein negatives Ergebnis (errno) zurück kommen, so hat in der Regel etwas nicht funktioniert.

Beispiel:

    // Datei öffnen (openat)
    mov     x0,  AT_FDCWD        // aktuelles Verzeichnis
    ldr     x1,  =filename       // Dateiname
    mov     x2,  #0              // O_RDONLY
    mov     x8,  #56             // syscall number für openat
    svc     #0

    // Rückgabewert ist jetzt in x0
    cmp     x0, #0
    b.ge    open_success         // x0 >= 0 → Erfolg

    // Fehlerbehandlung
    neg     x1, x0               // Fehlercode positiv machen
    // Jetzt enthält x1 z.B. 2 → ENOENT (Datei nicht gefunden)

open_success:
    // normal weiter...

Übersicht der Fehlercodes

Strukturen bei Systemcalls

einige Systemcalls in Linux (auch auf ARM64) erwarten Strukturen als Argumente – also zusammengesetzte Daten, die im Speicher bereitgestellt werden müssen und dann per Zeiger übergeben werden.

Strukturen an Systemcalls übergeben (ARM64/Linux)

Einige Systemcalls (z. B. stat, gettimeofday, timespec, uname, poll, etc.) brauchen als Argument einen Zeiger auf eine Struktur, die im Speicher angelegt ist.

Auf folgendes ist zu achten:

  • Speicher für die Struktur bereitstellen (z. B. über .skip oder .space)
  • Die Struktur ggf. initialisieren (z. B. Null setzen oder Werte eintragen)
  • Den Zeiger zur Struktur ins richtige Register laden (z. B. x1)

Beispiel 1: uname-Systemcall

Dieser Systemcall füllt eine struct utsname mit Informationen über das System (wie uname -a in der Shell).

// C-Definition:
struct utsname {
    char sysname[65];
    char nodename[65];
    char release[65];
    char version[65];
    char machine[65];
    char domainname[65]; // oft ignoriert
};

Gesamtgröße: mindestens 390 Bytes

Assembler-Beispiel: uname aufrufen und sysname (z. B. „Linux“) ausgeben

.section .bss
utsbuf:     .skip 390             // Speicher für struct utsname
outbuf:     .skip 65              // Puffer zum Ausgeben von sysname

.section .text
.global _start

_start:
    // Systemcall: uname(struct utsname *buf)
    // syscall number: 160

    ldr     x0, =utsbuf           // Zeiger auf struct utsname
    mov     x8, #160              // syscall number
    svc     #0

    // Rückgabewert in x0: 0 bei Erfolg, sonst -errno
    cmp     x0, #0
    b.ne    uname_fail

    // Jetzt sysname (erstes Feld der Struktur) ausgeben:
    // Es ist eine nullterminierte Zeichenkette

    ldr     x1, =utsbuf           // sysname ist am Anfang
    bl      print_string

    b       done

uname_fail:
    // Fehlerausgabe (optional)
    ldr     x1, =failmsg
    bl      print_string

done:
    // exit(0)
    mov     x0, #0
    mov     x8, #93
    svc     #0

// Ausgabe-Funktion (x1 = Zeiger auf 0-terminierten String)
print_string:
    mov     x2, #0
count_loop:
    ldrb    w3, [x1, x2]
    cbz     w3, print_now
    add     x2, x2, #1
    b       count_loop
print_now:
    mov     x0, #1          // stdout
    mov     x8, #64         // write
    svc     #0
    ret

.section .data
failmsg: .asciz "uname failed\n"

Kompilieren & Ausführen

as -o uname.o uname.s
ld -o uname uname.o
./uname

Erwartete Ausgabe:

Linux

Weitere Beispiele für strukturbasierte Syscalls

Systemcall Struktur Zweck
uname struct utsname Systeminfo
statx struct statx Dateiinformation
gettimeofday struct timeval aktuelle Uhrzeit
nanosleep struct timespec schlafen für Zeitspanne
poll struct pollfd Warten auf Dateideskriptor

Beispiel 2: nanosleep mit struct timespec

// struct timespec {
    long tv_sec;        // ganze Sekunden
    long tv_nsec;       // Nanosekunden
// };

Schlafen für 2 Sekunden:

.section .data
timespec:
    .quad 2             // tv_sec = 2
    .quad 0             // tv_nsec = 0

.section .text
.global _start

_start:
    ldr     x0, =timespec     // Zeiger auf timespec
    mov     x1, #0            // NULL für "remaining"
    mov     x8, #101          // syscall: nanosleep
    svc     #0

    // done
    mov     x0, #0
    mov     x8, #93
    svc     #0

Arbeiten mit Dateien

Siehe hier auch das Incude an: https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/fcntl.h

Datei öffnen (openat)

mov     x0, #-100         // AT_FDCWD (aktuelles Verzeichnis)
ldr     x1, =Dateiname    // char *pathname
mov     x2, #0            // flags z. B. O_RDONLY (0), O_WRONLY, O_CREAT usw.
mov     x3, #0            // Modus (z. B. 0666) – nur relevant bei O_CREAT
mov     x8, #56           // openat
svc     #0

// Rückgabewert (File Descriptor) in x0
adds    x10, x0, #0       // File Descriptor in x10 speichern
bpl     ok                // Wenn >= 0, dann erfolgreich
// Fehlerbehandlung

Datei schließen (close)

mov     x0, x10           // File Descriptor
mov     x8, #57           // __NR_close
svc     #0

Lesen aus einer Datei (read)

mov     x0, x10           // File Descriptor
ldr     x1, =buffer       // Zielpuffer
mov     x2, #256          // Anzahl der Bytes
mov     x8, #63           // __NR_read
svc     #0

In Datei schreiben (write)

mov     x0, x10           // File Descriptor
ldr     x1, =buffer       // Quelle
mov     x2, #256          // Anzahl der Bytes
mov     x8, #64           // __NR_write
svc     #0

Beispiel

Ein Beispiel das zeigt, wie Sie eine Datei öffnen, die Daten lesen, bearbeiten und anschließend die bearbeiteten Daten in einer anderen Datei speichern können. Ich werde dafür Systemaufrufe (syscalls) verwenden.

  • Überblick der Schritte:
  1. Datei öffnen (open).
  2. Daten aus der Datei lesen (read).
  3. Daten bearbeiten (In diesem Beispiel ändern wir die Daten nicht, sie könnten aber leicht modifiziert werden).
  4. Neue Datei erstellen und öffnen (open).
  5. Daten in die neue Datei schreiben (write).
  6. Beide Dateien schließen (close).
  • Vollständiges Beispiel in ARM64-Assembler:
.section .data
filename:     .asciz "input.txt"
newfilename:  .asciz "output.txt"
buffer:       .space 1024

.section .text
.global _start
_start:
    // input.txt öffnen
    mov     x0, #-100          // AT_FDCWD
    ldr     x1, =filename
    mov     x2, #0             // O_RDONLY
    mov     x3, #0             // mode (nicht benötigt)
    mov     x8, #56            // openat
    svc     #0
    mov     x19, x0            // FD in x19

    // Lesen
    mov     x0, x19
    ldr     x1, =buffer
    mov     x2, #1024
    mov     x8, #63            // read
    svc     #0
    mov     x20, x0            // Gelesene Bytes

    // output.txt öffnen (schreibend, erstellen, ggf. überschreiben)
    mov     x0, #-100
    ldr     x1, =newfilename
    mov     x2, #577           // O_WRONLY | O_CREAT | O_TRUNC (0x241)
    mov     x3, #0o644         // rw-r--r--
    mov     x8, #56            // openat
    svc     #0
    mov     x21, x0            // FD für Ausgabe

    // Schreiben
    mov     x0, x21
    ldr     x1, =buffer
    mov     x2, x20
    mov     x8, #64            // write
    svc     #0

    // Eingabedatei schließen
    mov     x0, x19
    mov     x8, #57            // close
    svc     #0

    // Ausgabedatei schließen
    mov     x0, x21
    mov     x8, #57
    svc     #0

    // Programm beenden
    mov     x0, #0
    mov     x8, #93            // exit
    svc     #0

Erklärung wichtiger Flags

Die Flags findest du unter:

fcntl.h auf GitHub

Beispiele:

#define O_RDONLY        00000000
#define O_WRONLY        00000001
#define O_RDWR          00000002
#define O_CREAT         00000100
#define O_TRUNC         00001000

Kombination z. B.:

O_WRONLY | O_CREAT | O_TRUNC = 0x1 | 0x100 | 0x1000 = 0x1101 = 577 dezimal