when to write in c

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
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

when to write in c

Post by sancho1980 »

hi
i was wondering, when i start coding a bootloader, i have to do it in assembly. but at some point, most of all the os'es switch over to c pretty soon after that...when is the time to write in c? i know how to build a binary from a c object file, but im wondering how all this c-calling conventions works; how can i make sure my assembly code interfaces correctly with c-written routines?

thanks

martin
User avatar
Kevin McGuire
Member
Member
Posts: 843
Joined: Tue Nov 09, 2004 12:00 am
Location: United States
Contact:

Post by Kevin McGuire »

i know how to build a binary from a c object file, but im wondering how all this c-calling conventions works; how can i make sure my assembly code interfaces correctly with c-written routines?
GCC by default uses the cdecl calling convention. I am supposing it stands for C declaration convention. There exist three major calling conventions: cdecl, stdcall, and fastcall. Each one has it's strength and weakness. Some conventions such as cdelc are suited for a variable number of arguments to be passed on the stack.

Take for instance a function prototype in C:
int myfunction(int a, int b, int c, int d);

Using the cdecl calling convention the arguments are passed using the stack, and are pushed onto the stack in reverse order which means starting with the argument on the right and moving to the left instead of the natural sense to start on the left and move to the right.
push d
push c
push b
push a
call myfunction


The reason we pushed the arguments from left to right is because it allows us to use functions with a variable number of arguments.
myfunction:
mov 4(%esp), %eax . . .# a
mov 8(%esp), %ebx . . .# b
mov 12(%esp), %ecx .. .# c
mov 16(%esp), %edx .. .# d


We know anything past 18 bytes on the stack could be a extra arguments passed for us to use. If pushed the arguments from left to right we would have no idea how many arguments were passed if our function took a variable number of them -- with out passing a count variable which just makes extra overhead and that is bad.

void myfunction(int a, int b, int c, int d, ...);
myfunction:
mov 4(%esp), %eax . . .# a
mov 8(%esp), %ebx . . .# b
mov 12(%esp), %ecx .. .# c
mov 16(%esp), %edx .. .# d
mov 20(%esp), %.... . . .# A extra argument passed.. maybe.


Generally you will see this with a printf function where it does not include a count argument, but instead expects another argument to be on the stack each time it encounters a format identifier in its format string. The number of format identifier it encounters acts as the count of arguments.
printf(const char *formatString, ...);

Also to note is that the caller cleans the stack up, such that:
push d
push c
push b
push a
call myfunction
add esp, 16 . . . # cleaning the stack up[/size=10]

Other calling conventions such as stdcall, used for Windows API function, leaves the stack cleanup responsibility with the callee. This allows smaller code to be generated since the stack cleanup code is only emitted once for each function instead of each function call as is the case with cdecl convention.

The problem with using stdcall with a variable number of arguments is quite apparent when you try to figure out a way for the called function to know how many arguments were pushed onto the stack with out giving it a count.

There is also a fastcall convention in which argument are passed by registers up to a certain extend. IIRC, GCC will pass only two arguments via registers using fastcall, then it resorts back to using the stack for any remaining ones. You will have to look this one up.

most of all the os'es switch over to c pretty soon after that...when is the time to write in c?

I dunno how to explain this one. :P
pcmattman
Member
Member
Posts: 2566
Joined: Sun Jan 14, 2007 9:15 pm
Libera.chat IRC: miselin
Location: Sydney, Australia (I come from a land down under!)
Contact:

Post by pcmattman »

Generally the bootloader loads a second-stage program which then loads the operating system. The bootloader should be in full ASM. The others can be a mix of both.

GRUB loads a second-stage program that knows the filesystem, and uses that to read in your kernel and start executing it.

In theory you could just write a bootloader that loads your kernel, but if it's compiled with GCC you would need to get into PMode in your bootloader first (because GCC doesn't output 16-bit code) and that can take a lot of code, especially since you only have 512 bytes.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

