Skip to content
Snippets Groups Projects
Commit af417384 authored by Peter Gerwinski's avatar Peter Gerwinski
Browse files

Notizen 18.5.2020: vom Systemaufruf bis zum Treiber-Modul

parent f0a7c6f9
No related branches found
No related tags found
No related merge requests found
......@@ -51,43 +51,3 @@ Diese Funktionen führen den Code aus sysdeps/unix/sysv/linux/x86_64/syscall.S a
Der Assembler-Befehl syscall bildet die Schnittstelle zwischen dem Programm im User-Space
und dem Kernel. An dieser Stelle gewinnt das Programm die zusätzlichen Rechte, die nötig
sind, um z.B. direkt in den Bildschrimspeicher schreiben zu können.
Kernel, 25.05.2017, 17:53:23
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Suchbegriff: sys_call_table
/usr/src/linux-headers-4.9.0-2-amd64/arch/x86/include/generated/asm/syscalls_64.h
arch/x86/entry/syscall_64.c: Defnition sys_call_table
arch/x86/entry/entry_64.S: Verwendung sys_call_table
arch/x86/kernel/cpu/common.c: wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
https://sites.google.com/site/masumzh/articles/hypervisor-based-virtualization/compute-virtualization
"As shown below, the content of the IA32_LSTAR MSR (Model Specific Register)
is copied to the instruction pointer register (RIP) [...]"
--> Weiter geht's mit dem Eintrag in sys_call_table,
also mit der Implementation von sys_write().
Die Tabelle wird benutzt in der Funktion do_syscall_64():
regs->ax = sys_call_table[nr](
regs->di, regs->si, regs->dx,
regs->r10, regs->r8, regs->r9);
Wenn man lange genug sucht, findet man: fs/read_write.c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
ret = vfs_write(f.file, buf, count, &pos);
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
ret = __vfs_write(file, buf, count, pos);
ssize_t __vfs_write(struct file *file, const char __user *p, size_t count, loff_t *pos)
if (file->f_op->write) return file->f_op->write(file, p, count, pos);
:-)
Jetzt fehlt nur noch: Wie bekommt f_op->write den Wert, den das Modul hinterlegt hat?
include/linux/fs.h: Definition "inode"
include/linux/cdev.h: Char-Device-spezifische Felder des inode
Major und Minor sind gemeinsam in einer 16-Bit-Integer hinterlegt.
18.05.2020, 13:33:02
~~~~~~~~~~~~~~~~~~~~
:) letzte Woche: von der Anwendung durch die Systembibliothek bis zum Kernel
:) im Kernel: vom Systemaufruf bis zum Callback des Treiber-Moduls
Kernel, 25.05.2017, 17:53:23
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Fragestellung: Wir haben in der glibc beim Maschinenbefehl "syscall"
aufgehört. Wie geht es nun im Kernel weiter? Wer nimmt den Funktions-
aufruf entgegen?
Suchbegriff: sys_call_table
--> Es gibt eine Tabelle, die für jeden der durchnumerierten Systemaufrufe
einen Zeiger auf die aufzurufende Funktion enthält.
--> Der Maschinenbefehl "syscall" ruft eine Funktion innerhalb des
Kernels auf. Wo steht, welche Funktion das ist?
arch/x86/entry/syscall_64.c: Defnition des Arrays sys_call_table
Initialisiertes Array von Zeigern auf Funktionen.
Zunächst zeigen alle Zeiger auf die Funktion sys_ni_syscall(),
die lediglich eine Fehlermeldung zurückgibt ("nicht implementiert").
Danach werden auf diejenigen Funktionen, die es tatsächlich gibt
Zeiger initialisiert. Dies erfolgt durch ein #include der Datei
/usr/src/linux-headers-4.19.0-8-amd64/arch/x86/include/generated/asm/syscalls_64.h
mit vorheriger Definition eines Präprozessor-Macros __SYSCALL().
asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
/*
* Smells like a compiler bug -- it doesn't work
* when the & below is removed.
*/
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_64.h>
--> [0] = __x64_sys_read,
[1] = __x64_sys_write,
[2] = __x64_sys_open,
...
};
Vorher wurden mit demselben Trick alle Funktionen vorwärts-deklariert:
#define __SYSCALL_64(nr, sym, qual) extern asmlinkage long sym(const struct pt_regs *);
#include <asm/syscalls_64.h>
--> extern asmlinkage long __x64_sys_read(const struct pt_regs *);
extern asmlinkage long __x64_sys_write(const struct pt_regs *);
extern asmlinkage long __x64_sys_open(const struct pt_regs *);
...
#undef __SYSCALL_64
Wie können wur nun dafür sorgen, daß der Befehl syscall dieses Array
auch benutzt?
arch/x86/entry/entry_64.S enthält eine Funktion entry_SYSCALL_64,
die eine Funktion "do_syscall_64()" aufruft
und sich über "USERGS_SYSRET64" beendet.
Dies ist ein Präprozessor-Makro, der zu "swapgs; sysretq;" expandiert
(Definition in include/asm/irqflags.h).
Woher kommt die Include-Datei in "generated"?
arch/x86/entry/syscalls/Makefile ruft das Shell-Skript syscalltbl.sh auf.
Dieses liest syscall_64.tbl und erzeugt daraus syscall_64.h.
Damit haben wir die Funktion gefunden, die den Syscall entgegennehmen soll.
Daraus ergeben sich neue Fragen:
1. Wie sorge ich dafür, daß sie tatsächlich bei "syscall" aufgerufen wird?
2. Wie geht es von dort aus weiter bis zu der Tabelle "sys_call_table"?
zu 1.:
Suche nach "entry_SYSCALL_64" liefert:
arch/x86/kernel/cpu/common.c: wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
Was bedeutet "wrmsrl"?
https://sites.google.com/site/masumzh/articles/hypervisor-based-virtualization/compute-virtualization
"As shown below, the content of the IA32_LSTAR MSR (Model Specific Register)
is copied to the instruction pointer register (RIP) [...]"
Demnach ist das Prozessorregister "IA32_LSTAR MSR" die Einsprungadresse,
zu der der "syscall"-Aufruf springen soll.
Um das Register "IA32_LSTAR MSR" zu setzen, gibt es den Befehl "wrmsrl"
(Write MSR (long = 32 Bit)).
Offensichtlich ist das MSR ein Array, und "IA32_LSTAR" ist der Index.
zu 2.:
arch/x86/entry/common.c enthält die C-Funcktion do_syscall_64().
Diese enthält die Zeile "regs->ax = sys_call_table[nr](regs);", also den
Aufruf der Funktion, auf die ein Zeiger in der tabelle "sys_call_table"
an der Stelle "nr" gespeichert ist.
--> Weiter geht's mit dem Eintrag in sys_call_table,
also mit der Implementation von sys_write().
Die Tabelle wird benutzt in der Funktion do_syscall_64():
regs->ax = sys_call_table[nr](
regs->di, regs->si, regs->dx,
regs->r10, regs->r8, regs->r9);
Wo wird die Funktion __x64_sys_write definiert?
Wenn man lange genug sucht, findet man: arch/x86/include/asm/syscall_wrapper.h
Dort steht:
#define __SYSCALL_DEFINEx(x, name, ...) \
... \
asmlinkage long __x64_sys##name(const struct pt_regs *regs) \
{ \
return __se_sys##name(SC_X86_64_REGS_TO_ARGS(x,__VA_ARGS__));\
} \
...
Dies ist eine kleine Funktion __x64_sys_write(), die ihrerseits eine Funktion
__se_sys_write() aufruft.
Der Präprozessor-Makro __SYSCALL_DEFINEx() wird über einen anderen Präprozessor-Makro
SYSCALL_DEFINEx() aufgerufen, der in include/linux/syscalls.h definiert wird
und seinerseits über SYSCALL_DEFINE3() aufgerufen wird, der ebenfalls in
include/linux/syscalls.h definiert wird.
Wenn man lange genug sucht, findet man: fs/read_write.c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count)
Dort wird eine Funktion definiert, die ihrerseits eine Funktion ksys_write() aufruft.
Diese enthält:
ret = vfs_write(f.file, buf, count, &pos);
Hier passiert das eigentliche Schreiben.
Ebenfalls in fs/read_write.c:
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
Diese ruft auf:
ret = __vfs_write(file, buf, count, pos);
Ebenfalls in fs/read_write.c:
ssize_t __vfs_write(struct file *file, const char __user *p, size_t count, loff_t *pos)
Diese ruft auf:
if (file->f_op->write) return file->f_op->write(file, p, count, pos);
:-)
Jetzt fehlt nur noch: Wie bekommt f_op->write den Wert, den das Modul hinterlegt hat?
Der Funktion wird ein Parameter "file" übergeben.
Dies ist eine "struct file", die u.a. ein Feld "f_op" enthält,
das auf ein weiteres struct mit Callback-Funktionen zeigt.
Wie kommen die "richtigen" Callback-Funktionen dort hinein?
Eigentlich müßte dies bei register_chrdev() passieren.
Schauen wir uns daher an, was register_chrdev() eigentlich macht.
Dies ist definiert in include/linux/fs.h und ruft die Funktion
__register_chrdev() auf.
Diese wiederum ist definiert in fs/char_dev.c.
Sie alloziert eine cdev-Struktur und speichert darin die fops.
Die cdev-Struktur wird dann mittels cdev_add() in eine Liste eingefügt.
Hierfür ruft cdev_add() die Funktion kobj_map() auf.
Diese Funktion wird in drivers/base/map.c definiert
und legt die cdev-Struktur in einer Hash-Tabelle ab.
Der letzte Parameter "data" ist dabei der Zeiger auf die cdev-Strutur,
die auch die fops enthält.
Wie kommen nun die fops von der Hash-Tabelle aus in die file-Struktur?
Vermutung: Dies geschieht beim Öffnen der Datei.
In fs/open.c gibt es eine Funktion vfs_open().
Diese sucht den zur Datei gehörenden inode heraus und ruft damit do_dentry_open() auf.
Theorie der Dateisysteme: Dateien werden durch "inodes" repräsentiert.
include/linux/fs.h: Definition "inode"
Die "struct inode" enthält ein Datenfeld "i_rdev", in dem die Major- und Minor-Nr.
der Gerätedatei gemeinsam gespeichert sind.
--> Sobald wir einen inode kennen, kennen wir Major- und Minor-Nr. der Gerätedatei.
do_dentry_open() schreibt den inode in die file-Struktur,
holt die fops aus dem inode und legt sie direkt in der file-Struktur ab.
Das einzige, was jetzt noch fehlt: Wie kommen die fops in den inode?
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment