有人可以解释以下汇编代码的作用吗?
int 0x80
int
表示中断,数字0x80
是中断号。中断将程序流传输给处理该中断的任何人,在本例中为中断 0x80
。在Linux中,0x80
中断处理程序是内核,用于其他程序对内核进行系统调用。
通过检查寄存器 %eax
中的值(AT&T 语法和 Intel 语法中的 EAX),通知内核程序想要进行哪个系统调用。每个系统调用对其他寄存器的使用都有不同的要求。例如,%eax
中的 1
值表示 exit()
的系统调用,%ebx
中的值保存 exit()
的状态码值。
它将控制传递给中断向量 0x80
请参阅http://en.wikipedia.org/wiki/Interrupt_vector
在 Linux 上,查看 this:它用于处理 system_call
。当然,在另一个操作系统上,这可能意味着完全不同的东西。
请记住 0x80
= 80h
= 128
您可以看到here,INT
只是 x86 指令集中存在的众多指令之一(实际上是它的汇编语言表示(或者我应该说是“助记符”))。您还可以在英特尔自己的手册 here 中找到有关此指令的更多信息。
从 PDF 中总结:
INT n/INTO/INT 3—调用中断过程 INT n 指令生成对目标操作数指定的中断或异常处理程序的调用。目标操作数指定一个从 0 到 255 的向量,编码为 8 位无符号中间值。 INT n 指令是执行软件生成的中断处理程序调用的通用助记符。
如您所见, 0x80 是您问题中的目标操作数。此时 CPU 知道它应该执行一些驻留在内核中的代码,但是什么代码呢?这是由 Linux 中的中断向量决定的。
最有用的 DOS 软件中断之一是中断 0x21。通过使用寄存器中的不同参数(主要是 ah 和 al)调用它,您可以访问各种 IO 操作、字符串输出等。
大多数 Unix 系统和衍生系统不使用软件中断,除了用于进行系统调用的中断 0x80。这是通过将对应于内核函数的 32 位值输入到处理器的 EAX 寄存器中,然后执行 INT 0x80 来实现的。
请看一下中断处理程序表中其他可用值的显示位置:
https://i.stack.imgur.com/hIg8n.png
如您所见,该表指向 CPU 执行系统调用。您可以找到 Linux 系统调用表 here。
因此,通过将值 0x1 移动到 EAX 寄存器并在程序中调用 INT 0x80,您可以使进程执行内核中的代码,这将停止(退出)当前正在运行的进程(在 Linux,x86 Intel CPU 上)。
不得将硬件中断与软件中断混淆。 Here 在这方面是一个很好的答案。
This 也是很好的来源。
int 0x80
i386 Linux 系统调用 ABI 与 DOS int 0x21
ABI 极为相似。将调用号放入寄存器(用于 DOS 的 AH,用于 Linux 的 EAX),并将其他参数放入其他寄存器中,然后运行软件中断指令。主要区别在于系统调用允许您做什么(直接在 DOS 中访问硬件,而不是在 Linux 中访问硬件),而不在于您调用它们的方式。
/usr/include/x86_64-linux-gnu/asm/unistd_64.h
看到可用的系统调用
最小可运行 Linux 系统调用示例
Linux 为 0x80
设置了中断处理程序,以便它实现系统调用,这是用户级程序与内核通信的一种方式。
.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
编译并运行:
as -o main.o main.S
ld -o main.out main.o
./main.out
结果:程序打印到标准输出:
hello world
并干净地退出。
您不能直接从用户区设置自己的中断处理程序,因为您只有 ring 3 and Linux prevents you from doing so。
GitHub upstream。在 Ubuntu 16.04 上测试。
更好的选择
int 0x80
已被更好的系统调用替代方案所取代:首先是 sysenter
,然后是 VDSO。
x86_64 有 a new syscall
instruction。
另请参阅:What is better "int 0x80" or "syscall"?
最小 16 位示例
首先了解如何创建一个最小的引导加载程序操作系统并在 QEMU 和真实硬件上运行它,正如我在此处所解释的:https://stackoverflow.com/a/32483545/895245
现在您可以在 16 位实模式下运行:
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
这将按顺序进行:
做0。
做1。
hlt:停止执行
请注意处理器如何在地址 0
处查找第一个处理程序,而在 4
处查找第二个处理程序:这是一个称为 IVT 的处理程序表,每个条目有 4 个字节。
Minimal example that does some IO 使处理程序可见。
最小保护模式示例
现代操作系统以所谓的保护模式运行。
这种模式的处理方式比较多,所以比较复杂,但是精神是一样的。
关键步骤是使用 LGDT 和 LIDT 指令,它们指向描述处理程序的内存数据结构(中断描述符表)的地址。
“int”指令导致中断。
什么是中断?
简单的回答:简单地说,中断是中断 CPU 并告诉它运行特定任务的事件。
详细答案:
CPU 有一个存储在内存中的中断服务程序(或 ISR)表。在实数(16 位)模式下,这存储为 IVT,或 I中断 Vector T有能力的。 IVT 通常位于 0x0000:0x0000
(物理地址 0x00000
),它是一系列指向 ISR 的段偏移地址。操作系统可以用它自己的 ISR 替换预先存在的 IVT 条目。
(注意:IVT 的大小固定为 1024 (0x400) 字节。)
在受保护(32 位)模式下,CPU 使用 IDT。 IDT 是一个可变长度结构,由描述符(也称为门)组成,它告诉 CPU 有关中断处理程序的信息。这些描述符的结构比 IVT 的简单段偏移条目复杂得多。这里是:
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
*IDT 可以是可变大小的,但它必须是连续的,即如果你声明你的IDT 是从0x00 到0x50,你必须有从0x00 到0x50 的每个中断。操作系统不一定会使用所有这些中断,因此 Present 位允许 CPU 正确处理操作系统不打算处理的中断。
当中断发生时(通过 IRQ 中的外部触发器(例如硬件设备),或通过程序中的 int
指令),CPU 会推送 EFLAGS,然后是 CS,然后是 EIP。 (这些由中断返回指令 iret
自动恢复。)操作系统通常存储有关机器状态的更多信息,处理中断,恢复机器状态,然后继续。
在许多 *NIX 操作系统(包括 Linux)中,系统调用是基于中断的。程序将系统调用的参数放入寄存器(EAX、EBX、ECX、EDX 等)中,并调用中断 0x80。内核已经将 IDT 设置为在 0x80 上包含一个中断处理程序,当它接收到中断 0x80 时会调用该处理程序。然后内核读取参数并相应地调用内核函数。它可以在 EAX/EBX 中存储回报。系统调用在很大程度上已被 sysenter
和 sysexit
(或 AMD 上的 syscall
和 sysret
)指令所取代,它们允许更快地进入 ring 0。
这个中断在不同的操作系统中可能有不同的含义。请务必检查其文档。
eax
用于系统调用号。 asm.sourceforge.net/intro/hello.html
如前所述,它会导致控制跳转到中断向量 0x80。实际上,这意味着(至少在 Linux 下)是调用系统调用;确切的系统调用和参数由寄存器的内容定义。例如,可以通过将 %eax 设置为 1 后跟“int 0x80”来调用 exit()。
它告诉 cpu 激活中断向量 0x80,它在 Linux 操作系统上是系统调用中断,用于调用系统函数,如文件的 open()
等。
int 只不过是一个中断,即处理器将暂停其当前执行。
0x80 只不过是系统调用或内核调用。即系统功能将被执行。
具体来说 0x80 代表 rt_sigtimedwait/init_module/restart_sys 它因架构而异。
有关详细信息,请参阅 https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md
int 0x80
是内核中函数的一种特殊call
(由eax
选择)。