Calling Functions in Assembly

CS 301: Assembly Language Programming Lecture, Dr. Lawlor

As a reminder, on our x86-64 linux machines:
These are the same registers used by your foo function, as well as any other functions you call.

Calling Functions in Assembly

The instruction "call" is used to call another function.   A called function can then return using "ret". 

By default, the assembler assumes functions you call are defined later in the same file.  To call an external function, such as NetRun's "print_int", or a standard C library function like "exit", you need to tell the assembler the function is "extern".  "extern" isn't actually an instruction--it doesn't show up in the disassembly--it's just a message to the assembler (often called a pseudoinstruction).  In C++ or C, header files tend to contain declarations of external functions, and so play the same role as extern in the assembler.

Here's how we'd call the UNIX function "exit", which ends the program:

extern exit ; tell assembler function is defined elsewhere
call exit ; call the function

(Try this in NetRun now!)

Here's how we'd call the standard C library function "getchar", which reads one ASCII character from cin, and returns it in rax.

extern getchar
call getchar
ret

(Try this in NetRun now!)

NetRun includes several useful functions designed to be easy to call from assembly.  For example, "read_input" parses an integer from cin, and returns it in rax:
extern read_input
call read_input
; rax has the read-in integer now
add rax,10000
ret

(Try this in NetRun now!)

To show integers, "print_long" takes one long integer in rdi, and prints it on the screen:
mov rdi, 17 ; function argument goes into print_long in rdi
extern print_long
call print_long
ret

(Try this in NetRun now!)

One caution: if you call any function, that function has a perfect right to use rax, rcx, rdx, rsi, rdi, and the other registers you can use.  This makes it a little tricky to store data across a function call.  For example, print_long will use rax, so this won't actually return 5:
mov rax, 5 ; I hope to return this
mov rdi, 3 ; function argument for print_long
extern print_long
call print_long
; CAUTION: print_long trashed rax!
ret

(Try this in NetRun now!)

The solution is to save our registers before calling functions--we'll see how to do this next lecture!

Defining Functions in Assembly

A function defined in assembly looks just like a jump label, but then ends with a "ret".  Here's an example:

mov rdi,7 ; pass a function parameter
call otherFunction  ; run the function below, until it does "ret"
add rax,1 ; modify returned value
ret

otherFunction: ; a function "declaration" in assembly
	mov rax,rdi ; return our function's only argument
	ret

(Try this in NetRun now!)

Notice how a function declaration in assembly looks exactly like a goto label.  The only real difference is you can get back from a function by calling "ret" to return to whoever called you.

If you mix up call / ret and jmp / jmp, often parts of your code will get skipped over.  For example, if we replace "call otherFunction" with "jmp otherFunction", then otherFunction's ret will return all the way back to main, and we never do the add.


Getting There
Getting Back
function
call somewhere
ret      *
goto
jmp somewhere
jmp backToYou
* How does it know where to return to?  Call stores the return address to jump back to on the stack, which we'll talk about at the next lecture.

By default, functions you declare in assembly are only visible in your file.  You can make those functions visible from outside using the "global" directive, another pseudo-instruction like "extern" (and performing the same sort of operation).  In "Inside a Function" mode, NetRun automatically declares your function this way, but if you switch to "Whole Functions" mode, you can take the training wheels off and declare it yourself:

global foo ; make "foo" visible from outside this file
foo: ; a function is just a jump label
	mov rax,3
	ret

(Try this in NetRun now!)