Kann jemand erklären, was der folgende Assembler-Code bewirkt?
int 0x80
Kann jemand erklären, was der folgende Assembler-Code bewirkt?
int 0x80
Antworten:
Es übergibt die Steuerung an den Interruptvektor 0x80
Sehen http://en.wikipedia.org/wiki/Interrupt_vector
Unter Linux haben Sie einen Blick auf diese : es wurde verwendet , um handhaben system_call
. Auf einem anderen Betriebssystem könnte dies natürlich etwas völlig anderes bedeuten.
int 0x80
als eine besondere Art call
einer Funktion im Kernel vorstellen (ausgewählt von eax
).
int
bedeutet Interrupt, und die Nummer 0x80
ist die Interrupt-Nummer. Ein Interrupt überträgt den Programmablauf an denjenigen, der diesen Interrupt behandelt, 0x80
in diesem Fall Interrupt . Unter Linux ist der 0x80
Interrupt-Handler der Kernel und wird verwendet, um Systemaufrufe des Kernels durch andere Programme durchzuführen.
Der Kernel wird benachrichtigt, welchen Systemaufruf das Programm ausführen möchte, indem der Wert im Register %eax
überprüft wird (AT & T-Syntax und EAX in Intel-Syntax). Jeder Systemaufruf hat unterschiedliche Anforderungen an die Verwendung der anderen Register. Zum Beispiel ein Wert von 1
in%eax
einen Systemaufruf von exit()
, und der Wert in %ebx
enthält den Wert des Statuscodes für exit()
.
Denken Sie daran, dass 0x80
= 80h
=128
Sie können hier sehen , dass dies INT
nur eine der vielen Anweisungen (tatsächlich die Assembler-Darstellung (oder sollte ich 'mnemonisch' sagen)) ist, die im x86-Befehlssatz vorhanden sind. Weitere Informationen zu dieser Anleitung finden Sie auch in Intels eigenem Handbuch hier .
Um aus dem PDF zusammenzufassen:
INT n / INTO / INT 3 - Aufruf zum Unterbrechen der Prozedur
Der Befehl INT n generiert einen Aufruf an den mit dem Zieloperanden angegebenen Interrupt- oder Exception-Handler. Der Zieloperand gibt einen Vektor von 0 bis 255 an, der als vorzeichenloser 8-Bit-Zwischenwert codiert ist. Der Befehl INT n ist die allgemeine Mnemonik zum Ausführen eines durch Software generierten Aufrufs an einen Interrupt-Handler.
Wie Sie sehen können, ist 0x80 der Zieloperand in Ihrer Frage. Zu diesem Zeitpunkt weiß die CPU, dass sie Code ausführen sollte, der sich im Kernel befindet, aber welcher Code? Dies wird durch den Interrupt-Vektor unter Linux bestimmt.
Einer der nützlichsten DOS-Software-Interrupts war Interrupt 0x21. Durch Aufrufen mit verschiedenen Parametern in den Registern (meistens ah und al) können Sie auf verschiedene E / A-Operationen, Zeichenfolgenausgabe und mehr zugreifen.
Die meisten Unix-Systeme und -Derivate verwenden keine Software-Interrupts, mit Ausnahme des Interrupts 0x80, der zum Tätigen von Systemaufrufen verwendet wird. Dies wird erreicht, indem ein 32-Bit-Wert, der einer Kernelfunktion entspricht, in das EAX-Register des Prozessors eingegeben und dann INT 0x80 ausgeführt wird.
Schauen Sie sich dies bitte an, wo andere verfügbare Werte in den Interrupt-Handler-Tabellen angezeigt werden:
Wie Sie in der Tabelle sehen können, weist die CPU darauf hin, einen Systemaufruf auszuführen. Die Linux System Call-Tabelle finden Sie hier .
Wenn Sie also den Wert 0x1 in das EAX-Register verschieben und INT 0x80 in Ihrem Programm aufrufen, können Sie den Prozess veranlassen, den Code im Kernel auszuführen, der den aktuell ausgeführten Prozess stoppt (beendet) (unter Linux, x86 Intel-CPU).
Ein Hardware-Interrupt darf nicht mit einem Software-Interrupt verwechselt werden. Hier ist eine sehr gute Antwort in dieser Hinsicht.
Dies ist auch eine gute Quelle.
int 0x80
i386 Linux-Systemaufruf ABI ist dem DOS int 0x21
ABI sehr ähnlich . Fügen Sie eine Rufnummer in ein Register (AH für DOS, EAX für Linux) und andere Argumente in andere Register ein und führen Sie dann eine Software-Interrupt-Anweisung aus. Der Hauptunterschied besteht darin, was Sie mit den Systemaufrufen tun können (Zugriff auf Hardware direkt unter DOS, jedoch nicht unter Linux) und nicht darauf, wie Sie sie aufrufen.
Beispiel für einen minimal ausführbaren Linux-Systemaufruf
Linux richtet den Interrupt-Handler 0x80
so ein, dass er Systemaufrufe implementiert, sodass Userland-Programme mit dem Kernel kommunizieren können.
.data
s:
.ascii "hello world\n"
len = . - s
.text
.global _start
_start:
movl $4, %eax /* write system call number */
movl $1, %ebx /* stdout */
movl $s, %ecx /* the data to print */
movl $len, %edx /* length of the buffer */
int $0x80
movl $1, %eax /* exit system call number */
movl $0, %ebx /* exit status */
int $0x80
Kompilieren und ausführen mit:
as -o main.o main.S
ld -o main.out main.o
./main.out
Ergebnis: Das Programm druckt auf stdout:
hello world
und geht sauber aus.
Sie können Ihre eigenen Interrupt-Handler nicht direkt vom Benutzerland aus festlegen, da Sie nur Ring 3 haben und Linux Sie daran hindert .
GitHub stromaufwärts . Getestet unter Ubuntu 16.04.
Bessere Alternativen
int 0x80
wurde durch bessere Alternativen für Systemaufrufe abgelöst: zuerst sysenter
VDSO.
x86_64 hat eine neue syscall
Anweisung .
Siehe auch: Was ist besser "int 0x80" oder "syscall"?
Minimales 16-Bit-Beispiel
Erfahren Sie zunächst, wie Sie ein minimales Bootloader-Betriebssystem erstellen und auf QEMU und realer Hardware ausführen, wie hier erläutert: https://stackoverflow.com/a/32483545/895245
Jetzt können Sie im 16-Bit-Real-Modus arbeiten:
movw $handler0, 0x00
mov %cs, 0x02
movw $handler1, 0x04
mov %cs, 0x06
int $0
int $1
hlt
handler0:
/* Do 0. */
iret
handler1:
/* Do 1. */
iret
Dies würde in der Reihenfolge tun:
Do 0.
Do 1.
hlt
: Stoppen Sie die AusführungBeachten Sie, wie der Prozessor nach dem ersten Handler an der Adresse 0
und dem zweiten unter der Adresse sucht 4
: Dies ist eine Tabelle von Handlern, die als IVT bezeichnet wird , und jeder Eintrag hat 4 Bytes.
Minimales Beispiel, das einige E / A- Vorgänge ausführt , um Handler sichtbar zu machen.
Beispiel für einen minimalen geschützten Modus
Moderne Betriebssysteme laufen im sogenannten geschützten Modus.
Das Handling hat in diesem Modus mehr Optionen, ist also komplexer, aber der Geist ist der gleiche.
Der Schlüsselschritt ist die Verwendung der LGDT- und LIDT-Anweisungen, die auf die Adresse einer speicherinternen Datenstruktur (Interrupt Descriptor Table) verweisen, die die Handler beschreibt.
int 0x80 ist die Assembler-Anweisung, mit der Systemaufrufe unter Linux auf x86-Prozessoren (dh Intel-kompatiblen Prozessoren) aufgerufen werden.
Der Befehl "int" verursacht einen Interrupt.
Einfache Antwort: Ein Interrupt ist einfach ausgedrückt ein Ereignis, das die CPU unterbricht und sie anweist, eine bestimmte Aufgabe auszuführen.
Detaillierte Antwort :
In der CPU ist eine Tabelle mit Interrupt Service Routines (oder ISRs) gespeichert. Im Real (16-Bit) -Modus wird dies als IVT gespeichert , oder ich nterrupt V ector T können. Das IVT befindet sich normalerweise unter 0x0000:0x0000
(physikalische Adresse 0x00000
) und besteht aus einer Reihe von Segmentversatzadressen, die auf die ISRs verweisen. Das Betriebssystem kann die bereits vorhandenen IVT-Einträge durch eigene ISRs ersetzen.
(Hinweis: Die Größe des IVT ist auf 1024 (0x400) Byte festgelegt.)
Im geschützten (32-Bit) Modus verwendet die CPU eine IDT. Das IDT ist eine Struktur variabler Länge, die aus Deskriptoren (auch als Gates bezeichnet) besteht, die die CPU über die Interrupt-Handler informieren. Die Struktur dieser Deskriptoren ist viel komplexer als die einfachen Segmentversatzeinträge des IVT. hier ist es:
bytes 0, 1: Lower 16 bits of the ISR's address.
bytes 2, 3: A code segment selector (in the GDT/LDT)
byte 4: Zero.
byte 5: A type field consisting of several bitfields.
bit 0: P (Present): 0 for unused interrupts, 1 for used interrupts.*
bits 1, 2: DPL (Descriptor Privilege Level): The privilege level the descriptor (bytes 2, 3) must have.
bit 3: S (Storage Segment): Is 0 for interrupt and trap gates. Otherwise, is one.
bits 4, 5, 6, 7: GateType:
0101: 32 bit task gate
0110: 16-bit interrupt gate
0111: 16-bit trap gate
1110: 32-bit interrupt gate
1111: 32-bit trap gate
* Die IDT kann eine variable Größe haben, muss jedoch sequentiell sein. Wenn Sie also angeben, dass Ihre IDT zwischen 0x00 und 0x50 liegt, muss jeder Interrupt zwischen 0x00 und 0x50 liegen. Das Betriebssystem verwendet nicht unbedingt alle, daher ermöglicht das Present-Bit der CPU, Interrupts, die das Betriebssystem nicht verarbeiten möchte, ordnungsgemäß zu behandeln.
Wenn ein Interrupt auftritt (entweder durch einen externen Trigger (z. B. ein Hardwaregerät) in einem IRQ oder durch die int
Anweisung eines Programms), drückt die CPU EFLAGS, dann CS und dann EIP. (Diese werden automatisch von wiederhergestelltiret
die Interrupt-Rückgabeanweisung .) Das Betriebssystem speichert normalerweise mehr Informationen über den Status der Maschine, behandelt den Interrupt, stellt den Maschinenzustand wieder her und fährt fort.
In vielen * NIX-Betriebssystemen (einschließlich Linux) basieren Systemaufrufe auf Interrupts. Das Programm legt die Argumente für den Systemaufruf in den Registern (EAX, EBX, ECX, EDX usw.) ab und ruft den Interrupt 0x80 auf. Der Kernel hat das IDT bereits so eingestellt, dass es einen Interrupt-Handler auf 0x80 enthält, der aufgerufen wird, wenn er den Interrupt 0x80 empfängt. Der Kernel liest dann die Argumente und ruft entsprechend eine Kernelfunktion auf. Möglicherweise wird eine Rückgabe in EAX / EBX gespeichert. Systemaufrufe wurden weitgehend durch sysenter
und ersetztsysexit
(oder syscall
und ersetztsysret
Anweisungen auf AMD) ersetzt, die einen schnelleren Eintritt in Ring 0 ermöglichen.
Dieser Interrupt kann in einem anderen Betriebssystem eine andere Bedeutung haben. Überprüfen Sie unbedingt die Dokumentation.
eax
wird für die Syscall-Nummer verwendet. asm.sourceforge.net/intro/hello.html
Wie bereits erwähnt, springt die Steuerung zum Interruptvektor 0x80. In der Praxis bedeutet dies (zumindest unter Linux), dass ein Systemaufruf aufgerufen wird. Der genaue Systemaufruf und die Argumente werden durch den Inhalt der Register definiert. Zum Beispiel kann exit () aufgerufen werden, indem% eax auf 1 gefolgt von 'int 0x80' gesetzt wird.
Es weist die CPU an, den Interrupt-Vektor 0x80 zu aktivieren, der unter Linux-Betriebssystemen der Systemaufruf-Interrupt ist, der zum Aufrufen von Systemfunktionen wie open()
für Dateien usw. verwendet wird.