【问题标题】:Real mode interrupt works if I use call, doesn't work(won't execute) if I use INT如果我使用调用,实模式中断有效,如果我使用 INT,则无效(不会执行)
【发布时间】:2020-10-09 17:46:35
【问题描述】:

我试图向我的 REAL MODE 操作系统添加系统调用,如果我这样写,它将起作用:

call [21h*4]

但是,如果我尝试使用它来调用它,它就不起作用

int 0x21

这是我用来设置系统调用的代码:

 mov word [21h*4],inthandler
 mov word [21h*4+2],CODE_SEG ;which is 0(incorrect)

我的中断处理程序定义为:

inthandler:
    mov ax,0e64h
    int 0x10
    iret

中断工作时应在显示屏上打印字母d。当它失败时,它不会打印任何东西。

【问题讨论】:

  • 定义“不起作用”。确保inthandler 的偏移量是相对于基于0 的段。如果您的call 确实有效,这似乎表明您将处理程序编写为近程程序,这将由于两个原因不起作用:它应该是远程序并且应该以IRET 结束以弹出标志。发布minimal reproducible example 并学习使用调试器。
  • 定义不起作用:int 处理程序是打印“d”的代码,它不打印任何内容。 int 处理程序代码:mov ax,0e64h;int 0x10;iret
  • @ClementPoon 请写一个minimal reproducible example。我目前的猜测是您的代码在段 0 以外的段中运行,但它可能是其他任何段。

标签: assembly x86-16 interrupt-handling osdev real-mode


【解决方案1】:

您的原始问题、cmets 和您的答案提示您问题的可能原因。您应该养成制作一个最小的完整可验证示例的习惯。没有更多上下文的代码 sn-ps 通常很难诊断,并且通常依赖于您没有告诉我们的细节。

在你的回答中你提到了这一点

mov word [es:21h*4+2],CODE_SEG ;which is NOT 0, should be 50h

我可以推断 50h 意味着您从内存中的 0x0050:0x0000 开始加载内核,就在 BIOS Data Area (BDA) 上方。从您的回答中,我还可以推断出 DS 不为零,因为您必须用 ES 覆盖,您在代码注释中说它等于 0。您的 DS 寄存器可能设置为 0x0050(以及 CS)。

一个最小的完整示例如下所示:

boot.asm

org 0x7c00

    xor ax, ax
    mov ds, ax                     ; DS=ES=0
    mov es, ax
    mov ss, ax                     ; SS:SP starts from top of first 64KiB in memory
    mov sp, ax                     ;     and grows down

    mov ax, 0x0201                 ; AH=2 BIOS disk read, AL=# sectors to read
    mov cx, 0x0002                 ; CH=cylinder 0, CL=sector number 2 
    mov dh, 0                      ; DH=head 0
    mov bx, 0x500                  ; ES:BX(0x0000:0x0500) = memory to read to
    int 0x13                       ; Read 1 sector after bootloader to 0x0000:0x0500
    ; Insert error checking code here. Left out retries etc for brevity 

    jmp 0x0050:0x0000              ; Start executing kernel at 0x0050:0x0000 
                                ;     Sets CS=0x0050, IP=0x0000

; Disk signature
TIMES 510-($-$$) db 0x00
dw 0xaa55

kernel.asm

CODE_SEG EQU 0x0050

org 0x0000                     ; Kernel will be run from 0x0050:0x0000

kernel:
    ; CS=0x0050 at this point because of FAR JMP that got us here
    mov ax, CODE_SEG
    mov ds, ax                     ; DS=ES=0x0050
    mov es, ax
    mov ss, ax                     ; SS:SP=0x0050:0x0000 wraps to top of 64KiB on 1st push
    xor sp, sp                     ;     and grows down

    mov ax, 0x0e << 8 | 'K'        ; AH=0x0e BIOS TTY print char service,
                                   ;     AL=char to print `K`
    mov bh, 0                      ; Ensure we are using text page 0
    int 0x10                       ; Print 'K' on the display

    mov word [21h*4], inthandler   ; Set CS:IP of int 21 handler to CODE_SEG:inthandler
    mov word [21h*4+2],CODE_SEG
 ;   call [21h*4]                  ; This works by printing 'd' to the display
    int 21h                        ; This fails. Doesn't print anything to display
    
.hltloop                           ; Infinite loop to stop kernel
    hlt
    jmp .hltloop

; Int 21h interrupt handler
inthandler:
    mov ax, 0x0e << 8 | 'd'        ; AH=0x0e BIOS TTY print char service, AL=char to print `K`
    int 0x10                       ; Print 'K' to display
    iret                           ; Return from interrupt

使用引导加载程序和内核构建磁盘映像:

#!/bin/sh

nasm -f bin boot.asm -o boot.bin
nasm -f bin kernel.asm -o kernel.bin

# Make 1.44MiB floppy disk image with bootloader followed by kernel    
dd if=/dev/zero of=floppy.img bs=1024 count=1440
dd if=boot.bin of=floppy.img conv=notrunc
dd if=kernel.bin of=floppy.img conv=notrunc seek=1

