中断处理程序在真实计算机上不起作用
Interrupt handler does not work on a real computer
我正在编写一个类似引导加载程序的程序,用于更改键盘中断 (int 0x9) 的默认中断处理程序。它适用于 bochs 和 qemu,但不适用于真正的计算机,它只打印一次 'A' 然后它不会对按下任何键做出反应。 (它应该至少打印两次字符,一次用于按下,一次用于释放)。我怎样才能使程序正常工作?我正在使用 gcc。内核代码:
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 [=10=]xFFFB, %sp\n\t"
"jmp main"
);
void print_char(char c){
asm volatile("int [=10=]x10" : : "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 [=10=]x15": : "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 并且有一个无限循环。 handler
代码:
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"
);
Update2:我发现我必须改用 lcallw
,但无限循环仍然存在。在 iret
执行后转到 0xfe9e6,然后返回到 iret
。
您的问题很可能是 ljmp *addr
语句。内存位置 addr
是相对于 DS 的,但在中断处理程序中这可以是任何内容。由于几乎可以肯定处理程序将在处理 int [=12=]x15, %ah = 0x85
的 BIOS 代码的上下文中被调用,因此 DS 将被设置为 BIOS 设置的任何内容,而不是您的代码设置的内容。
简单的解决方案是使用 ljmp *%cs:addr
使内存位置相对于 CS,但是您的代码对 CS 和 DS 使用了不同的值,因此这将不起作用。你真的应该解决这个问题,所以它们是一样的,但如果失败了,你将不得不使用像 ljmp *cs:addr-0x9200
.
这样的东西
我正在编写一个类似引导加载程序的程序,用于更改键盘中断 (int 0x9) 的默认中断处理程序。它适用于 bochs 和 qemu,但不适用于真正的计算机,它只打印一次 'A' 然后它不会对按下任何键做出反应。 (它应该至少打印两次字符,一次用于按下,一次用于释放)。我怎样才能使程序正常工作?我正在使用 gcc。内核代码:
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 [=10=]xFFFB, %sp\n\t"
"jmp main"
);
void print_char(char c){
asm volatile("int [=10=]x10" : : "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 [=10=]x15": : "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 并且有一个无限循环。 handler
代码:
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"
);
Update2:我发现我必须改用 lcallw
,但无限循环仍然存在。在 iret
执行后转到 0xfe9e6,然后返回到 iret
。
您的问题很可能是 ljmp *addr
语句。内存位置 addr
是相对于 DS 的,但在中断处理程序中这可以是任何内容。由于几乎可以肯定处理程序将在处理 int [=12=]x15, %ah = 0x85
的 BIOS 代码的上下文中被调用,因此 DS 将被设置为 BIOS 设置的任何内容,而不是您的代码设置的内容。
简单的解决方案是使用 ljmp *%cs:addr
使内存位置相对于 CS,但是您的代码对 CS 和 DS 使用了不同的值,因此这将不起作用。你真的应该解决这个问题,所以它们是一样的,但如果失败了,你将不得不使用像 ljmp *cs:addr-0x9200
.