Saturday, April 28, 2012

Calling an assembly function from C passing parameters by reference

In a recent post I showed how to call an assembly function from C. In that example we passed the exponent and base parameters by value to an assembly function, p_pow.
In this example, we'll see how to pass parameters by reference.

This time, instead of using the RAX register to return the result of the p_pow function, we pass a third parameter by reference that will hold the result.
This is the calling C code:
#include "stdlib.h"
#include "stdio.h"
#include "assert.h"

// Assembly function declaration
extern void p_pow(int, int, int *);

int main(void) 
{  
  int result;

  p_pow(2, 2, &result);
  assert(4 == result);
  
  p_pow(3, 2, &result);
  assert(9 == result);
  
  p_pow(5, 2, &result);
  assert(25 == result);   
  
  p_pow(6, 2, &result);
  assert(36 == result);   
  
  p_pow(3, 3, &result);
  assert(27 == result);      
  
  p_pow(3, 0, &result);
  assert(1 == result);
  
  p_pow(1, 5, &result);
  assert(1 == result);
  
  p_pow(-2, 2, &result);
  assert(4 == result);
  
  p_pow(-2, 3, &result);
  assert(-8 == result);

  printf("All tests passed!\n");
  return EXIT_SUCCESS;
}
Look at the prototype of the p_pow function.
extern void p_pow(int, int, int *);
We want to pass result by reference. Since in C everything is passed by default, to pass a parameter by reference what we actually do is passing by value the memory address where result is stored. That's why we've used a pointer.
This is the assembly code:
section .data          
  
section .text                

  ; Make the function name global so it can be seen from the C code
  global p_pow              

p_pow:
  push rbp
  mov rbp, rsp ; Stack initial state is stored
  
  push rdx     ; Store initial value of RDX 
               ; (memory address where the result will be stored)
               ; because RDX can be modified by MUL operation
  
  ; The base (b) is being passed in RDI register
  ; and the exponent (e) is being passed in RCX register
  
  mov eax, 1   ; Register RAX will hold the result temporarily
  
  cmp esi, 0   ; if (e == 0) -> b^0 = 1, and we're done
  jle pow_end   
  
  mul_loop:
   mul edi     ; eax*ebx = edx:eax (when operating 
               ; with ints, edx is not used).
   dec esi     ; esi = esi - 1
   jg mul_loop ; If esi > 0, it continues iterating
  
  pow_end:
  
  pop rdx      ; Restore initial value of RDX
               ; (memory address where the result will be stored)
  
  mov [RDX], dword eax ; Copy final result in memory
  
  mov rsp, rbp ; Stack initial state is restored
  pop rbp                     
  ret           
Now we compile, link and execute the program and we get:
$ yasm -f elf64 -g dwarf2 pow.asm 
$ gcc -g -o pow pow.o pow_c.c
$ ./pow
All tests passed!
Note that in the assembly code, the RDX register is holding the memory address where result is stored. That's how the assembly program is able to change its value. As I explained in a previous post, in the calling convention of the System V AMD64 ABI the registers RDI, RSI, RDX, RCX, R8 and R9 are used for integer and pointer arguments.
To see it better, we'll use gdb.
This is the content of the registers in the first call to p_pow right after executing mov [RDX], dword eax:
(gdb) info register
rax            0x4 4
rbx            0x0 0
rcx            0x0 0
rdx            0x7fffffffe0fc 140737488347388
rsi            0x0 0
rdi            0x2 2
rbp            0x7fffffffe0e0 0x7fffffffe0e0
rsp            0x7fffffffe0e0 0x7fffffffe0e0
r8             0x7ffff7dd7300 140737351873280
r9             0x7ffff7deb5f0 140737351955952
r10            0x7fffffffdf50 140737488346960
r11            0x7ffff7a76c90 140737348332688
r12            0x400460 4195424
r13            0x7fffffffe1e0 140737488347616
r14            0x0 0
r15            0x0 0
rip            0x400568 0x400568 <pow_end+3>
eflags         0x246 [ PF ZF IF ]
cs             0x33 51
ss             0x2b 43
ds             0x0 0
es             0x0 0
fs             0x0 0
gs             0x0 0
The RDX register contains the memory address of result and the content of that memory address is 4:
(gdb) x/d $rdx
0x7fffffffe0fc: 4

We've seen how by passing its memory address by value, we were able to change the content of a variable from inside an assembly function, as though we were passing the variable by reference.

No comments:

Post a Comment