I am going to write my first "hello world" bootloader program.I found an article on CodeProject website.Here is link of it.
http://www.codeproject.com/Articles/664165/Writing-a-boot-loader-in-Assembly-and-C-Part
Up-to assembly level programming it was going well, but when I wrote program using c,same as given in this article, I faced a runtime error.
Code written in my .c
file is as below.
__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");
}
I created image file floppy.img
and checking output using bochs
.
It was displaying something like this
Booting from floppy...
S
It should be Akatsuki9
. I don't know where did I mistake? Can any one help me to find why am I facing this runtime error?
Brief Answer: The problem is with gcc (in fact, this specific application of generated code) and not with the C program itself. It's hidden in the assembly code.
Long Answer: A longer (more elaborate) explanation with specific details of the problem:
(It would be helpful to have the assembly code. It can be obtained using the -S switch of gcc or use the one that I got from gcc; I've attached it at the end). If you don't already know about opcode-prefixing, c-parameter passing in assembly, etc. then have a look at the following background information section. Looking at the assembly source, it's evident that it's 32bit code. gcc with '.code16' produces 16bit code for 32bit-mode processor (using operand-size prefixes). When this same exact code is run in real (i.e. 16bit) mode, it is treated as 32bit code. This is not an issue (80386 and later processors can execute it as such, previous processors just ignore the operand-size prefix). The problem occurs because gcc calculates offsets based on 32bit-mode of (processor) operation, which is not true (by default) when executing boot-code.
Some background information (experienced assembly language programmers should skip this):
1. Operand-size prefix: In x86, prefix bytes (0x66, 0x67, etc.) are used to obtain variants of an instruction. 0x66 is the operand-size prefix to obtain instruction for non-default operand size; gas uses this technique to produce code for '.code16'. For example, in real (i.e. 16bit) mode, 89 D8
corresponds to movw %bx,%ax
while 66 89 D8
corresponds to movl %ebx,%eax
. This relationship gets reversed in 32bit mode.
2. parameter passing in C: Parameters are passed on stack and accessed through the EBP register.
3. Call instruction: Call is a branching operation with the next instruction's address saved on stack (for resuming). near Call saves only the IP (when in 16bit mode) or EIP ( when in 32bit mode). far Call saves the CS (code-segment register) along with IP/EIP.
4. Push operation: Saves the value on stack. The size of object is subtracted from ESP.
movl %esp, %ebp
in main: {{ %ebp is set equal to %esp }} pushl $.LC0
subtracts 4 from Stack Pointer {{ .LC0 addresses the char* "Akatsuki9"; it is getting saved on stack (to be accessed by printstring function) }}call printstring
subtracts 2 from Stack Pointer (16bit Mode; IP is 2bytes) pushl %ebp
in printstring: {{ 4 is subtracted from %esp }}movl %esp, %ebp
{{ %ebp and %esp are currently at 2+4(=6) bytes from the char *pstr }}pushl %ebx
changes %esp but not %ebp movl 8(%ebp), %edx
{{ Accessing 'pstr' at %ebp+8 ??? }}
Accessing 'pstr' at %ebp+8 instead of %ebp+6 (gcc had calculated an offset of 8, assuming 32bit EIP); the program has just obtained an invalid pointer and it's going to cause problem when the program dereferences it later: movsbl (%edx), %eax
.
As of now I don't know of a good fix for this that will work with gcc. For writing boot-sector code, a native 16bit code-generator, I think, is more effective (size-limit & other quirks as explained above). If you insist on using gcc which currently only generates code for 32bit mode, the fix would be to avoid passing function parameters. For more information, refer to the gcc and gas manuals. And please let me know if there is a workaround or some option that works with gcc.
I have found a fix for the program to make it work for the desired purpose while still using gcc. Kinda hackish & clearly not-recommended. Why post then? Well, sort of proof of concept. Here it is: (just replace your printstring function with this one)
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;
}
}
I invite @Akatsuki and others (interested) to verify that it works. From my above answer and the added C-pointer arithmetic, you can see why it should.
.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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With