String pointers off by 0x80 bytes in Protected Mode C code

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
tristanseifert
Posts: 10
Joined: Mon Oct 14, 2013 3:53 pm

String pointers off by 0x80 bytes in Protected Mode C code

Post by tristanseifert »

First of all, hello everyone. I got into this entire OS deving thing a few weeks ago and finally got some time this weekend to read up on protected mode and start work on some test code to interface with the graphics hardware without the BIOS. I wrote a console library that prints to the VGA text buffer.

Writing individual fixed characters works, however, when I try to use my string printing function, like this:

Code: Select all

console_write_string("Hello, kernel world!\n");
I read garbage from memory.

Since I had some strings in my second stage bootloader, for example, at 0x80F, I tried passing that address to the string printing function, only to notice that the function read exactly 0x80 bytes AHEAD of where I want it to read! I observed later that this happened to ALL pointers in my C code, including the VGA text buffer pointer — even though I specified 0xB8000, I had to subtract 0x80 to be able to write to the character at (0, 0) in the buffer.

When simply appending "-0x80" after all addresses, pointers and so on, everything works flawlessly, but this seems like a really bad hack I shouldn't have to deal with. What might cause this problem? Here's how I compile and link my kernel:

Code: Select all

gcc -c -Os -Oz -arch i386 -g -Wmost -fno-builtin -static -fomit-frame-pointer -mpreferred-stack-boundary=2 -fno-align-functions -mno-sse2 -nostartfiles -nodefaultlibs -std=c99 -Wno-unused-variable -mfpmath=387 -fno-stack-protector -I. -ffreestanding *.c
gcc -static -Wl,-preload -Wl,-e,_loader -Wl,-segaddr,__TEXT,3000 -ffreestanding -nostdlib -arch i386 -o kernel.o -Wl,-segalign,20 *.o
gobjcopy -O binary kernel.o kernel.bin
This produces a flat binary as an output, with all my strings in it, and I can disassemble it with gobjtool fine and see code that roughly matches my C code.

In addition, I loaded this into QEMU and stepped through the code with gdb — my function gets the proper logical address, which, when I examine memory, contains my string, and when I subtract the offset to which the kernel is loaded also matches the location of the string in my flat binary, so I feel like I'm making some mistake with configuring the processor. My GDT is as follows, although I doubt it to be an issue as it's identical to the ones used by basically everyone else:

Code: Select all

   608                                  gdt_start:
   609 00000430 0000000000000000        	dd	$00, $00
   610                                  
   611                                  	; Code segment
   612 00000438 FFFF                    	dw	$0FFFF
   613 0000043A 0000                    	dw	$0000
   614 0000043C 00                      	db	$00
   615 0000043D 9A                      	db	$9A
   616 0000043E CF                      	db	$0CF	
   617 0000043F 00                      	db	$00
   618                                  
   619                                  	; Data segment
   620 00000440 FFFF                    	dw	$0FFFF
   621 00000442 0000                    	dw	$0000
   622 00000444 00                      	db	$00
   623 00000445 92                      	db	$92
   624 00000446 CF                      	db	$0CF	
   625 00000447 00                      	db	$00
Thanks for any help… I'm sure I made some stupid mistake somewhere in here!
User avatar
xenos
Member
Member
Posts: 1121
Joined: Thu Aug 11, 2005 11:00 pm
Libera.chat IRC: xenos1984
Location: Tartu, Estonia
Contact:

Re: String pointers off by 0x80 bytes in Protected Mode C co

Post by xenos »

My suggestions would be to use a linker script instead of specifying addresses on the command line, and to use a format like ELF instead of a flat binary. It is much easier to mess addresses up with your method, and much harder to figure out where the mistake is.

To also answer your question about the GDT, this looks fine to me. If you are unsure about configuring the processor, I suggest using Bochs instead of QEMU for debugging. The advantage over QEMU + GDB is that it has very verbose logging features, which show the processor state including GDT and segment limits, and comes with an integrated debugger (must be enabled when compiling Bochs - binaries with and without debugger are available) for also checking these at runtime with commands like "info gdt".
Programmers' Hardware Database // GitHub user: xenos1984; OS project: NOS
gerryg400
Member
Member
Posts: 1801
Joined: Thu Mar 25, 2010 11:26 pm
Location: Melbourne, Australia

