Linking a C program to an Assembly OS's system calls

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.
User avatar
IanSeyler
Member
Member
Posts: 326
Joined: Mon Jul 28, 2008 9:46 am
Location: Ontario, Canada
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by IanSeyler »

I don't have optimizations enabled since they appear to trash a simple "if" statement.

Code: Select all

   if (tchar == 0x61)
   {
      b_print_string("key was 'a'\n");
   }
   else
   {
      b_print_string("key was not 'a'\n");
   }
Compiling with -O2 causes it to always print the else condition even if I hit the correct key.

-Ian
BareMetal OS - http://www.returninfinity.com/
Mono-tasking 64-bit OS for x86-64 based computers, written entirely in Assembly
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by Owen »

Can you post the code for us to look at?
User avatar
IanSeyler
Member
Member
Posts: 326
Joined: Mon Jul 28, 2008 9:46 am
Location: Ontario, Canada
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by IanSeyler »

Sure. Here is it:

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 *str);
void b_print_char(const char chr);
unsigned char b_input_wait_for_key(void);

int main(void)
{
	unsigned char tchar = 0x65;
	b_print_char(tchar);
	
	b_print_string("Hello world, from C!\nHit a key: ");
	tchar = b_input_wait_for_key();
	
	if (tchar == 'a')
	{
		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 *str)
{
	asm volatile ("call 0x00100010" :: "S"(str)); // Make sure string is passed in RSI
}

void b_print_char(const char chr)
{
	asm volatile ("call 0x00100018" :: "a"(chr));
}

// C call to os_input_key_wait
unsigned char b_input_wait_for_key(void)
{
	asm volatile ("call 0x00100038");
}
Looking at the assembly output with -O2 it doesn't even do a compare (cmp).

Thanks,
-Ian
BareMetal OS - http://www.returninfinity.com/
Mono-tasking 64-bit OS for x86-64 based computers, written entirely in Assembly
User avatar
Alexis211
Posts: 14
Joined: Mon Sep 14, 2009 9:19 am
Libera.chat IRC: lxpz
Location: France
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by Alexis211 »

The problem might be here :

Code: Select all

// C call to os_input_key_wait
unsigned char b_input_wait_for_key(void)
{
   asm volatile ("call 0x00100038");
}
How can you be sure GCC will keep your return value in rax ? This would probably be better :

Code: Select all

// C call to os_input_key_wait
unsigned char b_input_wait_for_key(void)
{
   unsigned char r;
   asm volatile ("call 0x00100038" : "=a"(r));
   return r;
}
User avatar
IanSeyler
Member
Member
Posts: 326
Joined: Mon Jul 28, 2008 9:46 am
Location: Ontario, Canada
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by IanSeyler »

Alexis211 wrote:How can you be sure GCC will keep your return value in rax ?
The ABI always has the return value in RAX.

Is there a different way to make this function? (Keystroke is returned in AL)

Code: Select all

Something like:

void b_input_wait_for_key(unsigned char chr)
{
	asm volatile ("call 0x00100038" : "=a"(chr));
}

Instead of: (gives a "warning: control reaches end of non-void function" error)

unsigned char b_input_wait_for_key(void)
{
	asm volatile ("call 0x00100038");
}

BareMetal OS - http://www.returninfinity.com/
Mono-tasking 64-bit OS for x86-64 based computers, written entirely in Assembly
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by Combuster »

The first code block loads a result into a stack register, which consequently gets ignored.
The second code block loads *AX, but the compiler can still decide to change it before returning from the call. In fact, GCC may inline the function for optimisation purposes, and you'll never see the result (more likely, your code will crash due to registers being altered without you telling the compiler about it. Volatile or not.)

Not to mention, both methods will fail when you'd try to use arguments later.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by Owen »

They should be

Code: Select all

void b_input_wait_for_key(unsigned char chr)
{
   asm volatile ("call 0x00100038" : "=a"(chr) : "0" (chr));
   return chr;
}

unsigned char b_input_wait_for_key(void)
{
   unsigned char chr;
   asm volatile ("call 0x00100038" : "=a" (chr));
   return chr;
}
The first one says "The value chr is modified and the value returned in the appropriate portion of register A, and store the starting value of chr in the same register" (The "0" means register index 0, i.e, the first one you specify). The second one should be reasonably obvious.

The code generated should be identical to how it would be otherwise; though I'd want to let GCC optimize everything by inlining.

Of course, for the first one, I'm assuming you want the parameter in RAX. If you actually want it in RSI, then tell GCC that.

If your code functions follow the x86_64 ABI, then the best method is to just declare the functions, and have an ASM file like

Code: Select all

.global some_function
some_function .equ absolute_address_of_function
Assuming that won't violate the maximum PC-relative displacement rules

Remember: If you don't tell GCC what registers your putting things in, it doesn't know!Unlike compilers such as Visual C++, it's actually pretty smart at dealing with ASM blocks (Visual C++'s handling of them is horrible; it basically turns off the optimizer for the function). Of course, this smartness means complexity.

