9

I'm trying to call an assembly function from c,but i keep getting errors.

    .text
    .globl integrate
    .type integrate, @function
integrate:
    push %ebp
    mov %esp, %ebp
    mov $0,%edi
start_loop:                
    cmp %edi,1024           
    je loop_exit
    mov 8(%ebp),%eax          
    mov 12(%ebp),%ecx          
    sub %eax,%ecx              
    add %edi,%ecx
    incl %edi                
    jmp start_loop             
loop_exit:                 
    movl %ebp, %esp
    popl %ebp
    ret   

This is my assembly function,file called integrate.s.

#include <stdio.h>

extern int integrate(int from,int to);

void main()
{
    printf("%d",integrate(1,10));
}

Heres my c code.

function.c:5:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]
/tmp/cciR63og.o: In function `main':
function.c:(.text+0x19): undefined reference to `integrate'
collect2: ld returned 1 exit status

Whenever i try to compile my code with gcc -Wall function.c -o function,it gives the 'undefined reference to integrate' error.I also tried adding link to the integrate.s file from c,like

#include<(file path)/integrate.s>

but it didnt work as well.Btw what assembly code is doing is not important,for now im just trying to call the function from c successfully.Can anyone help me about solving this problem ?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Ali U.
  • 119
  • 1
  • 1
  • 5
  • Jester's answer should make your program not crash at least. You should probably mark his answer. I was thinking about posting the full assembly code, but without knowing what the assembly is supposed to do, I really cannot do more than his answer. – Yongwei Wu Jan 15 '14 at 12:14

5 Answers5

11

I see the following problems with the code:

  • calling convention mandates you must preserve the value of edi
  • cmp %edi,1024 is using 1024 as address and will probably fault. You want cmp $1024,%edi for comparing with an immediate number
  • you are reloading eax and ecx from the arguments each iteration so the calculation you perform has no effect
  • you don't seem to put any sensible return value into eax (it will return the value of from that was passed in)

The first two points apply even if "what assembly code is doing is not important".

Jester
  • 56,577
  • 4
  • 81
  • 125
  • You didn't answer the question. I can't believe somebody votes this up. The question was clearly about linkage. He went to great length to avoid the two point you raised. – Albert van der Horst May 30 '23 at 16:07
  • I can't recall exactly as this was 11 years ago, but I am pretty sure the linkage problem was quickly resolved and then OP probably asked about other problems in the code. EDIT: see the comment under user529758's answer, below. In any case, for other visitors like yourself, it may be useful to point those out. If you do not agree, feel free to downvote. – Jester May 30 '23 at 18:13
  • I'm not in the habit of voting answers down that contain useful information. That happens only as the answer is plain wrong. – Albert van der Horst Aug 10 '23 at 09:16
11

x86-64 Linux example

There is already an answer here which shows how to call a void func(void), but here is an x86-64 Linux example that accepts parameters and has a return value, which was what was asked in the question. (The question and some other answers are using 32-bit code, which has a different calling convention).

To start off, let's simplify the assembly function:

# Need to make it global so it can be accessed in another file with extern
.globl integrate

# Cannot hurt to define it as a function type, sometimes useful for dynamic linking, see comments in: https://stackoverflow.com/questions/65837016/how-to-call-a-function-in-an-external-assembly-file#comment116408928_65837016 
.type integrate, @function

integrate:
    # int integrate(int from /*EDI*/,  int to /*ESI*/)
    # INPUT:
    #   the first parameter `from` is contained in %edi, the int-sized low half of %rdi
    #   the second parameter `to`  is contained in %esi
    # OUTPUT:
    #   return is passed in %eax;  
    #      you can leave garbage in the high half of RAX if convenient

    lea  123(%rdi, %rsi), %ecx         # from + to + 123 just for example
    # (main work of function done)

    mov %ecx, %eax # it seems your return value is in %ecx
                   # but we need it in %eax for the return value to C
     # or just use EAX instead of ECX in the first place to avoid this instruction
    ret

This is using the System V calling convention, where the function return value is passed back in rax and the parameters that the function receives are passed in rdi, rsi, rdx, rcx, r8, r9, then the stack in reverse order. (What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64). For example:

long add_four_nums(int first, long second, short third, unsigned fourth);

The function declared with this prototype would receive first in %edi, second in %rsi, third in %dx, and fourth in %ecx. It would return its result in %rax.

Now that we have the assembly written (though the function is mainly a stub to show how to accept arguments and return a value), you can use that function in your C file like you currently have:

