Short answer: The problem is with gcc (in fact, this is a specific application of the generated code), and not with the C program itself. It is hidden in the assembly code.
Long answer: A longer (more detailed) explanation with specific details of the problem:
(It would be useful to have build code. You can get it with the -S gcc switch or use the one I got from gcc, I bound it at the end). If you still do not know about the opcode prefix, the c-parameter passing in the assembly, etc., refer to the next section of the reference information. Looking at the source of the assembly, it is obvious that this is 32-bit code. gcc with ".code16" creates 16-bit code for a 32-bit processor (using operand-size prefixes). When this exact code is run in real (i.e. 16-bit) mode, it is considered as a 32-bit code. This is not a problem (80386 and later processors can execute it as such, previous processors simply ignore the operand size prefix). The problem arises because gcc calculates offsets based on the 32-bit operating mode (processor), which is not true (by default) when executing the boot code.
Some background information (experienced assembler programmers should skip this):
1. Prefix of operand size:. In x86, prefix bytes (0x66, 0x67, etc.) are used to obtain command options. 0x66 - operand size prefix to get instructions for non-default operand size; gas uses this method to generate code for ".code16". For example, in real (i.e. 16-bit) mode, 89 D8 corresponds to movw %bx,%ax , and 66 89 D8 corresponds to movl %ebx,%eax . This ratio changes in 32-bit mode.
2. Parameter passed to C: Parameters are passed to the stack and are accessible through the EBP register.
3. Call instruction: A call is a branch operation with the following instruction address stored on the stack (to resume). Near Call stores only IP (in 16-bit mode) or EIP (in 32-bit mode). far Call saves CS (code segment register) along with IP / EIP.
4. Push operation: Saves the value on the stack. The size of the object is subtracted from the ESP.
Exact problem
We start from the site
movl %esp, %ebp in the main: {{% ebp is set to% esp}}
pushl $.LC0 subtracts 4 from the stack pointer {{.LC0 addresses char * "Akatsuki9"; it is saved on the stack (to access the printstring function)}}
call printstring subtracts 2 from the stack pointer (16-bit mode, IP address 2 bytes)pushl %ebp in printstring: {{4 is subtracted from% esp}}
movl %esp, %ebp {{
% ebp and% esp are currently in 2 + 4 (= 6) bytes from char * pstr }}
pushl %ebx modifies% esp but not% ebp
movl 8(%ebp), %edx {{
Access to 'pstr' in% ebp + 8 ??? }}
Access to 'pstr' in% ebp + 8 instead of% ebp + 6 (gcc calculated offset 8, assuming 32-bit EIP); the program just received an invalid pointer, and this will cause a problem when the program plays it out later: movsbl (%edx), %eax .
Fix
At the moment, I do not know what a good solution for this would work with gcc. For writing boot sector code, it seems to me that a more natural 16-bit code generator is more efficient (size and other quirks, as explained above). If you insist on using gcc, which currently only generates code for 32-bit mode, the fix will be to avoid passing parameters to the function. See the gcc and gas manuals for more information. And please let me know if there is a workaround or any option that works with gcc.
EDIT
I found a fix for the program so that it works according to the desired purpose when using gcc. Kinda is hackish and clearly not recommended . Why post? Well, sort of a 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 make sure this works. From my answer above and the added arithmetic of C-pointers, you can understand why this should be.
My build 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