I'm trying to define some subroutines that have calls to printf in them. A very trivial example is as follows:
extern printf
LINUX equ 80H
EXIT equ 60
section .data
intfmt: db "%ld", 10, 0
segment .text
global main
main:
call os_return ; return to operating system
os_return:
mov rax, EXIT ; Linux system call 60 i.e. exit ()
mov rdi, 0 ; Error code 0 i.e. no errors
int LINUX ; Interrupt Linux kernel
test:
push rdi
push rsi
mov rsi, 10
mov rdi, intfmt
xor rax, rax
call printf
pop rdi
pop rsi
ret
Here test just has a call to printf that outputs the number 10 to the screen. I would not expect this to get called as I have no call to it.
However when compiling and running:
nasm -f elf64 test.asm
gcc -m64 -o test test.o
I get the output:
10
10
I'm totally baffled and wondered if someone could explain why this is happening?
In assembly language, the call instruction handles passing the return address for you, and ret handles using that address to return back to where you called the function from. The return value is the main method of transferring data back to the main program.
In assembly language, we use the word subroutine for all subprograms to distinguish between functions used in other programming languages and those used in assembly languages. The block of instructions that constitute a subroutine can be included at every point in the main program when that task is needed.
The call instruction calls near procedures using a full pointer. call causes the procedure named in the operand to be executed. When the called procedure completes, execution flow resumes at the instruction following the call instruction (see the return instruction).
A function is a piece of code that is designed to perform a subtask in the program. Functions can have local variables, receive arguments, and pass a result back to the calling program. Consider the following subroutine foo that return the value 4 to main. A function is called with the instruction “call foo”.
int 80H
invokes the 32-bit system call interface, which a) uses the 32-bit system call numbers and b) is intended for use by 32-bit code, not 64-bit code. Your code is actually performing a umask
system call with random parameters.
For a 64-bit system call, use the syscall
instruction instead:
...
os_return:
mov rax, EXIT ; Linux system call 60 i.e. exit ()
mov rdi, 0 ; Error code 0 i.e. no errors
syscall ; Interrupt Linux kernel
...
I would say that your call to exit
is failing, so when it returns, it falls through to the test
function, that prints the first 10.
Then when you return with ret
you go back to the instruction just after the call os_return
, that is, well os_return
. The call to exit fails again and falls through to the test
function again. But this time the ret
returns from the main
function and the program ends.
About why is the exit
call failing, I cannot tell as I don't have a 64-bit system available. But you could disassemble the exit
function from libc and see how it is done there. My guess is that the int LINUX
interface is 32-bit only, as it exists only for historic compatibility, and 64-bit linux in not so old.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With