Bootloader - 显示字符串运行时错误

时间:2014-12-06 07:32:59

标签: c linux inline-assembly bootloader

我将编写我的第一个“hello world”bootloader程序。我在CodeProject网站上找到了一篇文章。这里是它的链接。 http://www.codeproject.com/Articles/664165/Writing-a-boot-loader-in-Assembly-and-C-Part 最新的汇编级编程很顺利,但是当我使用c编写程序时,与本文中给出的相同,我遇到了运行时错误。 我的.c文件中编写的代码如下所示。

__asm__(".code16\n");
__asm__("jmpl $0x0000,$main\n");

void printstring(const char* pstr)
{
        while(*pstr)
        {
              __asm__ __volatile__("int $0x10": :"a"(0x0e00|*pstr),"b"(0x0007));
              ++pstr;
        }
}

void main()
{
        printstring("Akatsuki9");
}

我创建了图片文件floppy.img并使用bochs检查输出。 它显示的是这样的东西

Booting from floppy...
S

应该是Akatsuki9。我不知道我在哪里弄错了?任何人都可以帮我找到为什么我会遇到这个运行时错误?

2 个答案:

答案 0 :(得分:5)

简要回答:问题出在 gcc (实际上是生成代码的这个特定应用程序)而不是C程序本身。它隐藏在汇编代码中。

长答案:对问题的具体细节进行更长时间(更精细)的解释:
(使用汇编代码会很有帮助。可以使用 gcc -S 开关获取它,也可以使用我从 gcc获得的那个 em>;我在最后附上了它。如果您还不知道操作码前缀,c-parameter传递汇编等,那么请查看以下背景信息部分。看看汇编源代码,很明显它是32位代码。 gcc 与' .code16'为32位模式处理器生成16位代码(使用操作数大小前缀)。当这个相同的精确代码在实际(即16位)模式下运行时,它被视为32位代码。这不是问题(80386及以后的处理器可以这样执行它,以前的处理器只是忽略操作数大小前缀)。出现此问题是因为 gcc 根据(处理器)操作的32位模式计算偏移量,这在执行引导代码时不是(默认情况下)。

一些背景信息(有经验的汇编语言程序员应该跳过这个):
1。操作数大小前缀:在x86中,前缀字节(0x66,0x67等)用于获取指令的变体。 0x66是操作数大小前缀,用于获取非默认操作数大小的指令; gas 使用此技术为' .code16'生成代码。例如,在实际(即16位)模式中,89 D8对应于movw %bx,%ax,而66 89 D8对应于movl %ebx,%eax。这种关系在32位模式下反转。
2。参数传递C:参数在堆栈上传递并通过EBP寄存器访问。
第3。调用指令:调用是一个分支操作,下一条指令的地址保存在堆栈中(用于恢复)。在呼叫附近仅保存IP(在16位模式下)或EIP(在32位模式下)。 far Call将CS(代码段寄存器)与IP / EIP一起保存。
4。推送操作:将值保存在堆栈上。从ESP中减去对象的大小。


确切的问题

我们从中开始 主要movl %esp, %ebp:{{%ebp设置为等于%esp}}
pushl $.LC0从堆栈指针中减去4 {{。LC0解决了char *" Akatsuki9&#34 ;;它被保存在堆栈上(由printstring函数访问)}}
call printstring 从堆栈指针中减去2(16位模式; IP为2字节)
在printstring中pushl %ebp:{{4从%esp中减去}} movl %esp, %ebp {{%ebp和%esp当前位于char * pstr的2 + 4(= 6)字节}} pushl %ebx更改%esp但不更改%ebp
movl 8(%ebp), %edx {{访问' pstr' at%ebp + 8 ??? }}

访问' pstr' at%ebp + 8而不是%ebp + 6 gcc 计算了偏移量为8,假设为32位EIP);程序刚刚获得了一个无效指针,当程序稍后解除引用时会导致问题:movsbl (%edx), %eax

修复

到目前为止,我还不知道对于 gcc 有什么好处。对于编写引导扇区代码,我认为原生16位代码生成器更有效(如上所述,大小限制和其他怪癖)。如果你坚持使用目前只生成32位模式代码的 gcc ,修复方法是避免传递函数参数。有关更多信息,请参阅 gcc gas 手册。如果有解决方法或某些选项适用于 gcc ,请告诉我。

编辑

我找到了一个程序修复程序,使其在仍然使用 gcc 的同时可以达到预期目的。有点hackish&显然不推荐。为什么发布呢?好吧,有点概念证明。这是:(只需用这个替换你的printstring函数)

void printstring(const char* pstr)
{
  const char *hackPtr = *(const char**)((char *)&pstr-2);
  while(*hackPtr)
  {
    __asm__ __volatile__("int $0x10": :"a"(0x0e00|*hackPtr),"b"(0x0007));
    ++hackPtr;
  }
}

我邀请@Akatsuki和其他人(感兴趣的)来验证它是否有效。从上面的答案和添加的C指针算法,你可以看出为什么它应该。

我的汇编源文件

    .file   "bootl.c"
#APP
    .code16

    jmpl $0x0000,$main

#NO_APP
    .text
    .globl  printstring
    .type   printstring, @function
printstring:
.LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    pushl   %ebx
    .cfi_offset 3, -12
    movl    8(%ebp), %edx
    movl    $7, %ebx
.L2:
    movsbl  (%edx), %eax
    testb   %al, %al
    je  .L6
    orb $14, %ah
#APP
# 8 "bootl.c" 1
    int $0x10
# 0 "" 2
#NO_APP
    incl    %edx
    jmp .L2
.L6:
    popl    %ebx
    .cfi_restore 3
    popl    %ebp
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE0:
    .size   printstring, .-printstring
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "Akatsuki9"
    .section    .text.startup,"ax",@progbits
    .globl  main
    .type   main, @function
main:
.LFB1:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    pushl   $.LC0
    call    printstring
    popl    %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
.LFE1:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

答案 1 :(得分:0)

我遇到同样的问题,并找到了可能适合您的解决方案。它适用于模拟器(我在bochsqemu上测试过),但无法在真实硬件上运行。

解决方案

有一件事是使用gcc-4.9.2,并将代码生成更改为.code16gcc

因此,您的代码变为:

__asm__(".code16gcc\n");
__asm__("jmpl $0x0000,$main\n");

void printstring(const char* pstr)
{
        while(*pstr)
        {
              __asm__ __volatile__("int $0x10": :"a"(0x0e00|*pstr),"b"(0x0007));
              ++pstr;
        }
}

void main()
{
        printstring("Akatsuki9");
}

并编译它使用-m16上的gcc标记,在我的情况下,我尝试了

gcc -c -m16 file.c

请注意,您可以通过设置-march来根据需要更改架构。或者如果你想保留教程的标志

gcc -c -g -Os -march=i386 -ffreestanding -Wall -Werror -m16 file.c

TL;博士

设置.code16gcc而不是.code16,并将-m16gcc-4.9.2一起使用。