【发布时间】:2020-02-25 15:36:23
【问题描述】:
我想在 NASM 中有一个调用不是硬编码中断而是 int 的中断。在寄存器中。 举个例子:
mov al, 0x10
int al ; you can't do this for some reason
因此,如果我将 0x10 存储在寄存器 al 中,那么我可以根据该寄存器中的内容调用中断。
有什么办法可以做到吗?
【问题讨论】:
我想在 NASM 中有一个调用不是硬编码中断而是 int 的中断。在寄存器中。 举个例子:
mov al, 0x10
int al ; you can't do this for some reason
因此,如果我将 0x10 存储在寄存器 al 中,那么我可以根据该寄存器中的内容调用中断。
有什么办法可以做到吗?
【问题讨论】:
有什么办法可以做到吗?
在没有自修改代码的 16 位“实模式”中:
大多数 DOS 的 C 编译器都提供了一个库函数,允许执行 int al 的等效操作。
这通过以下方式起作用:
在实模式下,int 指令等于 pushf 后跟 call 到中断处理程序。
然而,“远”call 只不过是将下一条指令的“远”地址(cs 和ip)压入堆栈并执行跳转。 (“near”调用仅推送ip。)retf 将从堆栈中弹出ip 和cs 并跳转到该地址。
中断处理程序的地址存储在地址0:(4*n)。
所以要进入一个中断,首先执行如下代码:
pushf
push cs # Leave this line out if "call helper" is a "far" call
call helper
当进入函数helper时,栈是这样的:
Address (IP) of the instruction after "call helper"
Segment (CS) of the program
Flags
...
这三个元素在int 指令之后位于堆栈中。
程序helper 如下所示。
helper:
# Calculate BX = 4*AX
# (This can be done with less instructions on a modern CPU)
mov bl,al
mov bh,0
add bx,bx
add bx,bx
# Set DS to 0
xor ax,ax
mov ds,ax
# Push the segment part of the interrupt handler address
# to the stack
push word [bx+4]
# Push the offset part
push word [bx]
# Load all registers with the desired values
# TODO: Ensure all registers have the correct values
# Enter the interrupt
retf
在retf 之前,堆栈将如下所示:
Address (IP) of the interrupt routine
Segment (CS) of the interrupt routine
Address (IP) of the instruction after "call helper"
Segment (CS) of the program
Flags
...
retf 指令的行为方式与前两个字已被“远”call 指令压入一样:它将从堆栈中删除前两个字并跳转到这些字描述的地址两个词 - 这意味着:进入中断处理程序。
在中断处理程序结束时,最后3个字将从堆栈中弹出,并在call helper之后的指令处继续执行。
在带有自修改代码的 16 位“实模式”中:
这很简单:
# AL contains the interrupt number
# Modify the "int" instruction, so "int 42" becomes
# "int 31" if the value of AL is 31.
mov cs:[theIntInstruction+1], al
# Load all registers with the desired values
# TODO: Ensure all registers have the correct values
# Now perform the "int" instruction
theIntInstruction:
int 42
自修改代码可能会产生负面影响。这意味着可能会出现问题...
在(16 位或 32 位)“保护模式”下:
根据内存保护设置,您有机会写入“可执行”内存。在这种情况下,您可能会使用自修改代码。
如果您无法使用自修改代码,则无法执行 int al 的等效操作,除非您想执行以下操作:
performInt0:
int 0
ret
performInt1:
int 1
ret
performInt2:
int 2
ret
...
performInt255:
int 255
ret
...然后对所需标签执行call。
这当然总是可能的。
【讨论】:
jmp far [bx]?
ds、es 和bx 具有某些值,jmp far [bx] 也是可能的。我发布的变体允许所有寄存器具有任何值 - 使用 iret 而不是 retf 甚至可以加载具有所需值的标志。
一般情况下没有好的/简单的选项,不要这样做。你需要自我修改代码,或者每个选项的跳转表,或者条件链如果只有几个可能的值,则分支。
(但是,如果您只需要它在实模式下工作,请参阅 Martin 的回答;使用 pushf 和 call far [ivt_entry] 模拟 int)
如果你想创建一个包装函数,不要;使它成为一个宏,以便它可以与中断号作为常量内联。或者为您要使用的每个中断号制作单独的包装器。
【讨论】:
int的编码。第一个字节是操作码,第二个是中断号。覆盖它以更改您调用的中断。
pushf ; call far [idt-entry] 不是一个很好/简单的选择吗? (类似于马丁的回答。)
int,因为这只能在我通常不使用的实模式下实现。