Re: String pointers off by 0x80 bytes in Protected Mode C co

Post by gerryg400 »

Code: Select all

-segaddr,__TEXT,3000
Do you mean this number to be hex ? Shouldn't it be 0x3000 ?
If a trainstation is where trains stop, what is a workstation ?
User avatar
sortie
Member
Member
Posts: 931
Joined: Wed Mar 21, 2012 3:01 pm
Libera.chat IRC: sortie

Re: String pointers off by 0x80 bytes in Protected Mode C co

Post by sortie »

Stop! You are not using a cross-compiler. Please follow the Bare Bones tutorial for an example on how to use one. Additionally, you are passing a lot of quite harmful options to your compiler, possibly to conceal are not using a cross-compiler (indeed, I have never seen many of your scary options before, which makes me even more scared of them). Finally, stop using "flat binaries" because they are inherently stupid and instead adopt ELF.

This kind of problem you are experiencing is common when not following my above advise and is why I recommend it.

There is also the problem of how you load your kernel. The fact that you are using flat binaries suggest that you are using a custom bootloader. This makes it likely that your bootloader loaded your kernel incorrectly. Really, if this is the case, just implement an ELF loader in your bootloader. It's pretty straightforward to parse the program headers of ELF binaries.
Gigasoft
Member
Member
Posts: 856
Joined: Sat Nov 21, 2009 5:11 pm

Re: String pointers off by 0x80 bytes in Protected Mode C co

Post by Gigasoft »

You have to set CR0.PE first and then set the segment registers, not the other way around. With the GDT shown, DS, ES and SS should be set to 16.
tristanseifert
Posts: 10
Joined: Mon Oct 14, 2013 3:53 pm

Re: String pointers off by 0x80 bytes in Protected Mode C co

Post by tristanseifert »

sortie wrote:Stop! You are not using a cross-compiler. Please follow the Bare Bones tutorial for an example on how to use one. Additionally, you are passing a lot of quite harmful options to your compiler, possibly to conceal are not using a cross-compiler (indeed, I have never seen many of your scary options before, which makes me even more scared of them). Finally, stop using "flat binaries" because they are inherently stupid and instead adopt ELF.

This kind of problem you are experiencing is common when not following my above advise and is why I recommend it.

There is also the problem of how you load your kernel. The fact that you are using flat binaries suggest that you are using a custom bootloader. This makes it likely that your bootloader loaded your kernel incorrectly. Really, if this is the case, just implement an ELF loader in your bootloader. It's pretty straightforward to parse the program headers of ELF binaries.
I should have probably realised that I wasn't using a cross-compiler earlier -- I compiled a toolchain and it definitely removes the need to specify a billion different options to get stock GCC to output plain x86 code.
XenOS wrote:My suggestions would be to use a linker script instead of specifying addresses on the command line, and to use a format like ELF instead of a flat binary. It is much easier to mess addresses up with your method, and much harder to figure out where the mistake is.

To also answer your question about the GDT, this looks fine to me. If you are unsure about configuring the processor, I suggest using Bochs instead of QEMU for debugging. The advantage over QEMU + GDB is that it has very verbose logging features, which show the processor state including GDT and segment limits, and comes with an integrated debugger (must be enabled when compiling Bochs - binaries with and without debugger are available) for also checking these at runtime with commands like "info gdt".
I managed to somehow get this thing working properly by writing a new linker script and re-writing my bootloader to read files from FAT32-formatted partitions, and implementing an ELF loader. Sadly I haven't been able to get Bochs to cooperate with my OS X machine yet so I've been stuck with transferring my hard drive image to my PC over SCP instead of launching Bochs as part of my build process like I do with qemu right now.

Lesson learned: Use a cross compiler, use ELF instead of binaries, and treat the processor to a five-star steak dinner to get stuff working.
Post Reply