【问题标题】:crash in irq handler in qemu: trying to execute code outside RAM or ROMqemu 中的 irq 处理程序崩溃:尝试在 RAM 或 ROM 之外执行代码
【发布时间】:2014-01-12 06:03:00
【问题描述】:

我一直在看内核开发教程,尤其是bran's kernel,我使用 qemu 进行测试,使用 -kernel 或 -cdrom 我遇到了崩溃。据我了解,这里会发生什么:
一旦我启用了中断 (sti),我的 irq 处理程序就会捕获坑中断。我重用了 Bran 示例中的代码:

.intel_syntax noprefix
.global irq00
.extern_default_handler
irq00:
    cli
    push 0
    push 32
    jmp irq_common_stub
irq_common_stub:
    pushad
    push ds
    push es
    push fs
    push gs
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    push eax
    mov eax, irq_default_handler
    call eax
    etc...
    add esp, 8
    sti
    iret

现在当我启用 qemu 跟踪选项 (-d cpu,exec,in_asm) 时,我看到了:

IN:
0x0010121b: mov   %eax, %fs
0x0010121d: mov   %eax, %gs
0x0010121f: mov   %esp, %eax
0x00101222: mov   0x100e81, %eax
0x00101227: call  *%eax

Trace 0xb3aa3360 [0010121b]
EAX=83535657 EBX=00009500 ECX=0000000f EDX=00000000
ESI=00000000 EDI=0010a000 EBP=00000000 ESP=00107fc8
EIP=83535657 EFL=00000046 [---Z-P-] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9300 DPL=0 CS32 [-R-]
SS =0008 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0008 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0008 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0008 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00108060 0000001f 
IDT=     001080a0 0000071f
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000 
DR6=ffff0ff0 DR7=00000400 
CCS=00000000 CCD=00000000 CCO=SUBL
EFER=0000000000000000
qemu fatal: trying to execute code outside RAM or ROM at 0x83535657

最后一个objdump -d kernel.bin 让我检查一下确实是我的

void irq_default_handler(isr_stack_frame_t sf);

C 函数在我期望的地址:

00100e81 <irq_default_handler>

eax 和 eip 的值看起来很奇怪,但前面的 mov 显示了 C 函数的正确地址。 也许最后缺少的信息是以下结构:

 typedef struct {
     uint32_t gs, ds, es, ds;
     uint32_t edi, esi, ebp, ebx, edx, ecx, eax;
     uint32_t int_number, err_code;
     uint32_t eip, cs, eflags, useresp, ss;
 } isr_stack_frame_t;  

所以从那里不知道去哪里。不知道我如何从我的电话中得到一个不正确的 eip 值。特别是因为我也尝试直接调用 C 函数。欢迎任何建议或评论。

【问题讨论】:

  • Bran 的示例在我看来就像 Nasm 语法。对于“.intel_syntax noprefix”,请尝试mov eax, offset irq_default_handler。如果 Gas 对此抱怨,请尝试 mov eax, offset flat: irq_default_handler - 或使用与示例相同的汇编程序。
  • 该教程使用 NASM。 GAS 和 NASM 不一样。您应该这样做,或者理解程序集并翻译成 AT&T 语法。 -1 表示 GAS 中的 Intel 语法,其中数据从右向左流动。并为一个好问题 +1。

标签: assembly x86 kernel qemu


【解决方案1】:

我认为您的结构已关闭。

typedef struct {
     uint32_t gs, ds, es, ds;
     uint32_t edi, esi, ebp, ebx, edx, ecx, eax;
                           ↑↑
     uint32_t int_number, err_code;
     uint32_t eip, cs, eflags, useresp, ss;
} isr_stack_frame_t;

pushad 指令按操作码编码顺序(eax、ecx、edx、ebx、esp、ebp、esi、edi)推送所有八个 GPR。 (现在看链接的教程,Bran 那里也有esp。)

您缺少的另一件事是push eax 之前的mov eax,esp 和调用:您传递给IRQ 处理程序的isr_stack_frame_t * 指针在您的情况下是错误的。您需要传递实际的帧地址 - 这是堆栈的顶部(以及底部),因为您刚刚使用推送将它组装到那里。

但是 +2 使用 .intel_syntax noprefix - 我并不孤单! \o/

至于“良好的英特尔风格”,有点像@FrankKotler 已经说过的:从不写mov eax,foo; 总是写 either mov eax,offset foo 或 mov eax,[foo] 以免造成意外混淆。

另一件事:你不要在iret 之前直接sti:EI/DI(中断)标志存储在EFLAGS 中,无论如何都会由iret 恢复。因此,只需删除该行(Bran 也没有)。

【讨论】:

  • 嗨,问题确实是我误认为 nasm 语法和 gas intel 语法相同。使用正确的语法进行修复确实可以解决问题。也非常感谢额外的提示。但我对浪费时间感到非常恼火,尽管使用自动工具进行了额外的工作,但我还是转向了 nasm……感谢你和弗兰克,当然还有:圣诞快乐! :)
  • 我个人认为 NASM 已损坏(例如,在 32 位模式下编写 pusha 会汇编为 pushad 而不是 pusha,他们称之为 pushaw...)并且像在 Intel 中一样愉快地使用 GNU整个 MirBSD 的模式(从 MBR/bootmanager 开始,通过用于多个不同文件系统和第二阶段加载程序的第一阶段引导加载程序/引导扇区,到自己的第二阶段加载程序的设置代码,到用户空间的东西,例如在 libc 和 libm 中) .哦,也祝你圣诞快乐。
  • @mirabilos 但我个人认为英特尔语法本身是坏的。 AT&T 语法提供了更多的灵活性。
  • @AkibAzmain:灵活性,具体在哪里?一定要展示例子。 (但让我们转到 Chat;如果您想讨论这个问题,请随时与我打开一个。)在此之前,我将得出结论,与 GNU 相关的 Intel 和 AT&T 语法同样有效(操作码选择,如 @ 987654338@ vs 31 C0,两者都不能控制,毕竟是汇编器的实现选择)。
猜你喜欢
  • 2014-04-23
  • 2019-03-16
  • 1970-01-01
  • 1970-01-01
  • 2016-11-27
  • 2011-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多