Assembly 如果我使用call,实模式中断工作,不';如果使用INT,则无法工作(不会执行)

Assembly 如果我使用call,实模式中断工作,不';如果使用INT,则无法工作(不会执行),assembly,x86-16,interrupt-handling,osdev,real-mode,Assembly,X86 16,Interrupt Handling,Osdev,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

我试图在我的实模式操作系统中添加一个系统调用,如果我写下以下内容,它将起作用:

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
。当它失败时,它不会打印任何内容。

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

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

你的原始问题、评论和回答暗示了问题的可能原因。您应该养成生成最小完整可验证示例的习惯。没有更大上下文的代码片段通常很难诊断,并且通常依赖于您没有告诉我们的细节

你在答复中提到了这一点

我可以推断,50小时意味着您从0x0050:0x0000开始在内存中加载内核,该内存正好位于。从您的回答中,我还可以推断DS不是零,因为您必须用ES覆盖,您在代码注释中说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
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
内核.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
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
这可以通过QEMU使用以下命令进行测试:

qemu-system-i386 -fda floppy.img
如果您使用调用[21h*4]运行版本,它将显示如下内容:

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)

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

我相信这与您根据现有信息看到的体验类似。问题是为什么会发生这种情况


问题的解决办法 有几个问题,但真正的问题涉及如何将中断处理程序写入从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\u SEG
为0x0050

其思想是将中断处理程序的CS:IP(CODE_SEG:inthandler)写入中断21h的IVT中。中断21h的偏移量为0x0000:(0x0021*4),该段为0x0000:(0x0021*4+2)

由于DS是0x0050,您的代码实际上将中断向量地址写入0x0050:(0x0021*4)和0x0050:(0x0021*4+2)。这实际上是在你的内核或内核数据的某个地方!因此,当您执行
int21h
时,您调用了默认的
int21h
例程,它可能只是一个不执行任何操作并返回的
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作为暂存段寄存器,而不关心内容,则可以删除
推送ES
弹出ES
。我还对IVT的更新进行了
CLI
STI
说明。这是一种安全预防措施,以防在我们完全更新中断向量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]
在您的代码中,它通过从内存偏移量获取要跳转到的偏移量来在当前段(CS=0x0050)中执行近调用
[ds:21h*4]
。它称为中断处理程序的事实是一个幸运的巧合。虽然它确实将
d
打印到显示器上,但中断处理程序可能永远不会返回。如果在
int 21h
之后打印了其他内容,它可能永远不会出现,因为返回到内存中的错误位置

为了使用
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
属性。当
IRET
返回时,它会将IP和CS的旧值从堆栈中弹出,然后将旧标志寄存器内容从堆栈中弹出。未能在堆栈上放置标志值不会使堆栈在调用后保持与之前相同的状态,因为中断返回时带有
IRET
,而不是
RET

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