Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bootloader - Display String Runtime Error

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?

like image 963
Akatsuki Avatar asked Dec 06 '14 07:12

Akatsuki


1 Answers

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.


Exact problem

We start at the
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.

Fix

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.

EDIT

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.

My Assembly-Source file

    .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
like image 108
B.Shankar Avatar answered Oct 20 '22 16:10

B.Shankar