NASM - The Netwide Assembler
NASM Forum => Programming with NASM => Topic started by: maplesirop on April 16, 2013, 01:22:28 AM
-
I thought I knew a subroutine was, but now I am not sure.
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?
-
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...
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
-
Thanks for replying.
Do we absolutely need ret or can we just use a jump statement?
-
Fundamentally, that's the only thing that exists is jmps or if you will goto. So;
push .NX
jmp Routine ; Mimics call Routine
.NX nop
Routine pop ax
jmp ax ; Mimics return
functionally equivalent to
call Routine
Routine ret
Here is an example of a dynamically calculated code segment, so I've emulated a far call.
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
-
; ----------------------------------------------------------------------------
; 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?
-
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):
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
-
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?
_main:
push message
call _printf
add esp, 4
ret
-
"_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...
_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
-
Hi maplesirop,
_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
-
Sorry Frank, haven't seen that you've already answered the question.
Gerhard
-
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...
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...
...
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
-
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:
call ExitProcess
It could exist other, not documented ways, but I would use this method.
Gerhard