April 12, 2014
System Calls from C
A few days ago I wrote a super simple
program that read data coming from the Hanvon Graphicpal and interpreted
the data, outputing the coordinates of the stylus on the pad.
I wrote the program to use no standard C
library calls except for __syscall().
__syscall() is part of the FreeBSD libc, and
is used to make an indirect system call.
This is a cool, interesting, and fun way of
programming, but I wanted to do more with it. That’s today’s project.
I’ve been working on it while baking a cake for my fiancee’s birthday.
Basically, my C code starts in main() and
makes use of only one library function. I defined a few other functions in
my C code and called them from there, but that doesn’t really matter much
for what I wanted to do.
What I did was take all the C code that I
wrote before and leave it exactly the way it is, didn’t change even one
line.
Then, I wrote a small bit of ASM code.
The ASM code does two things:
1. Calls main()
2. Implements __syscall()
With that code, I can compile and assemble
my C code and ASM code, then I can link the resulting object code together
with each other and NOT with any other code. No more standard C library, no
more startup code.
The result: It does exactly the same thing!
Now, I know what you are thinking: that’s
a really hard way to something very simple.
Which is of course true. Although really the
code is so simple, so it’s hard to say that it’s “a really hard
way”, but comparitively, it’s much more complicated than just writing C
code calling the standard C library. However, if we take a look at
something simple and measurable, like say executable file sizes, we might
notice some differences.
The file size of the C code calling
__syscall() from the FreeBSD standard C library, statically linked and
stripped, is 356 kilobytes. Hmm.. that’s pretty small, right? Seems like
it would be hard to beat that.
Let’s just take a look at our C code
linked with the ASM glue. The C code linked with only the ASM __syscall
glue ends up being 2.3 kilobytes.
That’s 2.3K! That’s over 150 times
smaller than the code using the standard C library!
Now I know that comparing file sizes is not
really a good measure of anything, but it helps show that there is a
significant difference between the the two methods. I’m sure you get the
picture.
Now finally, let’s get to the code. Now
you should know that I go lucky with this code; Basically all it does it
move the values in registers around. This works because FreeBSD amd64
calling convention and syscall convention both use registers to pass
arguments. This will NOT work in 32 bit FreeBSD because 32 bit FreeBSD
passes arguments on the stack.
Here is the code:
section .text
global _start, __syscall
extern main
__syscall:
mov rax,rdi;
mov rdi,rsi;
mov rsi,rdx;
mov rdx,rcx;
syscall;
ret;
_start:
call main;
mov rax,1;
syscall;
ret;
We can assemble this code with yasm using
the following command line:
yasm -felf64 syscall_amd64.s
Which will create syscall_amd64.o, which we
can then link with hanvon.o like this:
ld hanvon.o syscall_amd64.o -o hanvon
Which will create the hanvon executable.
That's all I have today, enjoy!