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

Notizen 21.5.2021

parent aacc5be0
Branches
No related tags found
No related merge requests found
Bedeutung von "%rip", "%rax" usw. 21.05.2021, 12:04:22
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Quellen:
https://softwareengineering.stackexchange.com/questions/127668/what-does-the-r-in-x64-register-names-stand-for
https://stackoverflow.com/questions/10995349/what-does-the-r-stand-for-in-rax-rbx-rcx-rdx-rsi-rdi-rbp-rsp
1. Quelle:
It means register, and it isn't all for historical reasons.
The historical part is that Intel got itself into the habit of enumerating
registers with letters with the 8008 (A through E plus H and L). That scheme
was more than adequate at the time because microprocessors had very few
registers and weren't likely to get more, and most designs did it. The
prevailing sentiment then was that software would be rewritten for new CPUs as
they appeared, so changing the register naming scheme between models wouldn't
have been a big deal. Nobody foresaw the 8088 evolving into a "family" after
being incorporated into the IBM PC, and the yoke of backward compatibility
pretty much forced Intel into having to adopt schemes like the "E" on 32-bit
registers to maintain it.
The non-historical part is all practical. Using letters for general-purpose
registers limits you to 26, fewer if you weed out those that might cause
confusion with the names of special-purpose registers like the program counter,
flags or the stack pointer.
I don't have a source to confirm it, but I suspect the choice of R as a prefix
and the introduction of R8 through R15 on 64-bit CPUs signals a transition to
numbered registers, which have been the norm among 32-bit-and-larger
architectures not derived from the 8008 for almost half a century. IBM did it
in the 1960s with the 360 and has been followed by the PowerPC, DEC Alpha,
MIPS, SPARC, ARM, Intel's i860 and i960 and a bunch of others that are
long-forgotten.
You'll note that the existing registers would fit nicely into R0 through R7 if
they existed, and it wouldn't surprise me a bit if they're treated that way
internally. The existing long registers (RAX/EAX/AX/AL, RBX/EBX/BX/BL, etc.)
will probably stay around until the sun burns out.
rax/eax/ax/al = "accumulator"
rbx/ebx/bx/bl = "base"
rcx/ecx/cx/cl = "counter"
rdx/edx/dx/dl = "data"
rsi/esi/si = "source index" (64/32/16 Bit)
rdi/edi/di = "destination index" (64/32/16 Bit)
16-Bit-Assembler:
mov si <-- "Hello, world!\n" (Zeiger auf String)
mov di <-- Adresse einer Variablen (String dorthin kopieren)
mov cx <-- 14 (Länge)
rep movsb (Kopierschleife in einem einzigen Assembler-Befehl mit Präfix)
"CISC" - "Complex Instruction Set Computing"; z.B. x86-Architektur
"RISC" - "Reduced Instruction Set Computing"; z.B. i860, Atmel, ARM, MIPS, RISC-V
Statt "rep movsb": explizite Schleife
loop:
mov al <-- [si]
mov [di] <-- al
inc si
inc di
dec cx
jnz loop (Jump if Not Zero)
Intel i486: typischerweise 10 bis 30 Taktzyklen pro Befehl
Intel i860: bis zu 3 Befehle pro Taktzyklus
Pipeline-Befehle
"delayed branch"
--> Nebenläufigkeit
--> mit 40 MHz ca. 6.5mal so schnell wie i486 mit 66 MHz
loop:
mov al <-- [si]
mov [di] <-- al
inc si
dec cx
jnz loop (Jump if Not Zero - "delayed")
inc di <-- wird auf jeden Fall noch vor dem Sprung ausgeführt
Warum kam der Übergang von CISC zu RISC?
- "rep movsb": etliche Takte pro "Schleifendurchlauf"
- andere Befehle: mehrere Takte pro Befehl
- RISC: typischerweise 1 Befehl pro Takt
--> Eine "lange" Schleife in RISC verbraucht weniger
Takte pro Schleifendurchlauf als "rep movsb".
--> RISC ist schneller.
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?
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment