Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

subroutine queery

Status
Not open for further replies.

aldeburan

Technical User
Sep 19, 2002
8
GB
hello can anyone tell me the syntax for declaring, calling and returning from a subroutine. Thanx
 
aldeburan,

In a primitive ways declare a subroutine is just the same as a location.
MyProc:
mov ax,bx

Calling it simply use a "call" command:
call MyProc

and for return syntax is "ret". Here is a simple one.
jmp start
MyProc:
mov ax,bx
ret
start:
call MyProc
END

A later compiler can use a procedure to make asm program more organize, i.e:
MyProc1 PROC
mov ax,bx
ret
ENDP

MyProc2 PROC NEAR
mov ax,bx
ret
ENDP

and call it with:
call MyProc1
call MyProc2

You can also add parameter in the procedure, i.e.:
MyProc PROC value:WORD
mov ax, value
ret
ENDP

and call it with:
push reg/mem/immediate
call MyProc

In a latest MASM you can also call it with:
invoke MyProc, 1

Other compilers maybe have their ways. But as long as you use push & call, any compiler will accept it (I think)

Sorry if there's a wrong explanation :)
Regards


-- AirCon --
 
I suppose it would be easier if i gave an example of my code. Its the same code im trying to write with keys 1-8 playing musical notes. Someone recommended me to use jmp commands to a 'mainroutine' to reduce the size of code, however i cant always get the program to jump back to the original instruction without being in endless loops. So what i want to do is use subroutines, then the prog will return to the next instruction in the main block.
Heres a sample:

.model large
.stack 100h
.data
prompt DB 'Press 1-8 to play notes! Press x to exit!',13,10,'$'
.code
mov ax,@data
mov ds,ax
jmp rowrow
mainroutine proc
pop ax
out 42h,al
pop ax ;popping them back off again
out 42h,al ;and loading into port 42h
in al,61h
or al,00000011b
out 61h,al ;turn on speaker
RET

rowrow:
mov al,10110110b
out 43h,al
mov al,97h
push ax ;push LSB for notes reverse order
mov al,0ah
push ax ;push MSB for the notes
CALL mainroutine
CALL delay3
CALL turnoffspeaker


**
**
code
**
END

This doesnt work - you may see it as obvious but i am just a beginner. Do you have to push the address of the instruction you want to return to? if so im pushing other values onto ax that i need, is there another way i can do this?
I put the 'mainroutine' subroutine at the beginning only because i seen that is what you done, is this right?.
I am not getting any errors when i assemble but when i run the program i get booted out of dos and i need to restart my PC.
What does 'mov ax,bx' do?, whats its function here? since i see i have missed it out.
If your wondering how im getting the values for the notes it's the clock frequency, 1193180Hz divided by the frequency of the note required, i.e. for concert A, where all musical scales are taken from, 1193180/440 = 2711d = A97h, this value is loaded into counter 2 of the PIT 8253/8254 byte at a time, then the speaker is switched on by oring 00000011 with bit 0 and 1 of port B (61h), since we dont want to change any of the other bits.

Any advice you could give on the above would be greatly appreciated.
 
Seems you have mixed up the routine & procedure. But I'm not sure it is the problem or not. Lets change this first:

mov ds,ax
jmp rowrow
mainroutine proc
pop ax

into:

mov ds,ax
jmp rowrow
mainroutine:
pop ax

Try again, and learn to use debug
About 'mov ax,bx' is nothing but an example :)


-- AirCon --
 
Hm, not easy to explain this.
In assembler you are largely responsible for everything the processor does. If you write a procedure using PROC, all that's really happening is you are telling the assembler to remember its name, so this location can be called by name later.
So a piece of code starting
MyName PROC NEAR
.....
and ending
MyName ENDP
is simply a chunk of code. The assembler won't even put the return at the end of it (well, my ancient copy of tasm doesn't, anyway). This isn't a weakness of the assembler. It's what assembler is all about: you only get what you write, and you can write whatever you want.

To make something function as a subroutine, it needs to end by returning to the code that called it. To do this, you put ret at the end of your procedure. This will be interpreted as ret near or ret far according to whether you defined a PROC NEAR or PROC FAR. But the assembler won't object if you actually override it and specify retf at the end of a near procedure!

Ret returns to the address put on the stack, which is an offset in all cases, and also a segment if the call was far. So retf takes both an offset and a segment from the stack, while retn takes just an offset. The point of a call instruction is to put the appropriate offset and segment on the stack before it does a jump.

You can still call a procedure using a jump rather than a call, but a jump, unlike call, doesn't save the return address on the stack. Therefore at the end of the procedure there will be a horrible crash unless you explicitly put some reasonable address on the stack. There are odd occasions you might do this, for instance to make a procedure "return" to some other place in your code, not the instruction following the jump.

You are right to think about structure in your code. In my view it is even more important in assembler, which is basically hard to read, than in high level languages, which shouldn't be! But I'd try to keep things really simple in your procedure structure.

By the way, I'm not really very sure about your prompt-printing bit. Are you sure you're addressing the line of text correctly?

On procedures, there are all sorts of more complicated structures that tend to appear at the begining and end of procedures, especially when created by higher-level languages. These are things like
push bp
mov bp, sp
etc., which are involved in setting up bp so that it can be used to access variables pushed onto the stack.
 
