簡體   English   中英

在 x86 實模式匯編中編寫中斷處理程序

[英]Writing interrupt handler in x86 real mode assembly

我正在使用匯編學習 x86 實模式下的中斷處理。 我正在遵循從這里獲取的以下示例:

.include "common.h"
BEGIN
    CLEAR
    /* Set address of the handler for interrupt 0. */
    movw $handler, 0x00
    /* Set code segment of the handler for interrupt 0. */
    mov %cs, 0x02
    int $0
    PUTC $'b
    hlt
handler:
    PUTC $'a
    iret

但是當我編譯並運行上面的代碼時,

$ as --32 -o main.o main.S -g
$ ld -T linker.ld -o main.img --oformat binary -m elf_i386 -nostdlib main.o
$ qemu-system-i386 -hda main.img

我收到以下錯誤:

qemu-system-i386: Trying to execute code outside RAM or ROM at 0xf00fff53
This usually means one of the following happened:

(1) You told QEMU to execute a kernel for the wrong machine type, and it crashed on startup (eg trying to run a raspberry pi kernel on a versatilepb QEMU machine)
(2) You didn't give QEMU a kernel or BIOS filename at all, and QEMU executed a ROM full of no-op instructions until it fell off the end
(3) Your guest kernel has a bug and crashed by jumping off into nowhere

This is almost always one of the first two, so check your command line and that you are using the right type of kernel for this machine.
If you think option (3) is likely then you can try debugging your guest with the -d debug options; in particular -d guest_errors will cause the log to include a dump of the guest register state at this point.

Execution cannot continue; stopping here.

我在這里缺少什么? 為什么需要mov %cs, 0x02或者它到底在做什么?

我嘗試在 gdb 下調試這個,當我逐行逐行執行時,我沒有在 gdb 下遇到這個錯誤,這很奇怪,仍在檢查中。

編輯

這是BEGIN的定義方式:

.macro BEGIN
    .local after_locals
    .code16
    cli
    /* Set %cs to 0. */
    ljmp $0, $1f
    1:
    xor %ax, %ax
    /* We must zero %ds for any data access. */
    mov %ax, %ds
    mov %ax, %es
    mov %ax, %fs
    mov %ax, %gs
    mov %ax, %bp
    /* Automatically disables interrupts until the end of the next instruction. */
    mov %ax, %ss
    /* We should set SP because BIOS calls may depend on that. TODO confirm. */
    mov %bp, %sp
    /* Store the initial dl to load stage 2 later on. */
    mov %dl, initial_dl
    jmp after_locals
    initial_dl: .byte 0
after_locals:
.endm

我只能假設您在本教程中最初介紹的代碼中引入了錯誤。 例如,你說你組裝:

as --32 -o main.o main.S -g

如果包含本教程中出現的common.h ,則此命令應該會失敗並顯示如下內容:

 common.h: Assembler messages: common.h:399: Warning: stray `\\' common.h:400: Warning: stray `\\' common.h:401: Warning: stray `\\' common.h:421: Warning: stray `\\' common.h:422: Warning: stray `\\' common.h:423: Warning: stray `\\' common.h:424: Warning: stray `\\' common.h:425: Warning: stray `\\'

發生這些錯誤是因為編寫教程代碼的方式要求在匯編代碼上運行C預處理器。 最簡單的方法是使用 GCC 通過將代碼傳遞給后端 AS 匯編器來匯編代碼:

gcc -c -g -m32 -o main.o main.S

GCC 將采用任何帶有.S擴展名的文件擴展名,並在通過 AS 匯編程序之前在.S上運行 C 預處理器。 作為替代可以用直接運行C預處理器cpp然后運行as分開。

要使用 GCC 構建main.img ,您可以使用如下命令:

gcc -c -g -m32 -o main.o main.S
ld -T linker.ld -o main.img --oformat binary -m elf_i386 -nostdlib main.o

要使用C預處理器構建它,您可以執行以下操作:

cpp main.S > main.s
as -g --32 -o main.o main.s
ld -T linker.ld -o main.img --oformat binary -m elf_i386 -nostdlib main.o

使用 QEMU 運行時,代碼按預期工作:

qemu-system-i386 -hda main.img

輸出應類似於:

在此處輸入圖片說明

關於 CS 和實模式 IVT 的問題

您詢問了此代碼:

/* Set address of the handler for interrupt 0. */
movw $handler, 0x00
/* Set code segment of the handler for interrupt 0. */
mov %cs, 0x02
int $0

在實模式下,默認的 IBM-PC 中斷向量表 (IVT) 是內存的前 1024 個字節,從物理地址 0x00000 (0x0000:0x0000) 到 0x00400 (0x0000:0x0400)。 IVT 中的每個條目為 4 個字節(每個條目 4 個字節*256 個中斷 = 1024 個字節)。 中斷向量所在的指令指針 (IP)(也稱為偏移量)的一個字(2 個字節)后跟一個包含該段的字(2 個字節)。

中斷 0 從 IVT 最底部的內存 0x000000 (0x0000:0x0000) 開始。 中斷 1 從 0x00004 (0x0000:0x0004) 開始……中斷 255 從 0x003FC (0x0000:0x03FC) 開始。

指令:

/* Set address of the handler for interrupt 0. */
movw $handler, 0x00

handler的 16 位偏移量移動到內存地址 DS:0x0000 。 對於 16 位尋址, DS始終是隱含的段,除非寄存器BP出現在內存引用中(即(%bp) ),則假定該段為SS

DSBEGIN宏中設置為 0x0000,因此 DS:0x00 是 0x0000:0x0000,它是中斷 0 的段:偏移地址的 IP(偏移)部分。 指令:

/* Set code segment of the handler for interrupt 0. */
mov %cs, 0x02

