在x86实模式汇编中编写中断处理程序

问题描述 投票:2回答:1

我正在使用汇编学习x86实模式下的中断处理。我下面的例子取自here

.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
assembly x86 interrupt x86-16 gas
1个回答
0
投票

我只能假定您已将错误引入了本教程最初提供的代码中。例如,您说自己组装:

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

如果您包含本教程中出现的common.h,此命令将失败,并显示类似以下内容:

main.S: Assembler messages:
main.S:4: Error: no such instruction: `begin'
main.S:5: Error: no such instruction: `clear'
main.S:11: Error: no such instruction: `putc $98'
main.S:14: Error: no such instruction: `putc $97'

这些错误的发生是因为编写教程代码的方式要求C预处理程序在汇编代码上运行。最简单的方法是使用GCC通过将代码传递给后端AS汇编器来汇编代码:

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

GCC将使用带有.S扩展名的任何文件扩展名,并在通过AS汇编器之前在.S上运行C预处理程序。您也可以直接使用cpp运行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

输出应该类似于:

enter image description here


最小完整可验证示例的代码

您的问题缺少一个最小的,完整的可验证示例。我修改了common.h,使其仅包含您编写的代码所需的宏,并使其他所有内容相同:

linker.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);
    }
}

common.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

main.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
© www.soinside.com 2019 - 2024. All rights reserved.