jump into protected mode failing again :-(

Post by sancho1980 »

ok...i have copied a little bit from this site:

http://www.osdever.net/tutorials/brunma ... ial_03.php

and changed it a little bit to the attached file which i compile with

Code: Select all

gcc -ffreestanding -c boot.c -o boot.o
ld -o boot.bin -Ttext 0x0 -e main --oformat binary boot.o
In my 512-byte boot sector I have set up a GDT containing a 32-bit code segment with the exact same base address as I load this piece of code. So after entering protected mode, I simply jump to the selector:offset and was expecting this code to execute, but it doesnt work! can someone help me? is there anything wrong with how i compile?

i have also added the other pertinent files so you get the broader picture

thanks

martin
Attachments
bootdisksrc.zip
(3.11 KiB) Downloaded 51 times
User avatar
Kevin McGuire
Member
Member
Posts: 843
Joined: Tue Nov 09, 2004 12:00 am
Location: United States
Contact:

Post by Kevin McGuire »

You appear to be lacking knowledge about important debugging tactics and tools -- which means that until you lean these tactics and how to use these tools you will end up taking one-hundred times longer to complete something than someone who does posses this knowledge.

The major tool is BOCHS. The tactics are various and wide, but are primarily printing debugging information to the screen in some way that you can understand.
  1. Print characters to the screen to tell what instructions were completed before something went horribly wrong.
  2. Print values to the screen to determine what is happening.
  3. Run BOCHS in debugging mode to step through the machine instructions to find where something goes horribly wrong.
  4. Reduce the code to determine the location of the problem by using hlt instructions, for( ; ; ) loops, and while(1) loops -- if it does not crash then you must not have made it to the problem in the code yet.
  5. Learn to read the BOCHS output for errors if you use it.
  6. Google for a similar problem, search the forums, read documentation relevant to the problem (Intel/AMD System Programming).
Using only the methods described here (in general) you can find the answer to any problem with out ever asking for help! Sometimes you might not be able to find the answer, and then you will end up asking here. The way I can tell if someone has tried debugging their own code is if they explain what they have done to try and find the problems and the results from doing so.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

I am using qemu instead of Bochs (is that a good/bad idea?) and Im still curious in what many ways this can help me debug a program
in the other point i feel your not being wholly fair because i do know a lot of these techniques and have also used them in this bootcode, and i can tell you for sure that the problem in this example is the very last instruction in 512.asm, i.e. the jump to the c code...just leave it out and see how everything else is executed...

[edit]also, i simply asked if there is anything wrong with my conception that i can "just jump" to the c-code compiled the way i did here or if i have to compile in a different way..i added the rest of the files just so you can see in what context and how i am trying to run this code in case you're interested..i didnt expect anyone to read all of it. another point is: how am i going to print debug information when the whole point of this c-code which i am unsuccessfully trying to run is to get a printout in protected mode :-) [/edit]
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Post by Brendan »

Hi,
sancho1980 wrote:I am using qemu instead of Bochs (is that a good/bad idea?) and Im still curious in what many ways this can help me debug a program
in the other point i feel your not being wholly fair because i do know a lot of these techniques and have also used them in this bootcode, and i can tell you for sure that the problem in this example is the very last instruction in 512.asm, i.e. the jump to the c code...just leave it out and see how everything else is executed...
Use Qemu and Bochs - you can't test your code on too many different "computers"....

Today I found out that Virtual PC 2007 is a free download, so I installed it and set it up for network boot. My code refused to boot - instead it complained about the "PXE cached network information" data structure being too small. I've been using this code for about a year now to boot 3 different (real) computers without any problems, and thought my code didn't have any bugs.

In the end I found out I did have a bug in my PXE boot code (I was using the limit field in the structure instead of the size field). It seems that Etherboot sets both of these fields and the bug was there all this time - it just didn't show up. It did show up when I tried using the PXE ROM that comes with Virtual PC and would've shown up on other network card ROMs too, so I'm lucky I found it.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

yeah, but coming back to my question: just looking at the file boot.c and how i compile it in the Makefile (see attachment) what do you think: should i be able to load this very piece of code and just jmp to it (in 32-bit pm) or is there anything i didnt take into account??
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

i still cant find the problem
is there any good resource on debugging with qemu
if only i had a chance to see the contents of registers and memory at execution time with qemu
i think thats possible somehow, but i cant figure how..
any hints??
User avatar
Kevin McGuire
Member
Member
Posts: 843
Joined: Tue Nov 09, 2004 12:00 am
Location: United States
Contact:

Post by Kevin McGuire »

Yes, I have a hint: gbdshcob elbatucexe sedulcni rellatsni swodniw ehe ;reggubed a sah shcob.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

okay the windows version has a debugger, what do i do if im in linux?
ill have to compile bochs myself, no?
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Post by Brendan »

Hi,
sancho1980 wrote:okay the windows version has a debugger, what do i do if im in linux?
ill have to compile bochs myself, no?
I'm not sure if there's a way to avoid compiling Bochs yourself on Linux, but to be perfectly honest I wouldn't use a pre-compilied binary anyway.

For Bochs, you'd download the source code and do "./configure" with any options you want. These options include things like PCI support, SMP, FPU, MMX, SSE, 3DNOW, APICs, and the debugger, code to disassemble instructions, the 0xE9 hack, etc (to get a full list try "./configure --help"). After that you'd do "make bochs".

This gives you a huge amount of control over what Bochs does and doesn't emulate. It's not like Qemu (and other emulators) - you can change the features supported by the emulated CPU/s to see if your code works on everything from 80386 to the latest CPUs.

This can be very useful, as it's the only way to test all your code without buying lots of computers. For example, imagine if your OS supports SYSCALL and SYSENTER, and you want to test that SYSENTER and SYSCALL work if they are present and that none of your code tries to use these instructions if they aren't present.

Because of this I have about 20 different Bochs binaries - one for 80486 without the debugger, one for 80486 with the debugger, one for Pentium without the debugger, one for Pentium with the debugger, one for AMD K6, etc (all the way up to 64-bit Athlons and Pentium 4 CPUs).


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
sancho1980
Member
Member
Posts: 199
Joined: Fri Jul 13, 2007 6:37 am
Location: Stuttgart/Germany
Contact:

Post by sancho1980 »

ok, ill try that...but to be honest, my experience is every time i try to compile a program from source, i get tons of error messages even after reading the readme, then i spend around 5 hrs of unsuccessful attempts until i give up :-(

also, i used bochs under windows before which was working fine but after i changed to linux i had trouble getting it to run and when it finally did work i still got a funny problem that every time i pressed "3" (or was it "2"?? not sure...) in the virtual machine, i got an error output in the monitor saying something like "unsupported key", which convinced me it was better to use qemu... :-(

EDIT:

ok, apart from the fact that my c-code really isnt compiled the way i expected, it seems im having trouble again jmping into pm..brendan, would you have a look at it yet again please..the code does the following
-loads 512 byte boot sector (512.asm) (well thats done by the bios)
-the 512 bytes then relocate to 0500h
-then the gdt (gdt.asm) is read from disk into 010700h (gdt-reserved space til 0126ffh)
-012700h-0146ffh shall be used for the idt
-then the c-code is loaded at 014700h, but the jump to it is commented out

all i do after that is enable pm and clear the pipe (jump to enter_32), but what happens is that qemu starts to completely freeze, plus the messages i printed out while still in real mode disappear, blanking the screen..im not even able to kill qemu - i have to restart x!
if you take out the whole code between

Code: Select all

lgdt [gdt_desc]
and

Code: Select all

enter_32:
	jmp enter_32
then everything works well so I can definitely say that the problem is (again) the switch over to pm...can you help me again?


the relevant files are 512.asm, gdt.asm and Makefile (which sticks 512.bin and gdt.bin together into bootdisk.bin)

i cant attach the makefile:

Code: Select all

all: Makefile
	make bootdisk.bin
bootdisk.bin: 512.bin gdt.bin boot.bin Makefile
	-rm bootdisk.bin
	touch bootdisk.bin
	dd if=512.bin of=bootdisk.bin count=1 seek=0
	dd if=gdt.bin of=bootdisk.bin count=1 seek=1
	dd if=boot.bin of=bootdisk.bin count=1 seek=3
512.bin: 512.asm Makefile
	-rm 512.bin
	nasm -f bin 512.asm -o 512.bin
gdt.bin: gdt.asm Makefile
	-rm gdt.bin	
	nasm -f bin gdt.asm -o gdt.bin
boot.bin: boot.o Makefile
	-rm boot.bin
	ld -o boot.bin -Ttext 0x0 -e main --oformat binary boot.o
boot.o: boot.c Makefile
	-rm boot.o
	gcc -ffreestanding -c boot.c -o boot.o
do you know whats the problem here?
Attachments
512.asm
(3.74 KiB) Downloaded 51 times
gdt.asm
(4.05 KiB) Downloaded 18 times
boot.c
not relevant, uploaded for completeness
(1.15 KiB) Downloaded 18 times
Post Reply