Also remember to always give it the clobber list if needed.
User avatar
gravaera
Member
Member
Posts: 737
Joined: Tue Jun 02, 2009 4:35 pm
Location: Supporting the cause: Use \tabs to indent code. NOT \x20 spaces.

Re: Linking a C program to an Assembly OS's system calls

Post by gravaera »

ReturnInfinity wrote:Sure. Here is it:

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 *str);
void b_print_char(const char chr);
unsigned char b_input_wait_for_key(void);

int main(void)
{
	unsigned char tchar = 0x65;
	b_print_char(tchar);
	
	b_print_string("Hello world, from C!\nHit a key: ");
	tchar = b_input_wait_for_key();
	
	if (tchar == 'a')
	{
		b_print_string("key was 'a'\n");
	}
	else
	{
		b_print_string("key was not 'a'\n");
	}

	return 0;
}
Thanks,
-Ian
Hi: the solution is very simple. GCC has optimized your code since as far as it can tell, the value of tchar cannot change.

1. tchar is set to 0x65, the character 'e' in the ascii table.
2. You then call a function to wait for a key. But this function does not have a return statement.
3. GCC takes this to mean that there is no way that your wait key function could change the value of 'tchar'.
4. Gcc sees this as an opportunity to optimize a 'cmp', thus eliminating one possible cache miss, and also use a hardcoded value.
5. With the assumption that tchar will always, so far as GCC can see, be equal to 0x65, it optimizes away your whole 'if' construct, and just calls the obvious solution: if 'tchar' cannot change, then obviously the 'else' will always be taken.

Result: you get the optimized away result of the GCC's pre-coded comparison of 'tchar' against 0x61, or 'a'.

Solution:

1. Place a return statement inside of the wait key function.
2. This is the correct solution, actually: make 'tchar' a volatile char. GCC will be clued in to the fact that it can change without any explicit or seen implicit input from files it is able to see, and thus not pre-compute and optimize anything that references it.

The last note to take is that you should read up properly on optimizations before trying to use them. If you can't think about what the compiler is doing, then you will fail to get results. :)

--All the best in your project
gravaera
17:56 < sortie> Paging is called paging because you need to draw it on pages in your notebook to succeed at it.
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by Owen »

Erm, no. Making tchar volatile won't help here.

Whats happening is GCC is inlining b_input_wait_for_key. This function contains an assembly instruction with no apparent side effects. GCC then procedes to assign tchar to a register thats not AL, and all hell breaks loose.

Basically, when GCC says "No return value for function returning non-void", what it's really saying is "The return value is gonna be junk. Whatever you did, I'm assuming you called a function like abort which never returns without telling me about that never returning, but I don't know, so I'm going to play it safe". When it inlines that code and then does the assignment, it goes "Erm, stuck! don't know what to do!" and throws away the assignment.

The correct thing to do is notify the compiler of return values properly. Putting a volatile there will just make the compiler throw up it's hands in the year.

Years of experience programming have shown me one thing: There is never a reason for a variable to be declared volatile. I'm being serious, never.
User avatar
gravaera
Member
Member
Posts: 737
Joined: Tue Jun 02, 2009 4:35 pm
Location: Supporting the cause: Use \tabs to indent code. NOT \x20 spaces.

Re: Linking a C program to an Assembly OS's system calls

Post by gravaera »

Owen wrote:Erm, no. Making tchar volatile won't help here.
Let's wait and see. :wink:
17:56 < sortie> Paging is called paging because you need to draw it on pages in your notebook to succeed at it.
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:

Re: Linking a C program to an Assembly OS's system calls

Post by pcmattman »

Basically, when GCC says "No return value for function returning non-void", what it's really saying is "The return value is gonna be junk.
Maybe, just maybe, GCC is saying that the function is defined as returning a non-void type, and it doesn't have a return statement in there at all. I dunno, maybe the compiler doesn't know how to say what it wants to say or something.

