View
2
Download
0
Category
Preview:
Citation preview
11
Tafelübung zu BS
5. Dateioperationen
Olaf SpinczykArbeitsgruppe Eingebettete Systemsoftware
Lehrstuhl für Informatik 12TU Dortmund olaf.spinczyk@tu-dortmund.dehttp://ess.cs.uni-dortmund.de/~os/
http://ess.cs.tu-dortmund.de/DE/Teaching/SS2011/BS/
BS: U5 – Dateioperationen 22
Agenda● Besprechung Aufgabe 4: Speicherverwaltung
● Systemcalls unter Linux: Wie funktioniert das eigentlich?
● Aufgabe 5: Dateioperationen
● Öffnen/Lesen/Schreiben von Dateien/Verzeichnissen
- Syscall-Wrapper (open/close/write/read)- libc-Abstraktion
- Dateien (fopen/fclose/fwrite/fread)- Verzeichnisse (opendir/readdir/closedir)
● Big- und Little-Endian● One-Time-Pads
BS: U5 – Dateioperationen 33
Besprechung Aufgabe 4● → Foliensatz Besprechung
BS: U5 – Dateioperationen 44
Systemaufrufe durch Programme● Systemaufruf = Aufruf von Funktionen, die das
Betriebssystem zur Verfügung stellt● Zugriffe auf angeschlossene Hardware● Funktionen zur Speicherverwaltung● Funktionen zur Prozessverwaltung
● Syscalls: Funktionen, die nur in einem privilegierten Modus ausgeführt werden können, d.h. mit erweiterten Rechten
● Linux: Aufteilung in User- und Kernelspace● Problem: Ein einfacher Funktionsaufruf in die
Kernelfunktionen ist nicht sinnvoll● Ein fehlerhaftes Anwendungsprogramm kann das System zum
Absturz bringen● Jedes Anwendungsprogramm hat volle Zugriffsrechte → z.B.
Scheduling und Rechteverwaltung unmöglich● Kernel liegt in geschütztem Speicher
BS: U5 – Dateioperationen 55
Linux-Systemcalls (hier für x86)● Einzige Möglichkeit für Userspace-Programme auf
Kernelspace-Funktionen zuzugreifen● Jedem Systemcall ist eine eindeutige Nummer zugeordnet
● Direkter Aufruf von Systemcalls z.B. per syscall(2)
arch/x86/kernel/syscall_table_32.SENTRY(sys_call_table) .long sys_restart_syscall /* 0 */ .long sys_exit /* 1 */ .long sys_fork /* 2 */ .long sys_read /* 3 */ .long sys_write /* 4 */ .long sys_open /* 5 */...
#define _GNU_SOURCE#include <unistd.h> #include <sys/syscall.h> /* hier wird SYS_read=3 definiert */#include <sys/types.h>
int main(int argc, char *argv[]) { ... syscall(SYS_read, fd, &buffer, nbytes); /* read(fd, &buffer, nbytes) */ return 0;}
BS: U5 – Dateioperationen 66
Systemstruktur
Gerätetreiber
Kernel Subsysteme
Applikation
read() Applikation Applikation
Hardware
Kernelspace
Userspace
libc
...
Systemcall Schnittstelle
BS: U5 – Dateioperationen 77
Ablauf eines Systemcalls1) Argumente → Stack
(Konvention: Letztes zuerst)
2) Aufruf der Bibliotheksfunktion
(Implizit: push Rücksprungaddresse)
3) Argumente in Register laden
(Stack für User und Kernel versch.)
4) Interrupt auslösen
5) Interruptnummer Index in Tabelle,
hält Addressen der Zielfunktionen
6) Zielfunktion wählt mit eax Funktion
aus (Array aus Funktionspointern)
7) Kernel: sys_read()
8) Mode-Wechsel (alter Userstack)
9) Ausführung fährt fort
10) Rücksprungaddr. noch auf Stack
11) Stack aufräumen
BS: U5 – Dateioperationen 88
Beispiel: _exit(255) „per Hand“
myexit.c
int main(void) { asm("mov $0x01, %eax\n" /* syscall # in eax */ "mov $0xff, %ebx\n" /* Parameter 255 in ebx */ "int $0x80\n"); /* Softwareinterrupt an Kernel */ return 0;}
● Parameter von Systemcalls:● < 6 Parameter: Parameter werden in den Registern ebx, ecx, edx, esi, edi abgelegt
● >= 6 Parameter: ebx enthält Pointer auf Userspace mit Parametern
● Aufruf des sys_exit Systemcalls per Assembler● void _exit(int status) (beende den aktuellen Prozess mit
Statuscode status)
● sys_exit Systemcall hat die Nr. 0x01
pohl@host:~$ ./myexitpohl@host:~$ echo $?255pohl@host:~$
BS: U5 – Dateioperationen 99
Arbeiten mit Dateien● Alles unter Linux/Unix wird auf Dateien abgebildet
→ viele verschiedene Dateitypen (Geräte, Sockets, ...)● Typische Dateioperationen sind
● Öffnen/Lesen/Schreiben/Schließen
● Unter Linux mehrere Möglichkeiten● Syscall-Wrapper der C-Bibliothek („low-level“), z.B. open(2)
- keine Abstraktion, sondern direkte Umsetzung in Systemcall
● Abstrakte Stream Schnittstelle der C-Bibliothek („high-level“), z.B. fopen(3)
int open(const char *path, int flags)
syscall(__NR_open, path, flags)
FILE* fopen(const char *path, const char *mode)
Abstraktion von Filedeskriptor → Stream (FILE*)
open(path, flags)
BS: U5 – Dateioperationen 1010
„Low-Level“ Dateioperationen
int open(const char *path, int flags)
ssize_t read(int fd, void *buf, size_t count)
ssize_t write(int fd, void *buf, size_t count)
int close(int fd)
● Öffnen von Dateien mit open(2)
● gibt einen Dateideskriptor zurück, der für die anderen Funktionen verwendet wird
● Lesen aus Dateien mit read(2)
● Schreiben in Dateien mit write(2)
● Schließen von offenen Dateien mit close(2)
● Standardisiert u.a. in POSIX.1 2001
BS: U5 – Dateioperationen 1111
Beispiel „low-level“#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#define SIZE 64
int main(void) { int r_fd = 0, w_fd = 0; ssize_t r_bytes = 0, w_bytes = 0; char buf[SIZE] = {0}; /* Datei zum Lesen öffnen */ r_fd = open(“/tmp/inputfile“, O_RDONLY); if (r_fd == -1) { /* ... Fehler ... */ }; r_bytes = read(r_fd, buf, SIZE); /* SIZE Bytes aus der Datei lesen */
/* Ausgabedatei öffnen und leeren; nicht vorhanden? -> erzeugen! */ w_fd = open(“/tmp/outputfile“, O_CREAT|O_WRONLY|O_TRUNC); if (w_fd == -1) { /* ... Fehler ... */ }; w_bytes = write(w_fd, buf, r_bytes); /* gelesene Bytes schreiben */ close(r_fd); close(w_fd); return 0;}
BS: U5 – Dateioperationen 1212
C-Streams● Abstrakter, plattformunabhängiger Kommunikationskanal
zu● einem Gerät● einer einfachen Datei● einem Prozess, ...
● Interne Pufferung● Blockweises Lesen → höhere Verarbeitungsgeschwindigkeit
● Abstraktion vom eigentlichen Datenstrom● Lesen und Zurückschreiben von Daten (fgetc(3), ungetc(3))● Unabhängiges Lesen und Schreiben im Datenstrom (an
verschiedenen Positionen)
● Stream-Repräsentation durch struct FILE● enthält Zeiger auf Lese-/Schreibpuffer, aktuelle Positionen, Undo-
Informationen (in stdio.h definiert)
● definierte Standardstreams: stdin, stdout & stderr
BS: U5 – Dateioperationen 1313
„High-Level“ Dateioperationen
FILE *fopen(const char *path, const char *mode)
size_t fread(void *buf, size_t size, size_t count, FILE *stream)
size_t fwrite(void *buf, size_t size, size_t count, FILE *stream)
int fclose(FILE *stream)
● Öffnen von Dateien mit fopen(3)
● Modi: r/r+ (lesen/+schreiben), w/w+ (schreiben/+lesen), a/a+ (anhängen/+lesen) (siehe Manpage)
● Lesen aus Dateien mit fread(3)
● Schreiben in Dateien mit fwrite(3)
● Schließen von offenen Dateien mit fclose(3)
● Im C-Standard enthalten, plattformunabhängig!
BS: U5 – Dateioperationen 1414
Beispiel „High-Level“ (ein Block)#include <stdio.h>#define SIZE 64
int main(void) { FILE *r_file = NULL, *w_file = NULL; size_t r_bytes = 0, w_bytes = 0; char buf[SIZE] = {0}; /* Datei zum Lesen öffnen */ r_file = fopen("/tmp/inputfilef", "r"); if (r_file == NULL) { /* ... Fehler ... */ } r_bytes = fread(buf, sizeof(char), SIZE, r_file);
/* Ausgabedatei öffnen und leeren; nicht vorhanden? -> erzeugen! */ w_file = fopen("/tmp/outputfilef", "w"); if (w_file == NULL) { /* ... Fehler ... */ }
/* gelesene Bytes schreiben */ w_bytes = fwrite(buf, sizeof(char), r_bytes, w_file); fclose(r_file); fclose(w_file); return 0;}
BS: U5 – Dateioperationen 1515
Beispiel „High-Level“ (blockweise)#include <stdio.h>#define SIZE 64
int main(void) { FILE *r_file = NULL, *w_file = NULL; size_t r_bytes = 0, w_bytes = 0; char buf[SIZE] = {0};
/* ... */
while (!feof(r_file)) { /* läuft bis zum End-Of-File (EOF) */ r_bytes = fread(buf, sizeof(char), SIZE, r_file); if (ferror(r_file)) { /* ... Fehler ... */ }
/* z.B. buf[0] bis buf[r_bytes-1] byteweise bearbeiten */
fwrite(buf, sizeof(char), r_bytes, w_file); if (ferror(w_file)) { /* ... Fehler ... */ } }
/* ... */
return 0;}
BS: U5 – Dateioperationen 1616
Beispiel „High-Level“ (eine Variable)#include <stdio.h>#define SIZE 64
int main(void) { FILE *r_file = NULL, *w_file = NULL; size_t r_ints = 0, w_ints = 0; int val;
/* ... */
/* val als 4 Byte langen Puffer betrachten und 4 Bytes lesen */ r_ints = fread(&val, sizeof(int), 1, r_file); if (r_ints != 1) { /* ... EOF oder Fehler ... */ }
val *= 3; /* val „normal“ benutzen (dazu gleich mehr) */
/* den Inhalt von val in eine Datei schreiben */ w_ints = fwrite(&val, sizeof(int), 1, w_file); if (w_ints != 1) { /* ... Fehler ... */ }
/* ... */
return 0;}
BS: U5 – Dateioperationen 1717
Big- und Little-Endian● Gullivers Reisen (Jonathan Swift)
● Bewohner des Landes Lilliput teilen sich auf zwei verfeindete Gruppen auf …- die einen schlagen ihre Eier am großen Ende auf (Big Ender)
- die anderen schlagen ihre Eier am kleinen Ende auf (Little Ender)
● Big- und Little-Endian im Kontext der Informatikstudi@bs:~$ hexdump -C 000 00000000 78 56 34 12
studi@bs:~$ hexdump -C 000 00000000 12 34 56 78
So würde der int 0x12345678als Little-Endian aussehen ...
… und so als Big-Endian.
Ein Dateiformat – jedoch möglicherweise verschiedene Architekturen. Was tun?
Klar: Architektur feststellen und übersetzen, falls notwendig.(Keine Sorge, das ist in der Vorgabe bereits erledigt!)
BS: U5 – Dateioperationen 1818
Vollständige Fehlerbehandlung r_file = fopen("/tmp/inputfilef", "r"); if (r_file == NULL) { /* Fehlerbehandlung */ perror("fopen"); fclose(w_file); exit(EXIT_FAILURE); }
fread(buf, sizeof(char), SIZE, r_file); if (ferror(r_file)) { /* Fehlerbehandlung */ perror("fread"); fclose(r_file); fclose(w_file); exit(EXIT_FAILURE); }
fwrite(buf, sizeof(char), SIZE, w_file); if (ferror(w_file)) { /* Fehlerbehandlung */ perror("fwrite"); fclose(r_file); fclose(w_file); exit(EXIT_FAILURE); }
BS: U5 – Dateioperationen 1919
Vollständige Fehlerbehandlung#define cancel(failedcall) { \ perror(failedcall); \ if (r_file != NULL) fclose(r_file);\ if (w_file != NULL) fclose(w_file);\ exit(EXIT_FAILURE); \}
r_file = fopen("/tmp/inputfilef", "r"); if (r_file == NULL) cancel("fopen"); /* Makro-Text ersetzt den „Aufruf“ */
fread(buf, sizeof(char), SIZE, r_file); if (ferror(r_file)) cancel("fread");
fwrite(buf, sizeof(char), SIZE, w_file); if (ferror(w_file)) cancel("fwrite");
● Das parametrisierte Makro cancel() lässt sich wie eine lokale Funktion benutzen
BS: U5 – Dateioperationen 2020
Dateieigenschaften und stat(2)● Dateien unter Linux haben verschiedenste Eigenschaften
● Besitzer/Gruppe● Größe● Anzahl der belegten Blöcke● Anlegezeitpunkt, letzter Zugriffs- und Änderungszeitpunkt● Typ: Verzeichnis, Gerätedatei, (Symbolischer) Link, Named Pipe, ...
● Abfrage dieser Eigenschaften mit stat(2)
● Rückgabe der Werte in eine Struktur, Typ struct stat● Dateityp kann über Makros abgefragt werden, z.B.
● S_ISREG(st_mode) = reguläre Datei
● S_ISDIR(st_mode) = Verzeichnis
● Benötigte Header: <sys/types.h>, <sys/stat.h>, <unistd.h>
int stat(const char *path, struct stat *buf)
BS: U5 – Dateioperationen 2121
Beispiel stat(2)#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <stdio.h>
int main(void) { struct stat fileinfo = {0}; int retval = 0;
retval = stat("/etc/passwd", &fileinfo); if (retval == -1) { /* ... Fehler ... */ }; printf("normale Datei: %d\n", S_ISREG(fileinfo.st_mode)); printf("Verzeichnis : %d\n", S_ISDIR(fileinfo.st_mode)); printf("Größe : %d\n", (int) fileinfo.st_size); /* Extraktion der Rechteinformationen aus st_mode (siehe manpage) */ printf("Rechte : %o\n", fileinfo.st_mode & 0777);
return 0; }
BS: U5 – Dateioperationen 2222
● Öffnen von Verzeichnissen mit opendir(3)
● Auslesen der Verzeichnisinhalte mit readdir(3)
● Rückgabe = NULL → alle Verzeichniseinträge gelesen, keine weiteren vorhanden oder Fehler
● sonst: in dirent.d_name steht der Name des Verzeichniseintrages
● Schließen von geöffneten Verzeichnissen mit closedir(3)
● Benötigte Header: <sys/types.h>, <dirent.h>
„High-Level“ Verzeichnisoperationen
DIR *opendir(const char *path)
struct dirent *readdir(DIR *dir)
int closedir(DIR *dir)
BS: U5 – Dateioperationen 2323
Beispiel: Verzeichnis Auslesen#include <sys/types.h>#include <dirent.h>#include <stdio.h>
int main(void) { int retval = 0, count = 0; DIR *dir = NULL; struct dirent *entry = NULL;
dir = opendir("/tmp"); /* Verzeichnis öffnen, DIR-Zeiger erstellen */ if (!dir) { /* ... Fehler ... */ }
/* jeder Aufruf liefert einen Eintrag, NULL -> Listenende */ while ((entry = readdir(dir))) { printf("Eintrag %d: %s\n", ++count, entry->d_name); } printf("Anzahl: %d\n", count);
retval = closedir(dir); /* Verzeichnis wieder schließen */ if (retval == -1) { /* ... Fehler ... */ };
return 0; }
BS: U5 – Dateioperationen 2424
Programmieraufgabe One-Time-Pads● Umgang mit Dateioperationen soll geübt werden● Aufgabe: Verschlüsseln und Entschlüsseln von Dateien● Wieso One-Time-Pads (OTPs)?
● Eine sehr einfache Verschlüsselung (XOR auf alle Bytes)● Sehr wirksam und sicher, wenn man es richtig anwendet
● Nahezu vollständiges Programm ist vorgegeben, das mit entsprechenden Dateioperationen fertiggestellt wird
● Das Programm hat drei Funktionen:● Generieren eines One-Time-Pad (das ist der Schlüssel)● Verschlüsseln einer Datei (Klartext einer Nachricht) mit einem
solchen Schlüssel● Entschlüsseln einer verschlüsselten Datei
● Es wird Code zu vorhandenen Kommentaren geschrieben, damit das gegebene Programm Dateien lesen, verarbeiten und schreiben kann.
BS: U5 – Dateioperationen 2525
Programmieraufgabe One-Time-Pads● Vorgehensweise beim Verschlüsseln und Entschlüsseln:
● die Eingabedatei (den Klartext) blockweise lesen● immer einen gleichgroßen Block des Schlüssels (das One-Time-
Pad, auch eine Datei) lesen● alle Bytes der Dateien der Reihe nach mit dem exklusiv-ODER
verknüpfen● den so verschlüsselten Block in die Ausgabedatei schreiben
● Schlüssel besteht dabei aus Zufallszahlen, also ist das Ergebnis auch zufällig
68F4D21A 76 4F 8FA2
D8914A62 74 3C F632
B0659878 02 73 7990Klartext:
Schlüssel:
Verschlüsselt:
………
………
………
BS: U5 – Dateioperationen 2626
Programmieraufgabe One-Time-Pads● Entschlüsseln durch einen zweiten Durchlauf:
● Seien kx, s
x und v
x Bits aus dem Klartext, dem Schlüssel und der
verschlüsselten Datei, d.h. vx = k
x xor s
x (v
x wurde so erzeugt).
● xor ist assoziativ, also giltv
x xor s
x = (k
x xor s
x) xor s
x = k
x xor (s
x xor s
x) = k
x xor 0 = k
x
● Das Verfahren hat aber auch Nachteile● der Schlüssel (das One-Time-Pad) muss echt zufällig sein
(geht nicht ohne Hardware, also müssen hier pseudo-zufällige Zahlen als Demonstration reichen)
● Schlüssel sind genauso groß wie der Klartext● Man darf ein One-Time-Pad nicht mehrfach verwenden!
Dazu erschweren wir die Wiederverwendung, indem wir in den Schlüsseldateien speichern, ob sie bereits benutzt wurden, und eine Kopie des Schlüssels nur zum Entschlüsseln anlegen.
BS: U5 – Dateioperationen 2727
Programmieraufgabe One-Time-Pads● Generieren von Schlüsseln:
● vorgegebene Funktion pseudorandom() füllt beliebig lange Speicherblöcke mit (sehr schlechten) Zufallszahlen
● damit wird eine Schlüsseldatei blockweise gefüllt
● Mehrfaches Verwenden erschweren:● Schlüsseldateien kennzeichnen mit einer sog. „magic number“● steht in den ersten vier Bytes (praktisch ein int), vor dem
One Time Pad‑ ‑● Es gibt drei solche magic numbers
- eine für neue, unbenutzte Schlüssel
- eine für bereits benutzte Schlüssel
- eine für Schlüssel, mit denen nur entschlüsselt werden soll
(eine Kopie des Schlüssels für den Empfänger der Nachricht)
● beim Verschlüsseln wird anhand der magic number geprüft, ob der Schlüssel überhaupt geeignet ist
BS: U5 – Dateioperationen 2828
Programmieraufgabe One-Time-Pads● In den Vorgaben existieren bereits
● generate_otp() zum Generieren● otp_encrypt() zum Verschlüsseln● otp_decrypt() zum Entschlüsseln
● Der gesamte Code ist ausführlich kommentiert und lässt sich übersetzen, erzeugt aber noch keine Dateien
● Stellt das Programm Schritt für Schritt gemäß den Kommentaren fertig
● Denkt unbedingt an eine vollständige Fehlerbehandlung!(benutzt dazu z.B. das vorgegebene Makro)
BS: U5 – Dateioperationen 2929
Falls noch Zeit ist ....● ... eine kleine Vorstellung von GDB.
BS: U5 – Dateioperationen 3030
Optional: GDB● GDB – GNU Debugger● kommandozeilenbasiert● grafische Front-ends verfügbar (z.B. DDD, Insight)● ... oder integriert (z.B. Eclipse, NetBeans)● Starten mit:
● Programm normal ausführen:
● GDB fängt Signale an das gestartete Programm ab (z.B. ungültige Speicherzugriffe) und erlaubt Einsicht und Manipulation von Variablenwerten und Kontrollfluss.
home:~> gdb malloctest
(gdb) run
BS: U5 – Dateioperationen 3131
Optional: GDB – Segfault● Beispiel:
int main(){ int i = 43; int *p = (int*)0; *p = ++i; // Versucht Wert an Adresse 0 zu schreiben. return 0;}
(gdb) runStarting program: /home/kleinsor/temp/main
Program received signal SIGSEGV, Segmentation fault.0x08048378 in main () at main.c:45 *p = ++i; // Versucht Wert an Adresse 0 zu schreiben.(gdb)
# Verwende u.a. keine Register als Zwischenspeicher, + MetainfoHome> gcc -O0 -g -Wall -o main main.c
Home> gdb main
BS: U5 – Dateioperationen 3232
Optional: GDB – Segfault entdecken● Beispiel:
int main(){ int i = 43; int *p = (int*)0; *p = ++i; // Versucht Wert an Adresse 0 zu schreiben. return 0;}
Starting program: /home/kleinsor/temp/main
Program received signal SIGSEGV, Segmentation fault.0x08048378 in main () at main.c:44 *((int*)0) = ++i; // Versucht Wert an Adresse 0 zu schreiben.(gdb)print i$1 = 44(gdb)info localsi = 44p = (int *) 0x0(gdb)info variables[Symboltabelle!]
BS: U5 – Dateioperationen 3333
Optional: GDB – Breakpoints● Halten Ausführung an vordefinieren Punkten an.● Erlaubt also auch das punktuelle Auswerten von Variablen.
Home> gdb main(gdb) break mainBreakpoint 1 at 0x80483bc: file main.c, line 9.(gdb) runStarting program: /home/kleinsor/temp/a.out
Breakpoint 1, main () at main.c:99 printf("%d\n", segfault());(gdb)
#include <stdio.h>int segfault() { int i = 43; int *p = (int*)0; *p = ++i; return *p; }int main() { printf("%d\n", segfault()); return 0; }
BS: U5 – Dateioperationen 3434
Optional: GDB – Breakpoints#include <stdio.h>int segfault() { int i = 43; int *p = (int*)0; *p = ++i; // Der Segfault-Kandidat return *p; }int main() { printf("%d\n", segfault()); return 0; }
(gdb) list segfault[...]5 *p = ++i; // Der Segfault-Kandidat[...](gdb) break 4Breakpoint 1 at 0x8048391: file main.c, line 4.(gdb) contBreakpoint 1, segfault () at main.c:44 int *p = (int*)0;(gdb) backtrace#0 0x080483a2 in segfault () at main.c:4#1 0x080483c1 in main () at main.c:9(gdb) set variable p = &i(gdb) contContinuing.44
BS: U5 – Dateioperationen 3535
Optional: GDB – Even more● Kann sowohl auf Sourcecodeebene (-00 -g) als auch auf
Instruktionsebene arbeiten.● Also auch direkt mit Adressen im Speicher des überwachten
Prozesses.
● Kann noch erheblich mehr: ● Ausdrücke überwachen● Umgebung manipulieren (freier Zugriff!)● Kontrollfluss manipulieren: z.B. Funktionen 'interaktiv' aufrufen● und sehr viel mehr!
● Hilfe:● Allgemein: z. B.● Spezifisch: z. B.
(gdb) help
(gdb) help break
Recommended