diff --git a/20230515/bs-20230515.txt b/20230515/bs-20230515.txt index 8f14e3a364f0602129f111d4feae6f83d446d36f..e7f437d115db2602c1ef99fe9f37afcddfab6e80 100644 --- a/20230515/bs-20230515.txt +++ b/20230515/bs-20230515.txt @@ -1,5 +1,5 @@ -Von "Hello, world!n" bis zum Kernel, 15.05.2023, 16:20:31 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Von "Hello, world!\n" bis zum Kernel, 15.05.2023, 16:20:31 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ printf ("Hello, world!\n"); wird zu puts ("Hello, world!"); optimiert. Quelltext von puts(): apt-get source glibc diff --git a/20230522/bs-20230522.txt b/20230522/bs-20230522.txt new file mode 100644 index 0000000000000000000000000000000000000000..3767f5926ce59b033f4de227fbd2c281cfeb00f7 --- /dev/null +++ b/20230522/bs-20230522.txt @@ -0,0 +1,247 @@ +Vom syscall-Befehl bis zum Treiber-Modul, 22.05.2023, 16:11:55 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +arch/x86/entry/entry_64.S: + Assembler-Code für den Einsprung, nachdem das Benutzerprogramm + den syscall-Befehl aufgerufen hat + (einschließlich "trampoline": Code, der ermöglicht, ein Callback + auch mit einer anderen als der vorgesehenen Anzahl von Parametern + aufzurufen) + --> Funktionsaufruf do_syscall_64 (unsigned long a, struct pt_regs *b) + a = Nummer des Syscall-Aufrufs + b = Zeiger auf eine Datenstruktur auf dem CPU-Stack, + die die Registerinhalte des Benutzerprogramms enthält: + Stack-Segment, Stack-Pointer, CPU-Flags, Code-Segment, Instruction Pointer + (wichtig für den Zugriff auf Variable des aufrufenden Programms + sowie für den Rücksprung) + +arch/x86/entry/common.c: + if (unlikely(nr >= NR_syscalls)) + goto bad; + --> Hinweis an den Compiler, welcher Zweig einer if-Verzweigung + wahrscheinlicher ist, mit dem Ziel, Pipelining möglichst effizient + zu unterstützen + nr = array_index_nospec(nr, NR_syscalls) + --> Mache aus dem Übergebenen Parameter (Nr. des Syscalls) + einen Index für ein Array. + Wenn alles funktioniert hat: + regs->ax = sys_call_table[nr](regs); + --> Aufruf der Funktion, die den Syscall durchführt. + Das Ergebnis des Funktionsaufrufs speichern wir in der Struktur, die + die Registerinhalte des Aufrufers enthält. Der Aufrufer bekommt somit + den Funktionsrückgabewert in seinem ax-Register (genauer: %rax). + sys_call_table ist eine Tabelle mit Zeigern auf Funktionen. + Worauf zeigen diese? + +arch/x86/entry/syscalls/syscall_64.tbl: + 0 common read __x64_sys_read + 1 common write __x64_sys_write + 2 common open __x64_sys_open + ... +arch/x86/entry/syscalls/syscalltbl.sh: + Dieses Shell-Skript macht aus der o.a. Tabelle eine Datei "syscalls_64.h" + mit Aufrufen eines Präprozessor-Macros: + __SYSCALL_64(0, __x64_sys_read, ) + __SYSCALL_64(0, sys_read, ) + __SYSCALL_64(0, __x64_sys_read, ) + __SYSCALL_64(0, sys_read, ) + __SYSCALL_64(1, __x64_sys_write, ) + __SYSCALL_64(1, sys_write, ) + __SYSCALL_64(2, __x64_sys_open, ) + __SYSCALL_64(2, sys_open, ) + ... +arch/x86/entry/syscall_64.c: + #define __SYSCALL_64(nr, sym, qual) extern asmlinkage long sym(const struct pt_regs *); + #include <asm/syscalls_64.h> + #undef __SYSCALL_64 + --> Extern-Deklarationen aller SysCall-Funktionen (Prototypen) + Dadurch möglich: Zeiger auf diese Funktionen zeigen lassen + #define __SYSCALL_64(nr, sym, qual) [nr] = sym, + + 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> + }; + --> Dies ist die Tabelle, ein initialisiertes Array. + Bei jedem Index "nr" steht ein Zeiger auf die Funktion "sym". + Vorher initialisieren wir das ganze Array auf "&sys_ni_syscall" + ("not implemented syscall"). + +Wie sorgen wir nun dafür, daß unser Einsprungpunkt "entry_SYSCALL_64_trampoline" +tatsächlich aufgerufen wird, wenn ein Benutzerprogramm den syscall-Befehl aufruft? + +arch/x86/kernel/cpu/common.c: + void syscall_init(void) + { + ... + wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS); + if (static_cpu_has(X86_FEATURE_PTI)) + wrmsrl(MSR_LSTAR, SYSCALL64_entry_trampoline); + else + wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64); + ... + } + --> Hier hinterlegen wir die Adresse von SYSCALL64_entry_trampoline + in einem internen Prozessorregister, das genau für diesen Aufruf + zuständig ist. + wrmsrl: In ein solches Register schreiben + MSR_STAR: Nummer des Registers, das dafür zuständig ist, sich die Segmente zu merken + (__KERNEL_CS = Code-Segment des Kernels) + MSR_LSTAR: Nummer des Registers, das dafür zuständig ist, sich das Offset zu merken + +Wo ist die Funktion sys_write() denn nun definiert? + + include/linux/syscalls.h: + + /* fs/read_write.c */ + ... + asmlinkage long sys_write(unsigned int fd, const char __user *buf, + size_t count); + + fs/read_write.c: + + ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count) + { + struct fd f = fdget_pos(fd); + ssize_t ret = -EBADF; + + if (f.file) { + loff_t pos = file_pos_read(f.file); + ret = vfs_write(f.file, buf, count, &pos); + if (ret >= 0) + file_pos_write(f.file, pos); + fdput_pos(f); + } + + return ret; + } + + SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, + size_t, count) + { + return ksys_write(fd, buf, count); + } + + ebenfalls in fs/read_write.c: + + 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); + else if (file->f_op->write_iter) + return new_sync_write(file, p, count, pos); + else + return -EINVAL; + } + +Damit sind wir eigentlich am Ziel. + +Noch offen: Wie gelangen die "fops"-Callbacks aus dem Kernel-Modul +in die "struct file"-Datenstruktur? + +Dies führt uns zu dem Konzept der inodes (index nodes). + + include/linux/fs.h: + + struct file { + union { + struct llist_node fu_llist; + struct rcu_head fu_rcuhead; + } f_u; + struct path f_path; + struct inode *f_inode; /* cached value */ + const struct file_operations *f_op; + + /* + * Protects f_ep_links, f_flags. + * Must not be taken from IRQ context. + */ + spinlock_t f_lock; + enum rw_hint f_write_hint; + atomic_long_t f_count; + unsigned int f_flags; + fmode_t f_mode; + struct mutex f_pos_lock; + loff_t f_pos; + struct fown_struct f_owner; + const struct cred *f_cred; + struct file_ra_state f_ra; + + u64 f_version; + #ifdef CONFIG_SECURITY + void *f_security; + #endif + /* needed for tty driver, and maybe others */ + void *private_data; + + #ifdef CONFIG_EPOLL + /* Used by fs/eventpoll.c to link all the hooks to this file */ + struct list_head f_ep_links; + struct list_head f_tfile_llink; + #endif /* #ifdef CONFIG_EPOLL */ + struct address_space *f_mapping; + errseq_t f_wb_err; + } __randomize_layout + __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */ + + ... + + struct file_operations { + ... + ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); + ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); + ... + } __randomize_layout; + +Dieselbe "struct file_operations" übergeben wir in Kernel-Modulen beim Aufruf +von register_chrdev(). + + ebenfalls in include/linux/fs.h: + + struct inode { + ... + dev_t i_rdev; + ... + const struct file_operations *i_fop; /* former ->i_op->default_file_ops */ + ... + union { + struct pipe_inode_info *i_pipe; + struct block_device *i_bdev; + struct cdev *i_cdev; + char *i_link; + unsigned i_dir_seq; + }; + ... + } __randomize_layout; + + --> Ein inode repräsentiert entweder eine Pipe oder ein Block Device oder ein Char Device + oder einen Symlink oder ein Verzeichnis oder eine normale Datei. + + include/linux/cdev.h: + + struct cdev { + struct kobject kobj; + struct module *owner; + const struct file_operations *ops; + struct list_head list; + dev_t dev; + unsigned int count; + } __randomize_layout; + + include/linux/types.h: + + typedef u32 __kernel_dev_t; + ... + typedef __kernel_dev_t dev_t; + + include/linux/kdev_t.h: + + #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) + #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) + +Damit haben wir auch gefunden, wie die Datei ihre file_operations +aus dem Kernel-Modul bekommt (nämlich über den inode). diff --git a/20230522/trampoline-01.pas b/20230522/trampoline-01.pas new file mode 100644 index 0000000000000000000000000000000000000000..465cae325ee8ee10198ff58e7ef9948abd553eb8 --- /dev/null +++ b/20230522/trampoline-01.pas @@ -0,0 +1,15 @@ +program Trampoline; + +procedure MacheWas (procedure Callback (a: Integer)); +begin + Callback (42) +end; + +procedure Answer (a: Integer); +begin + WriteLn ('The answer is: ', a) +end; + +begin + MacheWas (Answer) +end. diff --git a/20230522/trampoline-02.pas b/20230522/trampoline-02.pas new file mode 100644 index 0000000000000000000000000000000000000000..75343c0ba34c8077b92d3f9da72b651bae8199c7 --- /dev/null +++ b/20230522/trampoline-02.pas @@ -0,0 +1,24 @@ +program Trampoline; + +procedure MacheWas (procedure Callback (a: Integer)); +begin + Callback (42) +end; + +procedure Philosophy; +var + Msg: String[42]; + + procedure Answer (a: Integer); + begin + WriteLn (Msg, a) + end; + +begin + Msg := 'The answer is: '; + MacheWas (Answer) +end; + +begin + Philosophy +end.