Especially when you see the lack of a return statement in the function. Gee, I wonder if GCC is actually saying "There's no return value" rather than "The return value is going to be junk":

Code: Select all

unsigned char b_input_wait_for_key(void)
{
   asm volatile ("call 0x00100038");
}
Use a function pointer if all you're doing is calling a function with the inline assembly:

Code: Select all

typedef unsigned char (*func)(void);
func b_input_wait_for_key = (func) 0x100038;
Years of experience programming have shown me one thing: There is never a reason for a variable to be declared volatile. I'm being serious, never.
While I agree with you in part, I'd like to know how you'd approach a global variable that's affected by external state changes - eg, a mutex that gets unlocked by an IRQ.
User avatar
Firestryke31
Member
Member
Posts: 550
Joined: Sat Nov 29, 2008 1:07 pm
Location: Throw a dart at central Texas
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by Firestryke31 »

pcmattman wrote:
Years of experience programming have shown me one thing: There is never a reason for a variable to be declared volatile. I'm being serious, never.
While I agree with you in part, I'd like to know how you'd approach a global variable that's affected by external state changes - eg, a mutex that gets unlocked by an IRQ.
Or what about a location in memory that gets changed by hardware?
Owner of Fawkes Software.
Wierd Al wrote: You think your Commodore 64 is really neato,
What kind of chip you got in there, a Dorito?
User avatar
Combuster
Member
Member
Posts: 9301
Joined: Wed Oct 18, 2006 3:45 am
Libera.chat IRC: [com]buster
Location: On the balcony, where I can actually keep 1½m distance
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by Combuster »

Owen wrote:Years of experience programming have shown me one thing: There is never a reason for a variable to be declared volatile. I'm being serious, never.
You sound like a linux evangelist - this is not the linux kernel where all instances of the volatile keyword have disappeared into assembly stubs because people didn't want to see volatile... </countertroll>

I'll have to agree that declaring tchar volatile in this context is semantically nonsense. As well as volatile often being used for the wrong thing.
Owen wrote:Erm, no. Making tchar volatile won't help here.
Let's wait and see. :wink:
If it does work, it's a really ugly hack.
"Certainly avoid yourself. He is a newbie and might not realize it. You'll hate his code deeply a few years down the road." - Sortie
[ My OS ] [ VDisk/SFS ]
User avatar
gravaera
Member
Member
Posts: 737
Joined: Tue Jun 02, 2009 4:35 pm
Location: Supporting the cause: Use \tabs to indent code. NOT \x20 spaces.

Re: Linking a C program to an Assembly OS's system calls

Post by gravaera »

Yah. I'll take back my little witty comeback then. Sorry if I was misleading the OP with the volatile assertion :)

--All the best
gravaera
17:56 < sortie> Paging is called paging because you need to draw it on pages in your notebook to succeed at it.
User avatar
Owen
Member
Member
Posts: 1700
Joined: Fri Jun 13, 2008 3:21 pm
Location: Cambridge, United Kingdom
Contact:

Re: Linking a C program to an Assembly OS's system calls

Post by Owen »

Firestryke31 wrote:
pcmattman wrote:
Years of experience programming have shown me one thing: There is never a reason for a variable to be declared volatile. I'm being serious, never.
While I agree with you in part, I'd like to know how you'd approach a global variable that's affected by external state changes - eg, a mutex that gets unlocked by an IRQ.
Or what about a location in memory that gets changed by hardware?
GCC's __builtin_atomic (Or MVSC's Interlocked*)

Actually, there is one case where I will use volatile: Inside a pair of macros:

Code: Select all

#define ATOMIC_LOAD(_type, _addr) (*((volatile _type*) _addr))
#define ATOMIC_STORE(_type, _addr, _val) (*((volatile type*) _addr) = _val)
(Alternatively, in C++ you can make this nicer and use templates, creating an "atomic" class).

The reason? When you use volatile, operating on an atomic variable looks like operating on a normal one - even though the semantics are very different. It is far too easy to make mistakes. In general, I prefer the explicit approach to doing things that are different from the normal.
gravaera wrote:
Owen wrote:Erm, no. Making tchar volatile won't help here.
Let's wait and see. :wink:
tchar will never have an address, because it's address is never taken, and it will be kept in registers. So GCC will shrug and go "Well, that was pointless". Basically, any access of tchar from outside that function would break aliasing rules
Post Reply