Syscalls and Operating System Calls from Assembly

CS 301 Lecture, Dr. Lawlor

When writing real code in assembly, you often need to interface with other programs.

Communication Method 0: Pass everything you need into assembly as function parameters

The easiest way to get information from C++ is just to pass it in as a parameter to an assembly language function.

Communication Method 1: Call the C standard library

Sometimes, assembly has to get its own work done.  One way to get information out of a machine is to call other C functions, like the builtin "printf" function in the C standard library header <stdio.h>.  Here's how to call it in assembly:
;Assembly equivalent of C: printf("The integer is %d\n",17);
push 17 ; integer to print
push myPrintString ; string to print it with
extern printf
call printf
add esp,8 ; clean up the stack
ret

myPrintString:
db "The integer is %d",0xa,0 ; 0xa = newline, 0 = end of a C string.

(Try this in NetRun now!)

Note that the C standard library (including fopen, rand, srand, time, etc) are just C functions, so you can just call them like ordinary C functions.

Communication Method 2: Make a direct syscall

Sometimes, such as when you're implementing a C library, or when there is no C library call to access the functionality you need, you want to talk to the OS kernel directly.  There's a special x86 "interrupt" instruction to do this, called "int".  On Linux, you talk to the OS by loading up values into registers then calling "int 0x80".

Konstantin Boldyshev has a good writeup and examples of Linux, BSD, and BeOS x86 syscalls, and a list of common Linux syscalls.  He uses NASM for the examples. Here's a slightly cleaned up version of his Linux example:
        mov     edx,8 ;message length
mov ecx,myString ;message to write
mov ebx,1 ;file descriptor (fd 1 is stdout)
mov eax,4 ;system call number (number 4 is sys_write)
int 0x80 ;call kernel
; Kernel call return value is in eax-- it'll do as a function return code.
ret

myString:
db 'Wazzup?',0xa ; our little string, followed by a newline

(Try this in NetRun now!)

Windows system call numbers keep changing, so direct system calls aren't at all easy to use on Windows.

Linker Interfacing

To define a new assembly function that can be called from outside, you need to do two things:

On Windows and several other platforms, C functions normally get linked with an underscore in front of their name, so your assembly function "foo" (in C/C++) must be declared "_foo" in assembly, and similarly the C function "printf" can only be accessed as "_printf" in assembly.

In Linux, you can even write a whole main program in assembly by just writing a "main" function:

global main
main:
push myString
extern printf
call printf
pop eax
ret

myString:
db "Hey!",0xa,0

(Try this in NetRun now!)

"extern" also works to access C/C++ global variables.

Windows Inline Interfacing

In the non-NetRun world, you have several ways to get assembly code running.  The easiest on Windows is to use inline assembly, which we previously discussed here.  Annoyingly, the Windows inline assembler (MASM) doesn't accept "extern" or "db" declarations, so you have to declare strings and external functions in C.  Thus the printf example above can be written like so in Microsoft Visual C++:

#include <stdio.h>

int main() {
       const char *myString="Hello!\n";
       __asm{
       push myString
       call printf
       pop eax
       }
       return 0;
}
See my NASM/Windows guide for how to make this code run without crashing.

Windows NASM Interfacing

You can get the full NASM syntax on Windows by using... NASM itself.  Dr. Hartman has an excellent howto on interfacing NASM with Visual C++.  The other way to use NASM from Windows is by calling it on the command line directly, like this:

  nasm -f win32 foo.S
  cl main.cpp /EHsc /Gr foo.obj

That'll turn foo.S and main.cpp into one program, main.exe.
If you prefer the Visual C++ IDE, see my illustrated guide to integrating NASM with Visual C++.