Thanx lionelhill, i managed to get it working, heres what i done:

.model large
.stack 100h
.data
prompt DB 'Press 1-9 to play notes in the scale of D!',13,10,'$'
exit DB 'Press x to exit!',13,10,'$'
.code
mov ax,@data
mov ds,ax
jmp firstnote

public mainroutine
mainroutine proc near
pop bx
pop ax
out 42h,al
pop ax
out 42h,al
in al,61h
or al,00000011b
out 61h,al
push bx
RET
mainroutine endp

firstnote:
mov al,10110110b
out 43h,al
mov al,011h
push ax
mov al,0cah
push ax
CALL mainroutine
CALL delay1
CALL offspeak
CALL pause
"
"
rest of code
"
"
END

obviously i have more procedures in the code than i have highlighted here.
My problem was that i was passing data between main and the procedure. I was storing them onto the stack for this. But when you call the procedure the return address gets stored on the top of the stack which would then muck up my data that i was passing. So to combat this, when i first enter the procedure i popped the return address into bx, since bx is not being used, then just before the RET command i pushed it back onto the top of the stack and this restored the return address, woo hoo!

Regarding your statement about my prompt, i never included the command in my example code that displays the code, which goes like;

mov dx, OFFSET prompt
mov ah,9
int 21h
mov dx, OFFSET exit
mov ah,9
int 21h

And they display OK, thanx for your concern and advice.
 
My problem was that i was passing data between main and the procedure. I was storing them onto the stack for this. But when you call the procedure the return address gets stored on the top of the stack which would then muck up my data that i was passing ....

At first I already figured that something wrong with the stack, but I didn't know how to explain cause I didn't know the exact result. That's why I asked you to learn debug. I guess you already tried it, aren't you ? Debugger will you best friend in asembly world :)
Now since you have found out, lets try this one (I'm trying to avoid push & pop by using register only)

mainroutine proc near
mov ax, dx
out 42h,al
shr ax,8
out 42h,al
in al,61h
or al,00000011b
out 61h,al
RET
mainroutine endp

firstnote:
mov al,10110110b
out 43h,al

; mov al,011h
; push ax
; mov al,0cah
; push ax

mov dx, 011CAh
CALL mainroutine

mov dx, ????h
CALL mainroutine

Let me know the result


-- AirCon --
 
Aircon is right, you can avoid pushing and popping by using the registers.
Alternatively if you cannot get all the data you need in registers, you can use the stack as you suggest, but there is a much better way to access things you've put on the stack, and that is to use bp.

The way you do this, is at the begining of your subroutine you set bp to be equal to the stack pointer. Conventionally you push bp first in case you ever find yourself working in nested subroutines, where the subroutine above where you are at the moment might itself be using bp in the same way. Hence a subroutine includes at its start
push bp
mov bp, sp
You'll find that the stack now contains:
+08: a word you pushed
+06: segment part of return address
+04: offset part of return address
+02: old bp
I might have slipped 2 here. Do check. I always get confused whether the stack pointer is decremented before or after pushing.... sorry I'm a bit thick.
Or something similar depending on whether you were making near or far calls.
Hence you can now get at your passed (pushed!) parameters with statements like
mov ax, ss:[bp+08]
bp defaults to using stack segment for exactly this purpose.
If you need more local storage space internally to your subroutine, you can even extend this by subtracting a lump from sp in order to reserve a little local stack space.
push bp
mov bp, sp
sub sp, 06
This space can now be addressed as ss:[bp-6] etc....
Either way, at the end of your subroutine you must restore the old bp and get the sp back to where the return address is before doing the return. You can do this simply by doing
mov sp, bp
pop bp
ret however....

The final ret instruction can free up space on the stack taken by your pushed variables.

I'm not advocating imitating a compiler (otherwise there wouldn't be a lot of point in writing assembler!) but this sort of local stack frame structure is very powerful, and worth knowing about. If you're bursting into assembler to make a recursive routine more efficient, though, try to avoid it.

Just a note on "enter" and "leave". These are Intel's realisation just how important local stack frames are, and do exactly what I've described above. However, they're slower than the individual instructions, so not recommended.
 
yes, the whole purpose of the bp register is to access parameters passed to a called routine.

however, recent complexity of routines are requiring alot more paramters to be passed. have you looked at the enter and leave opcodes? these opcodes are specifically used to pass a block of paramters to called routines.

anyhow, i beleive the favoured method as with the modern day BIOS is to use a paramter table and only pass the pointer of this table to the routine such as DS:SI - of corse there are many methods of passing parameters.

you just have to decide which you prefer :)
straiph


"There are 10 types of people in this world, those who know binary and those who don't!"
 
I think enter and leave simply do the same as push bp, mov sp, bp and all that stuff, but with less code, and a little more execution time. But I can't swear to how fast they run on good up-to-date processors. I'm still kicking around in early pentium world on that one.

Yes, passing a parameter by reference, rather than by value, is a good time-saver if you've a lot to pass. Straiph is quite right about that one. If you have several complex blocks of values you want to pass, you can of course push their addresses and retrieve them with les si, ss:[bp+something], or whatever.

And in assembler there's still a good case for the odd global variable! You can use that from anywhere without passing anything, so it may not make for easy-read code, but there's nothing quicker.

 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top