我将编写我的第一个“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
。我不知道我在哪里弄错了?任何人都可以帮我找到为什么我会遇到这个运行时错误?
答案 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字节) 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)
我遇到同样的问题,并找到了可能适合您的解决方案。它适用于模拟器(我在bochs
和qemu
上测试过),但无法在真实硬件上运行。
有一件事是使用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
设置.code16gcc
而不是.code16
,并将-m16
与gcc-4.9.2
一起使用。