#include <stdio.h>
extern int integrate(int from,int to);
int main() {
    printf("%d\n", integrate(1,10));
}

It can be compiled and linked with gcc, then run, as:

$ gcc -o combined -Wall main.c integrate.s   && ./combined
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
samuelbrody1249
  • 4,379
  • 1
  • 15
  • 58
  • 2
    *the first parameter `from` is contained in %rdi* - Nope, in C you told the compiler it's an `int`, so only EDI is guaranteed to hold your value, not necessarily zero- or sign-extended into 64-bit RDI. In practice a simple caller that passes constants *will* have it zero-extended into RDI, but the comment is still wrong unless you change the type to `uint64_t` or `long`. Same for ESI of course. – Peter Cordes Jan 22 '21 at 05:21
  • @PeterCordes cool, thanks for the feedback, I can update the answer if you think that'd be helpful, and the rest of the answer seems ok. – samuelbrody1249 Jan 22 '21 at 05:26
  • Other than that, everything looks correct; nice answer with good explanation of the necessary puzzle pieces and how they fit together. (Except ISO C says `main`'s return type must be `int`, not `void`, for "hosted" programs (i.e. that run under an OS, as opposed to freestanding)) – Peter Cordes Jan 22 '21 at 05:26
  • 1
    Yes, it should be updated. I decided to do it myself to save a bunch of back and forth in comments and edits. – Peter Cordes Jan 22 '21 at 05:37
  • @PeterCordes great thank you very much for your time and feedback! – samuelbrody1249 Jan 22 '21 at 05:38
6

Not sure if you have solved this or not, but here is how I have done this.

When compiling make sure to add both files: $gcc main.c print_msg.s -o main

To run the assembler file on its own: $as print_msg.s -o print_msg.o followed by $ld print_msg.o -e print -o print_msg. Note that this is not required if you only want to run it from your C file.

The assembler file: print_msg.s

# A program to be called from a C program
# Declaring data that doesn't change
.section .data
    string: .ascii  "Hello from assembler\n"
    length: .quad   . - string

# The actual code
.section .text
.global print
.type print, @function              #<-Important

print:
    mov     $0x1,%rax               # Move 1(write) into rax
    mov     $0x1,%rdi               # Move 1(fd stdOut) into rdi.
    mov     $string,%rsi            # Move the _location_ of the string into rsi
    mov     length,%rdx             # Move the _length_ of the string into rdx
    syscall                         # Call the kernel

    mov     %rax,%rdi               # Move the number of bytes written to rdi
    mov     $0x3c,%rax              # Move 60(sys_exit) into rax
    syscall                         # Call the kernel

then the C file: main.c

extern void print(void);

int main(void)
{
    print();
    return 0;
}
Olof Nord
  • 224
  • 3
  • 14
5

warning: return type of ‘main’ is not ‘int’

means that the return type of ‘main’ is not ‘int’... Change it to int, then:

int main()
{
}

Also, to solve the linker error, invoke GCC as

gcc -o myprog main.c integrate.s

and that should work.

  • It worked like `gcc mprog main.c integrate.s -o out`.But this time it says segmentation `fault:core dumped`.This is related to the mistakes in assembly function ? – Ali U. Dec 16 '12 at 13:02
  • @AliU. It still is. (Why are you posting this comment multiple times?) –  Dec 16 '12 at 13:04
  • It still is?You mean it is assembly function's fault?(mistakenly deleted it mistakenly,sorry.) – Ali U. Dec 16 '12 at 13:11
  • @AliU. If it compiles and links but then segfaults, then yes, I suspect it is the fault of the assembly function. Did you run the executable with a debugger attached? –  Dec 16 '12 at 13:32
0

First change:

/* void main() */
int main(void)

main cannot be void and has to return an integer (via the error). In order for C to see your integrate function, you have to specify its function signature in a header file or in the C file and after that you can do:

as -o integrate.o integrate.s

Then, do:

gcc -Wall -c c_program.c

Finally, do:

gcc c_program.o integrate.o -o integrate

Then try:

./integrate

or

sudo chmod u+x ./integrate && ./integrate

NOTE: the header file should look something like:

/* integrate.h */
#ifndef INTEGRATE
#define INTEGRATE
return_type integrate(ARGS, ...); /*if any ARGS are passed*/
#endif

and put #include "integrate.h" at the top of your C file.

N0083R
  • 1
  • 3