Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
B
bs
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Moh Kh
bs
Commits
7d9c000d
Commit
7d9c000d
authored
3 years ago
by
Peter Gerwinski
Browse files
Options
Downloads
Patches
Plain Diff
Notizen 21.5.2021
parent
aacc5be0
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
20210521/bs-20210521.txt
+309
-0
309 additions, 0 deletions
20210521/bs-20210521.txt
with
309 additions
and
0 deletions
20210521/bs-20210521.txt
0 → 100644
+
309
−
0
View file @
7d9c000d
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?
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment