diff --git a/20220425/bs-20220425.txt b/20220425/bs-20220425.txt index ba7537fce07fd14c553030fdd850ea464a8276a0..3841aab1f5dd71438f3e18f23115c91f559ab684 100644 --- a/20220425/bs-20220425.txt +++ b/20220425/bs-20220425.txt @@ -62,3 +62,219 @@ Datei fileops.c, Zeile 791 --> __libc_write() Datei sysdeeps/unix/sysv/linux/write.c, Zeile 26 --> return SYSCALL_CANCEL (write, fd, buf, nbytes); + +Systemaufrufe, 21.05.2021, 12:08:00 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +hello-2.c +... +--> _IO_new_do_write (f, ...) +--> new_do_write (f, ...) +--> _IO_SYSWRITE (fp, ...) +--> __libc_write() +Datei sysdeeps/unix/sysv/linux/write.c, Zeile 26 +--> return SYSCALL_CANCEL (write, fd, buf, nbytes); + +b+ │0x7ffff7eb54f0 <__GI___libc_write> lea 0xd61f9(%rip),%rax # 0x7ffff7f8b6f0 │ + >│0x7ffff7eb54f7 <__GI___libc_write+7> mov (%rax),%eax │ + │0x7ffff7eb54f9 <__GI___libc_write+9> test %eax,%eax │ + │0x7ffff7eb54fb <__GI___libc_write+11> jne 0x7ffff7eb5510 <__GI___libc_write+32> │ + │0x7ffff7eb54fd <__GI___libc_write+13> mov $0x1,%eax │ + │0x7ffff7eb5502 <__GI___libc_write+18> syscall │ + │0x7ffff7eb5504 <__GI___libc_write+20> cmp $0xfffffffffffff000,%rax │ + │0x7ffff7eb550a <__GI___libc_write+26> ja 0x7ffff7eb5560 <__GI___libc_write+112> │ + │0x7ffff7eb550c <__GI___libc_write+28> retq │ + │0x7ffff7eb550d <__GI___libc_write+29> nopl (%rax) │ + │0x7ffff7eb5510 <__GI___libc_write+32> push %r12 │ + │0x7ffff7eb5512 <__GI___libc_write+34> mov %rdx,%r12 │ + │0x7ffff7eb5515 <__GI___libc_write+37> push %rbp + +lea: Lade einen Zeiger aus einer konstanten Tabelle in das rax-Register +mov: Lade das, worauf der Zeiger zeigt, in das eax-Register +test: Prüfe, welchen Wert eax hat +jne: Jump if Not Equal = bedingter Sprung, falls ungleich 0 (Vermutung: Fehler) +mov: Lade die Zahl 1 als Parameter für syscall in das eax-Register. 1 steht für "write". +syscall: übergebe an den Kernel --> schreibt "Hello, world!\n" auf den Bildschirm +cmp: Vergleiche den Wert von eax mit einer Zahl +ja: Jump if Above (Prüfen auf Fehler) +retq: Beende die Funktion + +syscall erwartet Parameter in den Prozessor-Registern: + eax: Nummer der Funktion, die aufgerufen werden soll + rsi: Zeiger auf die auszugebenden Daten + (siehe: https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-1.html) + ebx oder edx: Länge der auszugebenden Daten + (ggf. weitere Register) + +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); + +Wo wird die Funktion __x64_sys_write definiert? +Wenn man lange genug sucht, findet man: arch/x86/include/asm/syscall_wrapper.h +Dort steht 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?