CSBEGIN宏中設置為 0x0000。 該指令將 0x0000 移動到內存地址 DS:0x02 (0x0000:0x0002)。 0x0000:0x0002 是中斷 0 地址的段部分。 在這條指令之后,中斷 0 的 IVT 條目現在指向我們引導扇區中的handler代碼。 指令:

int $0

調用現在指向handler中斷 0。 它應該在屏幕上顯示a ,然后在int $0之后繼續打印b然后停止的代碼。


最小完整可驗證示例的代碼

您的問題缺少一個最小的完整可驗證示例。 我修改了common.h以僅包含您編寫的代碼所需的宏,並保持其他所有內容相同:

鏈接器.ld

SECTIONS
{
    /* We could also pass the -Ttext 0x7C00 to as instead of doing this.
     * If your program does not have any memory accesses, you can omit this.
     */
    . = 0x7c00;
    .text :
    {
        __start = .;

        /* We are going to stuff everything
         * into a text segment for now, including data.
         * Who cares? Other segments only exist to appease C compilers.
         */
        *(.text)

        /* Magic bytes. 0x1FE == 510.
         *
         * We could add this on each Gas file separately with `.word`,
         * but this is the perfect place to DRY that out.
         */
        . = 0x1FE;
        SHORT(0xAA55)

        /* This is only needed if we are going to use a 2 stage boot process,
         * e.g. by reading more disk than the default 512 bytes with BIOS `int 0x13`.
         */
        *(.stage2)

        /* Number of sectors in stage 2. Used by the `int 13` to load it from disk.
         *
         * The value gets put into memory as the very last thing
         * in the `.stage` section if it exists.
         *
         * We must put it *before* the final `. = ALIGN(512)`,
         * or else it would fall out of the loaded memory.
         *
         * This must be absolute, or else it would get converted
         * to the actual address relative to this section (7c00 + ...)
         * and linking would fail with "Relocation truncated to fit"
         * because we are trying to put that into al for the int 13.
         */
        __stage2_nsectors = ABSOLUTE((. - __start) / 512);

        /* Ensure that the generated image is a multiple of 512 bytes long. */
        . = ALIGN(512);
        __end = .;
        __end_align_4k = ALIGN(4k);
    }
}

常見的.h

/* I really want this for the local labels.
 *
 * The major downside is that every register passed as argument requires `<>`:
 * http://stackoverflow.com/questions/19776992/gas-altmacro-macro-with-a-percent-sign-in-a-default-parameter-fails-with-oper/
 */
.altmacro

/* Helpers */

/* Push registers ax, bx, cx and dx. Lightweight `pusha`. */
.macro PUSH_ADX
    push %ax
    push %bx
    push %cx
    push %dx
.endm

/* Pop registers dx, cx, bx, ax. Inverse order from PUSH_ADX,
 * so this cancels that one.
 */
.macro POP_DAX
    pop %dx
    pop %cx
    pop %bx
    pop %ax
.endm


/* Structural. */

/* Setup a sane initial state.
 *
 * Should be the first thing in every file.
 *
 * Discussion of what is needed exactly: http://stackoverflow.com/a/32509555/895245
 */
.macro BEGIN
    LOCAL after_locals
    .code16
    cli
    /* Set %cs to 0. TODO Is that really needed? */
    ljmp $0, $1f
    1:
    xor %ax, %ax
    /* We must zero %ds for any data access. */
    mov %ax, %ds
    /* TODO is it really need to clear all those segment registers, e.g. for BIOS calls? */
    mov %ax, %es
    mov %ax, %fs
    mov %ax, %gs
    /* TODO What to move into BP and SP?
     * http://stackoverflow.com/questions/10598802/which-value-should-be-used-for-sp-for-booting-process
     */
    mov %ax, %bp
    /* Automatically disables interrupts until the end of the next instruction. */
    mov %ax, %ss
    /* We should set SP because BIOS calls may depend on that. TODO confirm. */
    mov %bp, %sp
    /* Store the initial dl to load stage 2 later on. */
    mov %dl, initial_dl
    jmp after_locals
    initial_dl: .byte 0
after_locals:
.endm

/* BIOS */

.macro CURSOR_POSITION x=$0, y=$0
    PUSH_ADX
    mov $0x02, %ah
    mov $0x00, %bh
    mov \x, %dh
    mov \y, %dl
    int $0x10
    POP_DAX
.endm

/* Clear the screen, move to position 0, 0. */
.macro CLEAR
    PUSH_ADX
    mov $0x0600, %ax
    mov $0x7, %bh
    mov $0x0, %cx
    mov $0x184f, %dx
    int $0x10
    CURSOR_POSITION
    POP_DAX
.endm

/* Print a 8 bit ASCII value at current cursor position.
 *
 * * `c`: r/m/imm8 ASCII value to be printed.
 *
 * Usage:
 *
 * ....
 * PUTC $'a
 * ....
 *
 * prints `a` to the screen.
 */
.macro PUTC c=$0x20
    push %ax
    mov \c, %al
    mov $0x0E, %ah
    int $0x10
    pop %ax
.endm

主.S :

.include "common.h"
BEGIN
    CLEAR
    /* Set address of the handler for interrupt 0. */
    movw $handler, 0x00
    /* Set code segment of the handler for interrupt 0. */
    mov %cs, 0x02
    int $0
    PUTC $'b
    hlt
handler:
    PUTC $'a
    iret

建議

GDB(GNU 調試器)不理解實模式段:偏移尋址。 使用 GDB 調試實模式代碼非常有問題,我不推薦。 您應該考慮使用BOCHS來調試實模式代碼,因為它理解實模式、段:偏移尋址,並且更適合調試引導加載程序或在進入 32 位保護模式或長模式之前運行的任何代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM