Bas*_*igo 1 x86 assembly interrupt x86-16
I would like a practical example of the cli and sti instructions in x86 16-bit assembly, i.e. an example in code written in this language which allows me to know what these instructions are for by practice and to go further than theory.
I know the documentation says that cli disables the interrupt flag and sti enables it and the interrupt flag does not affect
not handling non-maskable interrupts (NMI) or software interrupts generated by the int instruction.
In the tutorial I follow I have this code:
    mov ax, 0x8000
    cli
    mov ss, ax
    mov sp, 0xF000
    sti
My tests make me say that cli and sti are useless in the example given in the course, after doing several tests I was able to verify that the results will always be the same whether I put cli and sti or remove these instructions.
The explanation of the usefulness of cli and sti by the speakers on the different topics for the example given in the course is purely theoretical. That is to say that you have to put cli and sti for safety to avoid bugs / crashes. A speaker on discord says there's a one in a million chance that something goes wrong when I initialize the segments and the stack offset.
Which means he could never verify his theoretical explanation by himself, he just accepts the theory, no curiosity to go further and experiment by itself, impossible to verify by practice since there is a one in millions chance of having a problem.
在各种文档/网站上,严格没有其他实际示例真正演示它的用途cli、sti用途以及用途,只是复制并粘贴了没有示例代码的文档,即将cli中断标志设置为 0,并将sti其设置为 1。禁用的硬件中断将被忽略。零使用示例,只是理论句子,实践中没有任何东西可以测试这种事情,法语文档上确实有一个示例,但该示例也比我真正理解的教程示例毫无用处。也就是说,一个初始化段并在代码行之前和之后放置 和 的示例cli,sti如果我们删除cli,则sti无论如何结果都是相同的(也许我们有百万分之一的机会出现问题)如果我们删除cli和sti,就会发生,这很好,它让我永远不会在实践中检查理论)。
Discord 上的另一位发言者告诉我,他对这一切进行了一些实验,他用汇编编码了一段时间,根据他的经验,他理解为什么你必须把cliand放在sti一起,因为否则它会导致问题,所以你必须把它和就这样。当我要求他给我一个实际的例子时(这应该是他练习过的例子),他没有这样做,因为他不在家,但另一方面又给了我一本理论本来向我解释它是如何有用的,所以显然我们可以详细解释它有多么有用,但永远不会用 x86 16 位汇编中的实际示例来演示实用性。
我指出我不熟悉硬件中断。我只测试了可以调用的软件中断int。
我处于内核模式,我想要一个导致代码出现硬件中断问题的实际示例,然后是另一个cli可以sti解决问题的示例。
当然,重点cli/sti是管理硬件中断的处理,因此您需要了解硬件中断的一般工作原理。
这里简单概述一下:连接到 CPU 的硬件设备可以触发硬件中断(对于 8086,通过将高电压连接到 CPU 芯片上的 INTR 引脚,并使用其他引脚来指示应该使用哪个中断向量)称为)。当发生这种情况时,如果中断标志被置位,则CPU完成当前正在执行的指令,然后将CS、IP和FLAGS压入堆栈,并跳转到中断向量表的相应条目中指定的地址,这是内存的低 1024 字节 (0000:0000-0000:0400)。程序员应该事先设置此条目以指向要作为响应运行的代码块(中断处理程序)。中断处理程序将执行任何必要的操作来处理硬件中断,然后执行IRET以返回到任何被中断的代码。引起硬件中断的设备示例有:键盘上的按键、串行端口上的字节到达、定时器中断(MS-DOS 设置外部定时器以 18.2 Hz,即每 55 ms 生成一个中断)。
如果未设置中断标志,则不会发生任何事情,但当标志最终再次设置时,将调用中断处理程序。
因此,只要您不希望发生中断,就可以清除中断标志。这通常是因为您正在使用当前代码和中断处理程序之间共享的某些资源,因此如果此时运行中断处理程序,则会发生冲突。
例如,让我们考虑定时器中断。一个简单的处理程序可能什么都不做,只是增加一个计数器,以便执行的主线程可以知道已经过去了多少时间。(8086 没有任何其他内置时钟硬件。)如果 16 位计数器就足够了,您可以简单地拥有:
ticks DW 0
handler:
    inc word ptr [ticks]
    iret
main_code:
    mov ax, [ticks] ; now ax contains the number of ticks
但在 18.2 Hz 时,我们非常接近每小时 65536 次滴答(我认为这就是选择数字 18.2 的原因),因此计数器大约每小时都会溢出。如果您需要跟踪比这更长的时间间隔,那就不好了,所以我们应该使用 32 位计数器。由于 x86-16 没有 32 位算术指令,因此我们必须使用一ADD/ADC对。我们的代码可能如下所示:
ticks DD 0
handler:
    add word ptr [ticks], 1
    adc word ptr [ticks+2], 0
    iret
main_code:
    mov ax, [ticks]
    ;;; BUG what if interrupt occurs here ???
    mov dx, [ticks+2]
    ; now dx:ax contains the 32-bit number of ticks
