0

I'm doing one of many tasks from the book called "Programming From The ground up", by Jonathan Bartlett.

I'm currently at this task from page 72:

The factorial function can be written non-recursively. Do so.

Following program works fine.I'm having trouble understanding where EBX, ECX and EAX registers are on the stack.

Why does following code not use PUSHL and POPL instructions to place registers on stack?

What is difference between pushing registers and them mysteriously existing somewhere on stack?

    .section .data
    .section .text
    .globl _start
    
    _start:
    pushl $4        #pushing argument
    call factorial  #calling function
    addl $4, %esp       #move stack pointer back
    movl %eax, %ebx     #place result into ebx for exit status
    movl $1, %eax       #exit
    int $0x80
    
    #finds the factorial of a value
    #INPUT: First and only argument is the number to find the factorial for
    #NOTES: number must be greater than one Variables:
    # %ebx – holds the argument value
    # %ecx – holds the index we are multiplying the argument by
    # -4(%ebp) – holds the current result
    # %eax is used for temporary storage
factorial:
    pushl %ebp      #save old base pointer
    movl %esp, %ebp     #make stack pointer the base pointer
    subl $4, %esp       #get room for local storage
    movl 8(%ebp), %ebx  #put first argument in %eax
    movl %ebx, %ecx     #set index to the value of argument
    decl %ecx       #subtract index by 1 as starting point
    movl %ebx, -4(%ebp)     #store current result

    factorial_loop_start:
    movl -4(%ebp), %eax     #move current result into %eax
    imull %ecx, %eax    #multiple current result by index
    movl %eax, -4(%ebp)     #move new result into storage
    decl %ecx       #decrease index by 1
    cmpl $1, %ecx       #if index is 1 jump to end of loop
    je end_factorial
    jmp factorial_loop_start    #if index is not one restart loop
    
    end_factorial:
    #movl -4(%ebp), %eax    #return value goes in %eax
    movl %ebp, %esp     #restore stack pointer
    popl %ebp       #restore base pointer
    ret
        
    #    To run, from the command line (I'm using an x86-64 build so you may need to assemble differently)
    #               as --32 factorial.s -o factorial.o
    #           ld -melf_i386 factorial.o -o factorial
    #           ./factorial
    #           echo $?
Sep Roland
  • 26,423
  • 5
  • 40
  • 66
Edgars S
  • 1
  • 1
  • 2
  • 1
    Registers are not variables in memory. They are a separate named hardware entity. – Jonathon Reinhart Mar 07 '21 at 14:23
  • Why do you think EBX, ECX and EAX registers must be saved to stack? Is your question about calling conventions? Your code snippet pushes EBP to stack, changes it's value, uses EBP and before return restores it's value, thus the calling function can use it's original value. – nevilad Mar 07 '21 at 14:39
  • @nevilad i think that with saving them on stack is easier to navigate stack.Do i have to imagine that they are on stack? – Edgars S Mar 07 '21 at 14:47
  • No, registers are typically saved to stack if the program needs to use them (accessing registers is faster than accessing memory) but it needs their current values in future calculations. After it uses them it restores their values from stack. Another typical scenario is calling conventions - caller code calls a function and assumes that it will not change values of some registers. – nevilad Mar 07 '21 at 14:55
  • 1
    Register's values may be saved/pushed to the stack, but those are copies of values in the registers. The registers remain in the CPU. – Erik Eidt Mar 07 '21 at 16:50
  • "What is difference between pushing registers and **them mysteriously existing somewhere on stack**?" Could you please rephrase this sentence? It does not make sense! – Sep Roland Mar 07 '21 at 21:47

1 Answers1

2

The asm stack is just memory like any other region; once you reserve space, you can do random access to it with mov and addressing modes with ESP as the base register (or EBP if you set it up as a frame pointer).

In fact, often it's more convenient to do it that way: only having LIFO access to your local vars would be horrible!

In this case, it is just a missed optimization: sub $4, %esp / ... / movl %ebx, -4(%ebp) could have been combined into push %ebx. (Same as the missed optimization described in What C/C++ compiler can use push pop instructions for creating local variables, instead of just increasing esp once?)

Presumably the author of this inefficient code did that on purpose to follow standard conventions / patterns for how things are done (e.g. by compilers in debug mode.)

OTOH, some of the stuff in this code is just plain silly, like je end_factorial that only skips over a jmp factorial_loop_start. That should just have been jne factorial_loop_start or fall-through. And there's zero point in loading / storing the product every time through the loop; just keep it in a register. That's what registers are for! Only an actual compiler in debug-mode is normally that "dumb", but a compiler wouldn't have made that silly je-over-jmp mistake, so this is the worst of both worlds. :/

Peter Cordes
  • 286,368
  • 41
  • 520
  • 731