Author Topic: Call my C function with nasm  (Read 31707 times)

Offline macaswell

  • Jr. Member
  • *
  • Posts: 16
Call my C function with nasm
« on: March 26, 2011, 10:56:19 PM »
I need to call one of my C functions from assembly, as I am writing a virtual firmware like environment for my operating system, but I don't have it quite right.

Here's my C code:
Code: [Select]
#include <stdio.h>

void foo(char *c)
{
   printf("%s", c);
}

And here's my assembly:
Code: [Select]
SECTION .data
   msg      db "Test\n",0

SECTION .text
   GLOBAL _main
   extern _foo
   GLOBAL _start

_start:
   call _main
   ret

_main:
   mov eax, msg
   push eax
   call _foo
   pop eax
   ret

I'm not sure if I'm even calling the C function correctly (so please correct me).

Anyway, it assembles and compiles okay, but when linking:
Code: [Select]
$ ld -o test.o asm.o c_prog.o
c_prog.o: In function "foo":
c_prog.o(.text+0x12): undefined reference to `printf'
asm.o(.text+0xd): undefined reference to `_foo'

What am I doing wrong?

Thanks, Matt

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Call my C function with nasm
« Reply #1 on: March 27, 2011, 02:15:38 AM »
Hi Matt,

I think you're calling your function correctly, but naming it "wrong". Unlike 'doze, Linux (this is Linux, right?) doesn't add the underscore to "foo". In addition, I think you'll want to add "-lc" to the ld command line, so it knows where to find "printf". Unless I'm mistaken, that will link allright, but attempting to run it will result in a "no such file or directory" error (!). You'll also need to advise ld where to find the "correct" interpreter/dynamic linker - "-I/lib/ld-linux.so.2"...

If I suffer an unexpected burst of ambition, I'll actually try that for ya, but I think those changes will fix it... (famous last words...)

Best,
Frank




Offline macaswell

  • Jr. Member
  • *
  • Posts: 16
Re: Call my C function with nasm
« Reply #2 on: March 31, 2011, 04:17:14 PM »
Yeah, that worked.  However, execution is okay until exit, and I get a segmentation fault.  Heres an example

Code: [Select]
global main
extern printf

SECTION .data
   array    dd 0,1,2,3,4,5
   fmt      db "%i",10,0

SECTION .text
main:
   push ebp
   mov ebp, esp
   sub esp, 0x40
   mov ebx, [esp+8]
   mov ebp, 4
   mov eax, [array+ebp*4]
   push eax
   push dword fmt
   call printf
   mov eax, 0
   ret

When I run it it does this:
4
Segmentation Fault

Why?  I can't see what's wrong.


Thanks,
Macaswell

Offline brethren

  • Jr. Member
  • *
  • Posts: 28
Re: Call my C function with nasm
« Reply #3 on: March 31, 2011, 08:02:34 PM »
Yeah, that worked.  However, execution is okay until exit, and I get a segmentation fault.  Heres an example

Code: [Select]
global main
extern printf

SECTION .data
   array    dd 0,1,2,3,4,5
   fmt      db "%i",10,0

SECTION .text
main:
   push ebp
   mov ebp, esp
   sub esp, 0x40
   mov ebx, [esp+8]
   mov ebp, 4
   mov eax, [array+ebp*4]
   push eax
   push dword fmt
   call printf
   mov eax, 0
   ret

When I run it it does this:
4
Segmentation Fault

Why?  I can't see what's wrong.


Thanks,
Macaswell

you can't just use ret like that at the end of the main procedure. you need to explicitly end the process, under linux you use sys_exit and under windows you call ExitProcess

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: Call my C function with nasm
« Reply #4 on: March 31, 2011, 08:52:32 PM »
Consult the Friendly Manual:

http://www.nasm.us/xdoc/2.09.07/html/nasmdoc9.html#section-9.1

It isn't the greatest explanation, but it may help. We had a thread recently, demanding to know where the "C calling convention" was officially written, "cast in stone". I don't know - ask the C guys - but I know you've gotta do it!

