Author Topic: How do we pass an argument to a subroutine?  (Read 26276 times)

Offline maplesirop

  • Jr. Member
  • *
  • Posts: 60
How do we pass an argument to a subroutine?
« on: April 16, 2013, 01:22:28 AM »
I thought I knew a subroutine was, but now I am not sure.

Code: [Select]
mulb: ; c=a*b;
mov eax,[a] ; load a (must be eax for multiply)
imul dword [b] ; signed integer multiply by b
mov [c],eax ; store bottom half of product into c
pabc "c=a*b" ; invoke the print macro

Is the following a subroutine? If so, how do we pass an argument to that?

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: How do we pass an argument to a subroutine?
« Reply #1 on: April 16, 2013, 10:41:48 AM »
I think I'd define "subroutine" as something that ends in a "ret", so no it isn't, really. We would want to pass two parameters to our subroutine, both "a" and "b". We mignt also want to pass the address of "c" (no use passing its current "[contents]"), but it's more usual to return the answer in eax...
Code: [Select]
section .data
    a dd 21
    b dd 2
section .bss
    c resd 1
section .text
global _start:
_start:
    push dword [b]
    push dword [a]
    call multiply
    add esp, 4 * 2
    mov [c], eax
    ; print the answer?
    ; exit cleanly, as always
;-------------------

multiply:
    push ebp ; save caller's ebp - they're probably using it
    mov ebp, esp
    ; sub esp, localsize ; if local variables needed

    mov eax, [ebp + 8]
    imul dword [ebp + 12]
    ; result in edx:eax... we ignore edx...

; since we didn't use any local variables
; or otherwise alter esp, we could do without this next line
; if we had, this would restore esp
    mov esp, ebp
    pop ebp  ; restore caller's ebp
; the "leave" instruction does both of the above two lines
    ret
; end of subroutine
;----------------------------

Does that help?

Best,
Frank


Offline maplesirop

  • Jr. Member
  • *
  • Posts: 60
Re: How do we pass an argument to a subroutine?
« Reply #2 on: April 16, 2013, 01:06:53 PM »
Thanks for replying.

Do we absolutely need ret or can we just use a jump statement?

Offline TightCoderEx

  • Full Member
  • **
  • Posts: 103
Re: How do we pass an argument to a subroutine?
« Reply #3 on: April 16, 2013, 03:46:12 PM »
Fundamentally, that's the only thing that exists is jmps or if you will goto.  So;

Code: [Select]
push .NX
jmp Routine ; Mimics call  Routine
   .NX nop
   
Routine pop ax
jmp ax ; Mimics return

functionally equivalent to

Code: [Select]
call Routine

Routine ret

Here is an example of a dynamically calculated code segment, so I've emulated a far call.

Code: [Select]

mov ax, es ; Build far pointer
push ax

push 0
retf ; Jump to it


Since the 4040 in 1974, Intel has devoted a lot of engineering into that which we use today. So the best thing to do is understand the ins and outs of the instruction set and use the architecture to its best advantage. I think you are well on your way to that end, as if not you'd never have drawn the correlation between RET & JMP

Offline maplesirop

  • Jr. Member
  • *
  • Posts: 60
Re: How do we pass an argument to a subroutine?
« Reply #4 on: April 16, 2013, 09:08:15 PM »
Code: [Select]
; ----------------------------------------------------------------------------
; helloworld.asm
;
; This is a Win32 console program that writes "Hello, World" on one line and
; then exits.  It needs to be linked with a C library.
; ----------------------------------------------------------------------------

        global  _main
        extern  _printf

        section .text
_main:
        push    message
        call    _printf
        add     esp, 4
        ret
message:
        db      'Hello, World', 10, 0

Can main be a subroutine? And can you think of a very simple program with a simple routine. I have been going through some of the sample codes on the Internet and they are quite difficult to parse. Also, does ret mean jump to the code segment that called the subroutine?

Offline Gerhard

  • Jr. Member
  • *
  • Posts: 51
Re: How do we pass an argument to a subroutine?
« Reply #5 on: April 16, 2013, 09:20:34 PM »
Hi maplesirop,

Also, does ret mean jump to the code segment that called the subroutine?

no. Here's a quote from the Intel Manual 253667.pdf (Vol. 2B 4-369):

Quote
Transfers program control to a return address located on the top of the stack. The address is usually placed on the stack by a CALL instruction, and the return is made to the instruction that follows the CALL instruction.

I hope that helps. By the way, in a stand alone assembly language program, you can pass the parameters to a procedure via registers, too. That's a very fast way.

Gerhard


Offline maplesirop

  • Jr. Member
  • *
  • Posts: 60
Re: How do we pass an argument to a subroutine?
« Reply #6 on: April 18, 2013, 09:10:03 PM »
Thanks,