这可以通过 QEMU 使用以下命令进行测试:

qemu-system-i386 -fda floppy.img

如果您使用call [21h*4] 运行该版本,它将显示如下内容:

内核打印K,所以我知道内核正在运行。我的中断处理程序打印d。如果我尝试将我的中断处理程序(系统调用)与int 21h 一起使用,我会得到:

我相信这与您根据可用信息看到的体验相似。问题是为什么会这样?


问题的解决方案

有几个问题,但真正涉及如何将中断处理程序写入从 0x0000:0x0000 开始到 0x0000:0x400 结束的实模式中断向量表 (IVT)。你有这个代码:

    mov word [21h*4], inthandler   ; Set CS:IP of int 21 handler to CODE_SEG:inthandler
    mov word [21h*4+2],CODE_SEG

代码相当于:

    mov word [ds:21h*4], inthandler   ; Set CS:IP of int 21 handler to CODE_SEG:inthandler
    mov word [ds:21h*4+2],CODE_SEG

实模式下的每个内存访问都有一个与之关联的默认段寄存器。如果内存地址包含对寄存器BP 的引用,则假定该段为SS(堆栈段),否则为DS(数据段)。在这段代码中CODE_SEG 是 0x0050。

这个想法是将中断处理程序的 CS:IP (CODE_SEG:inthandler) 写入 IVT 以用于中断 21h。中断 21h 的偏移量在 0x0000:(0x0021 * 4),段在 0x0000:(0x0021 * 4+2)。

由于 DS 为 0x0050,您的代码实际上将您的中断向量地址写入 0x0050:(0x0021 * 4) 和 0x0050:(0x0021 * 4+2)。这实际上是在您的内核或内核数据中间的某个地方!因此,当您执行 int 21h 时,您调用了默认的 int 21h 例程,这可能只是一个不执行任何操作并返回的 IRET

您需要将中断向量写入段 0x0000.. 这可以通过多种方式完成。一种方法是将 ES(额外段)设置为 0x0000 并覆盖内存操作数以使用 ES 而不是默认的 DS。修改后的代码如下:

;        push es                        ; Save previous value of ES
        xor ax, ax
        mov es, ax                     ; ES=0
        cli                            ; Make sure no interrupt occurs while we update IVT
        mov word [es:21h*4], inthandler; Set CS:IP of int 21 handler to CODE_SEG:inthandler
        mov word [es:21h*4+2],CODE_SEG
        sti                            ; Re-enable interrupts
;        pop es                         ; Restore original value of ES

如果您使用 ES 作为暂存段寄存器并且不关心内容,则可以删除 push espop es。我还针对 IVT 的更新添加了 CLISTI 指令。这是一种安全预防措施,以防在我们完全更新之前发生某些使用中断向量 21h 的中断。这种情况在引导加载程序中几乎不存在,但如果您为 DOS 编写代码,则可能会出现问题。

或者,您可以通过将 DS 更改为 0x0000 并避免段覆盖来解决问题:

push ds                        ; Save previous value of DS
xor ax, ax
mov ds, ax                     ; DS=0
cli                            ; Make sure no interrupt occurs while we update IVT
mov word [21h*4], inthandler   ; Set CS:IP of int 21 handler to CODE_SEG:inthandler
mov word [21h*4+2],CODE_SEG
sti                            ; Re-enable interrupts
pop ds                         ; Restore original value of DS

由于您可能希望将 DS 设置为其原始值 (0x0050),因此需要保存和恢复其值。


特别说明

您不能可靠地执行此操作来调用中断 21h:

call [21h*4]

在您的代码中,通过获取要从内存偏移量[ds:21h*4] 跳转到的偏移量,在当前段 (CS=0x0050) 中执行 NEAR 调用。它调用您的中断处理程序的事实是一个幸运的偶然事件。尽管它确实将d 打印到显示器上,但您的中断处理程序可能永远不会返回。如果您在int 21h 之后打印了其他内容,它可能永远不会出现,因为IRET 回到了内存中的错误位置。

为了使用CALL 正确模拟中断调用,您必须执行以下操作:

xor ax, ax
mov es, ax                     ; ES=0
pushf                          ; An interrupt pushes current FLAGS on the stack so we need
                               ;     to do something similar
call far [es:21h*4]            ; We need to do a FAR CALL (not a NEAR call)

我们需要执行 FAR CALL 而不是默认的 NEAR CALL,因此我们需要在内存操作数上使用 FAR 属性。当IRET 返回时,它会将旧的 IPCS 值从堆栈中弹出,然后将旧的 FLAGS 寄存器内容从堆栈中弹出.未能在堆栈上放置 FLAGS 值不会使堆栈在调用后保持与之前相同的状态,因为中断返回时返回 IRET 而不是 RET

【讨论】:

  • 寄存器名称es 代表我相信的“额外段”。
【解决方案2】:

显然我在代码中犯了一些错误,系统调用设置代码应该是:

;es=0
mov word [es:21h*4],inthandler
 mov word [es:21h*4+2],CODE_SEG ;which is NOT 0, should be 50h

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-25
    • 2016-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多