First, before we get to C... it is not immediately obvious that the "call" instruction stores its return address on the stack, and the "ret" instruction gets the address to return to off the stack - so when you get to "ret", esp had better be pointing to the return address! You don't get this right.

Then, if you're going to play nicely with C, there are further rules. Besides the stack being right, ebp must be preserved (the caller is presumably using ebp for the same purpose you are). Further, ebx, esi and edi must be preserved - if you don't alter them, you don't have to do anything, but if you do, they must be returned to their original values before "ret". push/pop are usually used, though you could do it in other ways. The return value is in eax (edx:eax for 64-bit values, but this seems rare), ecx and edx are presumed "trashed". (this is a PITA if you're used to using ecx as a "counter"!) You can expect C to follow these rules when you call a C function, and you must follow 'em when C is calling you. "main" is called! If you "exit" by other means, you can "cheat" on these rules, but if you "ret" from "main", you should "play by the rules". There's no guarantee that ecx will be trashed, or that altering ebx will cause a crash - depends on C version - but if you expect it to "always" work - do it.

Code: [Select]
global main
extern printf

SECTION .data
   array    dd 0,1,2,3,4,5
   fmt      db "%i",10,0

SECTION .text
main:
   push ebp
   mov ebp, esp
   sub esp, 0x40
   mov ebx, [esp+8]

You've moved an uninitialized value into ebx. You probably intended "[ebp + 8]"(?). This would put the first parameter into ebx. The first parameter to "main" would be "argc". Why? Since you've altered ebx, you should save and restore its original value.

Code: [Select]
   mov ebp, 4
   mov eax, [array+ebp*4]

You probably don't want to alter ebp within your function. You can do it, but we "usually" use ebp to return esp to a known value, as well as using it to access parameters and local variables. Better to use some other register. Since "printf" will trash ecx and edx, those aren't a great choice (although you could save/restore 'em around the call to "printf"). ebx, esi, and edi must be "preserved" - if we do that at the start and end of "main", we can use 'em freely, and C won't mess with them.

Code: [Select]
   push eax
   push dword fmt
   call printf

It is the caller's responsibility to "clean up the stack" ("remove" the pushed parameters). "add esp, 8"... or I like to write it as "add esp, 4 * 2" so I can alter it easily if I change the number of parameters...

Code: [Select]
   mov eax, 0
   ret

Well... you need to restore esp to its original state before the "ret". pop off any of the "non-volatile" registers (ebx, esi, edi) you've pushed, add that 0x40 back on, and pop caller's ebp. Then you're ready to "ret".

Here's a tested version that "seems to work".

Code: [Select]
; nasm -f elf32 myfile.asm
; gcc -o myfile myfile.o
;
; for Windows (untested)
; put underscores on global and extern variables
; nasm -f win32 --prefix _ myfile.asm


global main
extern printf

SECTION .data
   array    dd 0,1,2,3,4,5
   array_size equ ($ - array) / 4 ; number of items, not bytes!

   fmt      db "%i",10,0

SECTION .text
main:
   push ebp
   mov ebp, esp

; make room for local variables
; you don't use any, but it won't hurt
   sub esp, 0x40

; save caller's regs
    push ebx
    push esi
    push edi

; what parameter is this intended to get???
   mov ebx, [ebp+8]

   mov esi, 0 ; starting index
   mov edi, array_size ; number of items to print
top:
   mov eax, [array+esi*4]
   
   push eax
   push dword fmt
   call printf

; C calling convention - caller cleans up stack
   add esp, 4 * 2 ; two parameters at 4 bytes each
   
   add esi, 1 ; next index
   sub edi, 1 ; until done
   jnz top
   
   mov eax, 0 ; claim "no error"
; restore caller's regs
   pop edi
   pop esi
   pop ebx

; exit function
   leave
; equivalent to:
; mov esp, ebp ; this "frees" your local variables
; pop ebp ; restore caller's ebp
   ret
;------------------

This isn't too great. Too much stuff "hard-coded" into our routine. We probably should do something like....

Code: [Select]
...
push array_size
push array
call print_dword_array
...

I'll leave that as an "exercize for the reader" for now...

Best,
Frank