c - 中断处理程序在真实计算机上不起作用

标签 c gcc assembly x86 bootloader

我正在编写一个类似引导加载程序的程序,该程序更改键盘中断(int 0x9)的默认中断处理程序。它适用于 bochs 和 qemu,但不适用于真正的计算机,它仅打印一次“A”,然后按任何键都不会使用react。 (它应该打印至少两次字符,一次为按下,一次为释放)。我怎样才能使程序正确运行?我正在使用海湾合作委员会。内核代码:

asm(".code16gcc\n");

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef struct __attribute__((__packed__)) FullAddr{
    ushort offset;
    ushort seg;
} FullAddr;

#define MAIN_CODE_START 0x9200

asm (
    "xorw %ax, %ax\n\t"
    "movw %ax, %ds\n\t"
    /*Linker places the start of the .data section at this address */
    "movw (0x9202), %ax\n\t"
    "movw %ax, %ds\n\t"
    "movw %ax, %ss\n\t"
    "movw $0xFFFB, %sp\n\t" 
    "jmp main"
);

void print_char(char c){
    asm volatile("int  $0x10" : : "a"(0x0E00 | c), "b"(7));
}

void get_int_addr(ushort interrupt, FullAddr *addr)
{
    asm volatile(
        "pushw %%ds\n\t"
        "movw %w3, %%ds\n\t"
        "movw (%w2), %w0\n\t"
        "movw 2(%w2), %w1\n\t"
        "popw %%ds"
        : "=c"(addr->offset), "=a"(addr->seg):"b"(interrupt*4),"a"(0)
    );
}

void set_int_addr(ushort interrupt, uint func){
    asm volatile(
        "cli\n\t"
        "pushw %%ds\n\t"
        "movw %w2, %%ds\n\t"
        "movw %w0, (%w1)\n\t"
        "movw %%cs, 2(%w1)\n\t"
        "popw %%ds\n\t"
        "sti"
        : : "c"(func-MAIN_CODE_START), "b"(interrupt*4), "a"(0):
    );
}

void wait(uint usec)
{
    asm volatile("int $0x15": : "a"(0x8600), "c"(usec>>16), "d"(usec&0xFFFF));
}

FullAddr addr;

void handler_func(){
    print_char('A');
}

void handler();
asm(
    "handler:\n\t"
    "pushal\n\t"
    "call handler_func\n\t"
    "popal\n\t"
    "ljmp *addr\n\t"
    "iret\n\t"
);

void main(){
    get_int_addr(9, &addr);
    set_int_addr(9, (uint)handler);
    while(1){
        wait(1000);
    }
}    

完整项目可以下载here ,它包括软盘镜像。要构建它,请启动文件 build.sh 和 build_main.sh。

更新: 我尝试过 @RossRidge 代码 - lcall 指令跳转到 0xfe9e6 而不是 0xfe897,并且存在无限循环。 处理程序代码:

asm(
    "handler:\n\t"
    "pushw %ds\n\t"
    "pushal\n\t"
    "xorw %ax, %ax\n\t"
    "movw %ax, %ds\n\t"
    "movw (0x9202), %ax\n\t"
    "movw %ax, %ds\n\t"
    "call handler_func\n\t"
    "popal\n\t"
    "pushf\n\t"
    "lcall *addr\n\t"
    "popw %ds\n\t"
    "iret\n\t"
);

更新2:我发现我必须使用lcallw来代替,但无限循环仍然存在。在 iret 执行后,转到 0xfe9e6,然后返回到 iret

最佳答案

您的问题可能是 ljmp *addr 语句。内存位置addr是相对于DS的,但在中断处理程序中,它可以是任何东西。由于几乎可以肯定,该处理程序将在处理 int $0x15, %ah = 0x85 的 BIOS 代码上下文中被调用,因此 DS 将被设置为 BIOS 设置的值,而不是您的设置值。代码将其设置为。

简单的解决方案是使用 ljmp *%cs:addr 使内存位置相对于 CS,但您的代码对 CS 和 DS 使用不同的值,因此这是行不通的。您确实应该解决这个问题,以便它们是相同的,但如果做不到这一点,您将不得不使用类似 ljmp *cs:addr-0x9200 的东西。

关于c - 中断处理程序在真实计算机上不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34488932/

相关文章:

c - 似乎没有调用 ex19 Learn C The Hard Way 中的 void *self

c - 在#define 之前使用#undef

c - 数组上不兼容指针类型警告的分配

gcc - ELF 共享库 : relocation offset out of bounds

c - 为什么 C 语言中年份返回 116 而不是 2016?

c - fgets() : Ok by console, 管道错误

linux - Linux 中的核心转储

assembly - 添加2个bcd数字-mips

linux - 我可以从 x64 Linux 为 x86 Windows 编写程序集吗?

assembly - 反转 MIPS 汇编中数字的位