但这段代码有一个错误。如果定时器中断偶然发生在标记的指令之间BUG,则主代码将得到ticks不同步的低字和高字。例如假设 的ticks值为0x1234ffff。主代码将低位字 加载0xffff到 ax 中。然后定时器中断发生并递增ticks,所以现在是这样0x12350000。中断处理程序返回,主代码返回mov dx, [ticks+2]并获取值0x1235。所以现在主代码已经加载了 value 0x1235ffff,这是非常错误的:它比实际时间晚了整整一个小时。
我们可以通过使用cli/sti禁用中断来解决此问题,以便在标记为 的站点上不会发生中断BUG。更正后的代码如下所示:
main_code:
    cli
    mov ax, [ticks]
    mov dx, [ticks+2]
    sti
在 32 位计数器的特定情况下,恰好有其他方法可以在不禁用中断的情况下解决此问题,但您已经明白了。您可以想象处理程序和主代码可能需要使用的一些更复杂的数据结构:I/O 缓冲区、一些包含刚刚发生的 I/O 事件信息的较大结构、链表等。
CPU 的寄存器也是共享资源,例如您注意到的 SS:SP 示例。假设堆栈当前位于 ,1234:5678并且主代码想要将其切换到2222:4444。你会想到这样做:
switch_stack:
    mov ax, 0x2222
    mov ss, ax
    ;;; BUG: what if interrupt occurs here?
    mov sp, 0x4444
如果在第 行发生中断BUG,则 SS:SP 的值将为2222:5678,这是 CPU 在跳转到处理程序之前推送 CS/IP/FLAGS 值的位置。这真的很糟糕,因为这不是旧堆栈或新堆栈的正确位置。该地址可能有重要的数据,CPU 现在正在覆盖这些数据,因此我们现在将面临一个难以重现的内存损坏错误。
所以我们同样会考虑修复它
switch_stack:
    mov ax, 0x2222
    cli
    mov ss, ax
    ;;; interrupt can't occur here!
    mov sp, 0x4444
    sti
现在看来,这其实是一个特例。由于在这种情况下忘记禁用中断会特别令人讨厌,因此 8086 设计者决定为程序员提供一点帮助。该mov ss, reg指令有一个非常特殊的功能,它会自动禁用一条指令的中断。因此,事实上,如果您在编写代码mov ss, ax后立即添加mov sp, 0x2222,那么在此期间就不会发生中断,并且如果没有 ,代码实际上是安全的cli/sti。
但我要再次强调,这是一个独特的特例。我相信它是唯一mov ss, reg具有pop ss这样的功能的,所以像 32 位滴答计数器这样的例子确实需要cli/sti。事实上,如果您颠倒了两条指令并mov sp, 0x2222随后进行编码mov ss, ax(表面上看起来同样好),您将再次遇到错误,并且可以使用指向 的堆栈来调用中断处理程序1234:2222。另外,正如 @ecm 在评论中指出的那样,一些早期的 8086/8088 芯片存在硬件错误(?),其中“禁用一条指令的中断”功能不起作用,因此在此类芯片上,您还必须使用cli/sti. (或者也许这个功能直到后来才真正成为规范的一部分?)
386 添加了一条lss指令,可以在一条指令中同时加载堆栈段和堆栈指针,这是解决此问题的更可靠的方法。在这种情况下它也更重要,因为在虚拟 8086 模式下,cli/sti不会直接执行,而是会陷入操作系统,这非常慢,最好尽可能避免。
您认为,也许这种情况发生的可能性很低,我们实际上不需要担心。让我们看一下 32 位定时器示例,并想象一个具有“闹钟”功能的应用程序。在执行其他工作时,它会定期检查滴答计数器(假设每秒检查 100 次),以查看指定时间是否已过,如果已过,则它会执行某些操作来提醒用户。如果您省略cli/sti,那么如果那里发生中断且低字等于0xffff(每小时发生一次),它会认为时间比实际时间晚一小时,因此也可能发出最多一小时的警报很快。(如果你想更戏剧化,可以将“发出警报”替换为“启动危险机械”、“发射导弹”等)
8086 上的指令mov ax, mem需要 10 个时钟周期,因此当我们容易受到攻击时,每秒有 1000 个时钟周期。最初的 IBM PC 的时钟频率为 4.7 MHz,因此每小时触发 bug 的概率约为 1/4700。如果您将应用程序交付给 50,000 个用户,并且每个用户每天使用该应用程序 8 小时,那么通过一点数学计算,您可以算出在发布的第一周内您预计会收到 425 条有关此错误的投诉。你的老板会非常生气的。
请记住,我们回到了 20 世纪 80 年代中期,还没有互联网,因此您必须向 50,000 名客户中的每一位邮寄一张带有补丁的软盘。加上几美元的邮费,这个错误已经让公司损失了大约 10 万美元。相比之下,1984 年作为入门级程序员的年薪约为 20,000 美元。您认为保住工作的可能性有多大?
| 归档时间: | 
 | 
| 查看次数: | 1134 次 | 
| 最近记录: |