6

I am learning AT&T x86 assembly language. I am trying to write an assembly program which takes an integer n, and then return the result (n/2+n/3+n/4). Here is what I have done:

.text
.global _start
_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax
    mov $0, %esi
    movl $4, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $3, %ebp
    div %ebp
    addl %eax, %esi
    movl %ecx, %eax
    movl $2, %ebp
    div %ebp
    addl %eax, %esi
    movl %esi, %eax
    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ret

The problem is I am getting segmentation fault. Where is the problem?

Claudio
  • 10,614
  • 4
  • 31
  • 71
user1700688
  • 63
  • 1
  • 1
  • 3
  • 8
    Did you try stepping through the code with a debugger to see what is going on ? – Paul R Sep 26 '12 at 15:07
  • 1
    AT&T branched into processors? (-: – tripleee Sep 26 '12 at 15:22
  • tripleee: "AT&T syntax" refers to the assembler syntax above, originally used in SysV Unix. It puts the operand size onto the instruction as a suffix, uses a "%" to tag register names, and puts the destination register at the end of the list. It's the syntax supported by the GNU assembler on linux. It's quite different from "Intel" syntax used more commonly in the PC industry. – Andy Ross Sep 26 '12 at 15:30
  • 6
    If you're going to learn Intel assembly language, use Intel syntax. In a decent world, that abomination you're calling AT&T syntax would never have existed. – Jerry Coffin Sep 26 '12 at 15:52

5 Answers5

8

I think the code fails here:

_start:
    pushl $24
    call profit
    movl %eax, %ebx
    movl $1, %eax
    int $0x80

profit:
    popl %ebx
    popl %eax

So, you push $24 (4 bytes) and then call profit, which pushes eip and jumps to profit. Then you pop the value of eip into ebx and the value $24 into eax.

Then, in the end, if jg end branches to end:, then the stack won't hold a valid return address and ret will fail. You probably need pushl %ebx there too.

    cmpl %ecx, %esi
    jg end
    pushl %ebx
    ret

end:
    mov %ecx, %eax
    ; `pushl %ebx` is needed here!
    ret
nrz
  • 10,435
  • 4
  • 39
  • 71
  • 2
    Good level of detail in explanation. nice. – Richard Chambers Sep 26 '12 at 15:21
  • The normal and more efficient solution is to leave the return address on the stack and access the first arg with `mov 4(%esp), %eax`. (Or whatever offset it's at if you push something else first). If you make a traditional stack frame with EBP, you'd be able to access the first arg as `8(%ebp)` regardless of other pushes. – Peter Cordes Apr 15 '19 at 13:22
2
  1. you use ecx without ever explicitly initializing it (I'm not sure if Linux will guarantee the state of ecx when the process starts - looks like it's 0 in practice if not by rule)
  2. when the program takes the jg end jump near the end of the procedure, the return address is no longer on the stack, so ret will transfer control to some garbage address.
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
2

You do not appear to be doing function calls correctly. You need to read and understand the x86 ABI (32-bit, 64-bit) particularly the "calling convention" sections.

Also, this is not your immediate problem, but: Don't write _start, write main as if this were a C program. When you start doing something more complicated, you will want the C library to be available, and that means you have to let it initialize itself. Relatedly, do not make your own system calls; call the wrappers in the C library. That insulates you from low-level changes in the kernel interface, ensures that errno is available, and so on.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • 6
    The question is about segmentation fault in *assembly* language, not about using *inline assebly in C* nor how to use wrappers for system calls. `_start` is perfectly valid here this being a pure assembly program. – nrz Sep 26 '12 at 15:40
2

Your problem is that you pop the return address off of the stack and when you branch to end you don't restore it. A quick fix is to add push %ebx there as well.

What you should do is modify your procedure so it uses the calling convention correctly. In Linux, the caller function is expected to clean the arguments from the stack, so your procedure should leave them where they are.

Instead of doing this to get the argument and then restoring the return address later

popl %ebx
popl %eax

You should do this and leave the return address and arguments where they are

movl 4(%esp), %eax

and get rid of the code that pushes the return address back onto the stack. You then should add

subl $4, %esp

after the call to the procedure to remove the argument from the stack. It's important to follow this convention correctly if you want to be able to call your assembly procedures from other languages.

Dirk Holsopple
  • 8,731
  • 1
  • 24
  • 37
1

It looks to me like you have a single pushl before you call profit and then the first thing that profit does is to do two popl instructions. I would expect that this would pop the value you pushed onto the stack as well as the return code so that your ret would not work.

push and pop should be the same number of times.

call pushes the return address onto the stack.

Richard Chambers
  • 16,643
  • 4
  • 81
  • 106