By Susmit Agrawal

2019-05-13 13:51:21 8 Comments

I'm trying to link x86 assembly and C.

My C program:

extern int plus_10(int);

# include <stdio.h>

int main() {
    int x = plus_10(40);
    printf("%d\n", x);
    return 0;

My assembly program:

[bits 32]

section .text

global plus_10
    pop edx
    mov eax, 10
    add eax, edx

I compile and link the two as follows:

gcc -c prog.c -o prog_c.o -m32
nasm -f elf32 prog.asm -o prog_asm.o
gcc prog_c.o prog_asm.o -m32

However, when I run the resulting file, I get a segmentation fault.

But when I replace

pop edx


mov edx, [esp+4]

the program works fine. Can someone please explain why this happens?


@Jabberwocky 2019-05-13 14:08:10

This is a possible assembly code of int x = plus_10(40);

        push    40                      ; push argument
        call    plus_10                 ; call function
retadd: add     esp, 4                  ; clean up stack (dummy pop)
        ; result of the function call is in EAX, per the calling convention

        ; if compiled without optimization, the caller might just store it:
        mov     DWORD PTR [ebp-x], eax  ; store return value
                                        ; (in eax) in x

Now when you call plus_10, the address retadd is pushed on the stack by the call instruction. It's effectively a push+jmp, and ret is effectively pop eip.

So your stack looks like this in the plus_10 function:

|  ...   |
|   40   |  <- ESP+4 points here (the function argument)
| retadd |  <- ESP points here

ESP points to a memory location that contains the return address.

Now if you use pop edx the return address goes into edx and the stack looks like this:

|  ...   |
|   40   |  <- ESP points here

Now if you execute ret at this point, the program will actually jump to address 40 and most likely segfault or behave in some other unpredictable way.

The actual assembly code generated by the compiler may be different, but this illustrates the problem.

BTW, a more efficient way to write your function is this: it's what most compilers would do with optimization enabled, for a non-inline version of this tiny function.

global plus_10
    mov   eax,  [esp+4]    ; retval = first arg
    add   eax,  10         ; retval += 10

This is smaller and slightly more efficient than

    mov   eax,  10
    add   eax,  [esp+4]        ; decode to a load + add.

@Lundin 2019-05-13 14:12:25

The cdecl calling convention will expect the value to get returned through eax though. So you can't just write the asm function the way you like, it has to be compatible with the compiler-generated C.

@Jabberwocky 2019-05-13 14:14:23

@Lundin apparently his platform uses the cdecl convention. I also wrote it's possible assembly code, so depending on the platform it might be somewhat different. Edited and clarified. Thanks.

@Susmit Agrawal 2019-05-13 14:14:39

This really clears things up!

Related Questions

Sponsored Content

12 Answered Questions

[SOLVED] What is a segmentation fault?

3 Answered Questions

[SOLVED] What is %gs in Assembly

  • 2012-02-12 13:55:27
  • Alex F
  • 15511 View
  • 15 Score
  • 3 Answer
  • Tags:   linux assembly x86

1 Answered Questions

[SOLVED] error: comma, colon, decorator or end of line expected after operand

  • 2017-11-02 11:03:59
  • sohil farahi
  • 813 View
  • -1 Score
  • 1 Answer
  • Tags:   assembly nasm x86-64

1 Answered Questions

[SOLVED] Stack push and pop in assembly language for x86 processors

2 Answered Questions

[SOLVED] x86 memory access segmentation fault

1 Answered Questions

NASM on linux: Using sys_read adds extra line at the end

3 Answered Questions

[SOLVED] Push/Pop segmentation fault at Assembly x86

2 Answered Questions

1 Answered Questions

[SOLVED] nasm , 64 ,linux, segmentation fault core dumped

1 Answered Questions

[SOLVED] Jumping to the next "instruction" using gdb

  • 2011-10-10 01:42:26
  • Lelouch Lamperouge
  • 11422 View
  • 16 Score
  • 1 Answer
  • Tags:   c assembly gdb

Sponsored Content