so basically

we put the address of printf into the stack with call

and use that address to print something with ret?

In essence, call _printf is useless without ret?

It's like interrupt right?

What's the point of using a subroutine then?

Code: [Select]
_main:
        push    message
        call    _printf
        add     esp, 4
        ret

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: How do we pass an argument to a subroutine?
« Reply #7 on: April 18, 2013, 09:56:26 PM »
"_printf" IS a subroutine, as is "_main". The "call" instruction puts the address right after the "call" - the "return address" - on the stack... and "jmp"s to the address of "_printf" (Nasm doesn't know this - the linker fills it in). When we get to the "ret" in "_printf" (it's there, although we ordinarily don't see it) we "pop" the return address off the stack, and "jmp" there... and continue execution right after the "call".

The "int" instruction is similar, except it looks up the address of the "Interrupt Service Routine" in a table, and the flags register is pushed,  as well as the return address. The "iret" instruction (which should end the ISR) pops the return address off the stack and also restores the flags register. Same general idea.

The point of a subroutine is that you can call it from different places...
Code: [Select]
_main:
        push    message
        call    _printf
        ; the "ret" in _printf brings us back here
        add     esp, 4
        push 42
        push format_int ; with a "%d" in it
        call _printf
        ; now the "ret" brings us back here

        add esp, 4 * 2 ; two parameters of 4 bytes each

        ret ; return to _main's caller

Best,
Frank


Offline Gerhard

  • Jr. Member
  • *
  • Posts: 51
Re: How do we pass an argument to a subroutine?
« Reply #8 on: April 18, 2013, 10:10:45 PM »
Hi maplesirop,

Code: [Select]
_main:
        push    message
        call    _printf
        add     esp, 4
        ret

It depends. In your code fragment has the ret instruction nothing to do with the call to _printf. The function _printf is the callee (your main is the caller) and has an own ret instruction (inside the code section of _printf). We're speaking about the special case of interfacing 32 bit C code (_printf) with assembly language code. In C has the caller to clean the stack (add  esp, 4); there are other conventions in other high level languages. For example, in Pascal has the callee to clean the stack.

The ret instruction at the end of your main function is a bit strange, because ret jumps back to the next instruction after the function call. But your main should go back to the OS. That's the point. Usually, one would finish main with an INT 80h (32 bit Linux) or an INT  21h (DOS) or whatever.

Gerhard   

Offline Gerhard

  • Jr. Member
  • *
  • Posts: 51
Re: How do we pass an argument to a subroutine?
« Reply #9 on: April 18, 2013, 10:12:42 PM »
Sorry Frank, haven't seen that you've already answered the question.

Gerhard

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: How do we pass an argument to a subroutine?
« Reply #10 on: April 18, 2013, 11:37:54 PM »
No problem, Gerhard! Always good to have more information, and/or a different approach!

Besides Pascal, the Windows APIs use the "callee cleans up" convention...
Code: [Select]
    push 0
    push caption
    push message
    push 0
    call MessageBoxA
    ; no need to remove parameters from stack
The reason we don't need to remove the parameters is that the code for MessageBoxA, although we don't see it, ends like this...
Code: [Select]
    ...
    ret 16 ; 4 parameters of 4 bytes each
Although they share the name "ret", this is a completely different opcode than "ret" with no parameter. This one, besides popping the return address, "removes" 4 parameters from the stack - just moves esp up by 16 bytes, the parameters don't really go anywhere...

The reason you can "ret" from "main" (if it's a "real C-style main" - you can call anything "main" in assembly language) is that "main" is "call"ed by some code in what I call "C startup code" (the real name used to be "crt0.o" - I think they call it something else now). This code, amongst other things, rearranges the stack slightly and calls main. After main returns, this should take care of the actual "back to the shell" interrupt. This code gets linked with your gem unless you explicitly ask for "--nostartfiles" or some such. So IF you haven't butchered the stack, and IF you've preserved the non-volatile registers (ebp, ebx, esi, edi) (this may not matter these days, but it's the way I learned it), you can "ret" from main...

The Linux "_start" label is not "call"ed, so we can't "ret" from it. We have to do the int 80h/1 or whatever - the "destroy this process and return to the shell" interrupt... or I suppose we could call "exit()" to do it for us... I'm not sure whether there's an equivalent "not-called" label for Windows or not...

Best,
Frank


Offline Gerhard

  • Jr. Member
  • *
  • Posts: 51
Re: How do we pass an argument to a subroutine?
« Reply #11 on: April 20, 2013, 06:24:53 PM »
Hi Frank,

I'm not sure whether there's an equivalent "not-called" label for Windows or not...

a good and clean way to finish a Windows application is always:

Code: [Select]
        call        ExitProcess

It could exist other, not documented ways, but I would use this method.

Gerhard