Page 1 of 2
Linking a C program to an Assembly OS's system calls
Posted: Mon Dec 14, 2009 11:50 am
by IanSeyler
Hi everyone,
My OS BareMetal is written in x86-64 assembly and I would like to get C programs to access to OS system calls.
How can I pass the needed information in C to my OS? The assembly os_print_string function takes a string at address RSI.
Does this code look ok? Or is there a better way to do it?
Code: Select all
// gcc -o testc.o -c testc.c -m64 -nostdlib -nostartfiles -nodefaultlibs -O2 -fomit-frame-pointer
// ld -T app.ld -o testc.bin testc.o
void printstring(char* string);
int main(void)
{
char *str = "Hello world, from C!";
printstring (str); // Print the string using the OS system call.
return 0;
}
// os_print_string -- Displays text
// IN: RSI = message location (zero-terminated string)
// OUT: All registers perserved
void printstring(char* string)
{
asm("nop"); // To see if we get here. Debug this to see how the string address is passed. Move to RSI if needed.
asm("jmp 0x00020010"); // A link to os_print_string is at this address
}
This code does execute correctly but I would like to use the OS calls instead:
Code: Select all
char *str = "Hello world, from C!", *ch;
unsigned short *vidmem = (unsigned short*) 0xb8000;
unsigned i;
for (ch = str, i = 0; *ch; ch++, i++)
{
vidmem[i] = (unsigned char) *ch | 0x0700;
}
Re: Linking a C program to an Assembly OS's system calls
Posted: Mon Dec 14, 2009 1:22 pm
by Combuster
Care to explain what you didn't understand about the wiki page on
System Calls?
Re: Linking a C program to an Assembly OS's system calls
Posted: Mon Dec 14, 2009 2:23 pm
by IanSeyler
Hi Combuster,
I looked at it as well as the doc on Inline Assembly but the specifics were lacking. I don't use interrupts for system calls and have no need for changing privilege levels.
I'm looking for help on getting the C->ASM "glue" to work.
Re: Linking a C program to an Assembly OS's system calls
Posted: Mon Dec 14, 2009 2:56 pm
by Combuster
You can use interrupts and call gates within ring 0 with little to no modifications. Still, I'm missing large parts of your puzzle. By saying you don't use interrupts for system calls, you are also saying you are using something else - yet you never told us what that was.
Re: Linking a C program to an Assembly OS's system calls
Posted: Mon Dec 14, 2009 3:15 pm
by IanSeyler
Sorry that I didn't explain the issue more clearly.
BareMetal uses a call table at the start of the kernel:
Code: Select all
kernel_start:
jmp start ; Skip over the function call index
; Aligned for simplicity.
align 16
jmp os_print_string ; 0x00100010
align 8
jmp os_print_char
align 8
jmp os_print_char_hex
align 8
...etc...
so I just needed help with getting C to jump to the right area in the kernel. Even if the actual code behind os_print_string changes location the index will always point to it.
Here is the code that works:
Code: Select all
// gcc -o testc.o -c testc.c -m64 -nostdlib -nostartfiles -nodefaultlibs -O2 -fomit-frame-pointer
// ld -T app.ld -o testc.bin testc.o
void printstring(char *string);
int main(void)
{
static char str[] = "Hello world, from C!";
printstring(str);
return 0;
}
// C call to os_print_string
// C passes the string address in RDI instead of RSI
void printstring(char *string)
{
asm ("xchg %rsi, %rdi");
asm ("jmp 0x00100010");
asm ("xchg %rsi, %rdi");
}
Compiling that C program and running it under my OS is successful! Very primitive but it works.
Thanks,
-Ian
Re: Linking a C program to an Assembly OS's system calls
Posted: Mon Dec 14, 2009 3:27 pm
by NickJohnson
You probably want to put together some assembly functions that convert the C calling convention to your "system call" format, and put them together into a library of some sort that C programs can link to. For example, if you want your printstring function to work like this:
Code: Select all
void printstring(const char *string);
and the argument needs to end up in RSI, just use something like this: (I don't know x86_64 asm very well, but you should get the idea)
Code: Select all
[bits 64]
global printstring
printstring:
push rsi
mov rsi, [rsp+16]
call 0x001000010
pop rsi
If your "system call" modifies registers without saving/restoring them, you may have to go to this sort of extreme:
Code: Select all
[bits 64]
global printstring
printstring:
push rsi
mov rsi, [rsp+16]
pusha
call 0x001000010
popa
pop rsi
Re: Linking a C program to an Assembly OS's system calls
Posted: Tue Dec 15, 2009 12:53 pm
by IanSeyler
Yes, the plan is to create a proper library so any C/C++ app can call the OS functions. I'm still figuring out the "glue" with regards to the stack.
Current code is this an it works!
Code: Select all
// gcc -o testc1.o -c testc1.c -m64 -nostdlib -nostartfiles -nodefaultlibs -fomit-frame-pointer
// ld -T app.ld -o testc1.app testc1.o
void b_print_string(const char *string);
unsigned char b_input_wait_for_key(void);
int main(void)
{
unsigned char tchar;
b_print_string("Hello world, from C!\nHit a key: ");
tchar = b_input_wait_for_key();
if (tchar == 0x61)
{
b_print_string("key was 'a'\n");
}
else
{
b_print_string("key was not 'a'\n");
}
return 0;
}
// C call to os_print_string
// C passes the string address in RDI instead of RSI
void b_print_string(const char *string)
{
asm ("xchg %rsi, %rdi");
asm ("call 0x00100010"); // Do a call so it returns back
asm ("xchg %rsi, %rdi");
}
// C call to os_input_key_wait
unsigned char b_input_wait_for_key(void)
{
unsigned char temp = 0;
asm ("call 0x00100038");
asm ("mov %0, %%al" :"=r"(temp)); // Return the value in AL
return temp;
}
Re: Linking a C program to an Assembly OS's system calls
Posted: Tue Dec 15, 2009 3:05 pm
by NickJohnson
But my point is that the functions that have to do the system calls should be in pure assembly: assembly is good at interfacing with C, but C is not very good at interfacing with assembly. Inline assembly is implementation specific, and is hard to keep really stable, because it is a hack anyway. Everything above those interface stubs can and should be C if you're making a C library.
Re: Linking a C program to an Assembly OS's system calls
Posted: Tue Dec 15, 2009 5:43 pm
by Owen
Code: Select all
void b_print_string(const char *string)
{
asm ("xchg %rsi, %rdi");
asm ("call 0x00100010"); // Do a call so it returns back
asm ("xchg %rsi, %rdi");
}
Aaah! For all you know, GCC might be using %rdi for something other than you expect it to. Also, it's completely allowed to reorder or even eliminate that code as it has no established side effects.
It should probably look something like
Code: Select all
inline void b_print_string(const char* str) {
asm volatile("call 0x00100010" :: "?"(str) : "**List clobbered registers here**");
}
(Replacing the ? with whatever the GCC instruction is for "Put this value in %rsi; I've forgotten).
That says "This instruction has side effects you don't know about (volatile), and also "don't reorder this". It's also inlined, helping GCC assign registers more efficiently.
I'm curious though - any reason why your system calls can't follow the C convention? It would be more efficient
Re: Linking a C program to an Assembly OS's system calls
Posted: Wed Dec 16, 2009 11:57 am
by IanSeyler
NickJohnson,
Your way would be ideal but I still need to figure out how to link that kind of code in C. It looks like the MikeOS C library does it that way. The OS is x86-64 only so cross-compatibility is not an issue (as long as GCC always works the same way).
Owen,
You are correct about GCC. Who knows what it might actually use. Taking your advice the code is now this (the address of the string is copied to the RSI register):
Code: Select all
// C call to os_print_string
void b_print_string_new(const char *string)
{
asm volatile ("call 0x00100010" :: "S"(string)); // Do a call so it returns back
}
Almost all of my system calls preserve the registers. Except for things like returning the length of a string or key from input via keyboard. How do you mean the "C convention"? I wrote my system calls in a way that made the most sense in x86-64 assembly (Print a string that is located at the source (RSI), Write keyboard input to the location at the destination (RDI), return a character count in RCX, etc..).
Thanks,
-Ian
Re: Linking a C program to an Assembly OS's system calls
Posted: Wed Dec 16, 2009 12:22 pm
by Brynet-Inc
ReturnInfinity wrote:How do you mean the "C convention"?
Calling conventions... juicy behind the scenes details that the compiler handles, how arguments are passed to different routines.. does the caller or callee clean things up?
http://en.wikipedia.org/wiki/X86_calling_conventions
Perhaps you should try disassembling something compiled by the compiler, on x86 the "cdecl" convention is common.. but different compilers and operating systems use different conventions.
Writing these low level wrappers in assembly is a good idea, if you handle the prologue and epilogue code right.. then it should be simple to call it from C, and you don't risk the compiler optimizing away important details.
Does that help clarify things?
Re: Linking a C program to an Assembly OS's system calls
Posted: Wed Dec 16, 2009 1:12 pm
by IanSeyler
That helps a lot. Judging from what I see in Bochs as the C program runs it is definitely "cdecl".
Thank-you very much,
-Ian
Re: Linking a C program to an Assembly OS's system calls
Posted: Wed Dec 16, 2009 1:17 pm
by Owen
I'd still inline the code; then GCC should be able to generate code with speed close to that of a native call.
And it should be using the AMD64 ABI convention.
Re: Linking a C program to an Assembly OS's system calls
Posted: Wed Dec 16, 2009 1:45 pm
by IanSeyler
I tried the inline option but it made no difference to the final program file. It looks like it does the same thing with or without that option.
I am compiling to a flat binary but I don't see that causing a problem.
EDIT: And I will look into the x86-64 ABI specs. I think R8 and R9 are included (as well as RAX, RCX, and RDX) now.
Re: Linking a C program to an Assembly OS's system calls
Posted: Wed Dec 16, 2009 4:35 pm
by Owen
What optimization options did you pass GCC? It won't inline code unless optimizations are enabled (Or you use __attribute__((forceinline)), (or is that alwaysinline?))