Прерывание в реальном режиме работает, если я использую вызов, не работает (не выполняется), если я использую INT - PullRequest
0 голосов
/ 19 июня 2020

Я пытался добавить системный вызов в свою операционную систему 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, когда оно работает. Когда он терпит неудачу, он ничего не печатает.

Ответы [ 2 ]

2 голосов
/ 29 июня 2020

Ваш исходный вопрос, комментарии и ваш ответ дают намек на то, что, вероятно, является причиной ваших проблем. Вы должны иметь привычку создавать минимально полный проверяемый пример. Фрагменты кода без подробного контекста часто очень трудно диагностировать, и они часто полагаются на детали, которые вы нам не сообщаете.

В своем ответе вы упоминаете это

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

Я могу сделать вывод, что 50h означает, что вы загрузили ядро, начиная с 0x0050: 0x0000 в память чуть выше B IOS 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], она покажет что-то вроде:

enter image description here

The kernel prints K so I know the kernel is running. My interrupt handler prints d. If I attempt to use my interrupt handler (system call) with int 21h I get this:

enter image description here

I believe this is similar to the experience you are seeing based on the available information. The question is why this is happening?


Solution to the Problem

There are a couple of issues but the really involve how you write your interrupt handler to the real mode Interrupt Vector Table (IVT) that starts at 0x0000:0x0000 and ends at 0x0000:0x400. You have this code:

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

The code is equivalent to:

    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

Every memory access in real mode has a default segment register associated with it. If the memory address contains a reference to register BP the segment is assumed to be SS (Stack Segment) otherwise it is DS (Data Segment). In this code CODE_SEG is 0x0050.

The idea is to write CS:IP (CODE_SEG:inthandler) of your interrupt handler into the IVT for interrupt 21h. The offset of Interrupt 21h is at 0x0000:(0x0021 * 4) and the segment is at 0x0000:(0x0021 * 4+2).

Since DS is 0x0050 your code actually wrote your interrupt vector address to to 0x0050:(0x0021 * 4) and 0x0050:(0x0021 * 4+2). This is actually in the middle of your kernel or kernel data somewhere! So when you do int 21h you called the default int 21h routine which is likely just an IRET that does nothing and returns.

You need to write the interrupt vector to segment 0x0000.. This can be done in a variety of ways. One way would be to set the ES (Extra Segment) to 0x0000 and override the memory operand to use ES instead of the default DS. The revised code would look like:

;        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

If you use ES as a scratch segment register and don't care about the contents you can remove the push es and pop es. I have also put a CLI and STI instruction around the update of the IVT. This is a safety precaution in the event that some interrupt occurs that happens to use Interrupt vector 21h before we have completely updated it. This situation is almost non existent in a bootloader but could present itself as an issue had you written the code for DOS.

Alternatively you could have fixed the problem by changing DS to 0x0000 and avoided the segment override:

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

Since you likely want to keep DS set to its original value (0x0050) saving and restoring its value would be required.


Special Notes

You can't reliably do this to call Interrupt 21h:

call [21h*4]

In your code this does a NEAR call in the current segment (CS=0x0050) by getting the offset to jump to from memory offset [ds:21h*4]. The fact it called your interrupt handler was a lucky happenstance. Although it did print d to the display your interrupt handler would likely have never returned. If you printed something else after int 21h it likely would never have appeared because the 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, оно выталкивает из стека старое значение IP и CS , а затем выталкивает старое содержимое регистров FLAGS из стека. Невозможность поместить в стек значение FLAGS не оставит стек в том же состоянии после вызова, что и раньше, потому что прерывание возвращается с IRET, а не RET.

0 голосов
/ 19 июня 2020

По-видимому, я сделал несколько ошибок в коде, и код настройки системного вызова должен быть:

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