Author Topic: A simple subprogram  (Read 19824 times)

Offline sledge

  • Jr. Member
  • *
  • Posts: 19
A simple subprogram
« on: August 11, 2011, 09:15:54 AM »
The intention of this code is to make a subprogram called introduce, it takes one parameter, which is a pointer to a $-terminated string, its supposed to be a person's name, and the subprogram outputs "My name is [argument1]"

Code: [Select]
org 100h

push dword name
call introduce

mov ah, 4Ch
int 21h

name db 'Johnny', '$'

introduce:

push ebp
mov ebp, esp

mov ah, 9
mov dx, str1
int 21h

mov ah, 9
lea dx, [ebp+8] ; dx = argument1
int 21h

pop ebp
ret

str1 db 'My name is ', '$'

I'm certain that the problem with this code is when using the LEA instruction, though i still cant figure the correct way to use it in my code.
« Last Edit: August 11, 2011, 09:19:35 AM by sledge »

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 429
  • Country: us
Re: A simple subprogram
« Reply #1 on: August 11, 2011, 11:07:46 AM »
DX probably (?) contains zero since you are trying to push a dword onto the stack but only loading a word into DX.
Since this is a DOS .com file you're building addresses are only 16-bit offsets.
Since the address of the string is already on the stack you can simply change lea to mov.
Since function introduce is rather small you can eliminate the push/pop ebp ops.
Combine all that and you get:
Code: [Select]
mov dx, word [esp+2] ; dx = argument1


Offline sledge

  • Jr. Member
  • *
  • Posts: 19
Re: A simple subprogram
« Reply #2 on: August 14, 2011, 05:25:32 AM »
Well, here's the new version of my code (which is working fine but I still have some doubts.)

Code: [Select]
org 100h

push name1 ; I wonder if whats being pushed on stack is a 16bit or 32bit value, help!
call introduce
sub esp, 4 ;goes back on stack 4 bytes, since its 32bit stack (extended)

push name2
call introduce
sub esp, 4

push name3
call introduce
sub esp, 4

mov ah, 4Ch
int 21h

name1 db 'Johnny', '$'
name2 db 'Maria', '$'
name3 db 'Peter', '$'

introduce:

mov ah, 9
mov dx, msg
int 21h

mov ah, 9
mov dx, word [esp+2]
int 21h

mov dx, msg_end
int 21h

ret

msg db 'Hello, my name is ', '$'
msg_end db '!', 13, 10, '$'

Since all the registers in DOS mode is 16bits, how come theres no 16-bit SP register (stack pointer)?
I could only make this code using ESP (32), when I tried using SP it didnt work!

I left some comments on the code so it becomes clear what my doubt is.
Btw, I'm using the dos version of nasm!

Offline Rob Neff

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 429
  • Country: us
Re: A simple subprogram
« Reply #3 on: August 14, 2011, 01:53:54 PM »
The sub esp, 4 is wrong here.  On x86 the stack grows downward from high to low memory.  A push will decrement the stack pointer and then store the contents.  A call will decrement the stack pointer and store the return address.

The ret opcode pops the return address off of the stack.  Following the call is when you add esp, 2.  You don't mention the command line parameters you gave Nasm so I'm assuming -f bin.

Here is an example of parameter pushing, function call and stack cleanup:

Code: [Select]
    push  a_string_addr
    call  string_function
    add   esp, 2     ; 4 for 32-bit

Hope that helps.


Offline sledge

  • Jr. Member
  • *
  • Posts: 19
Re: A simple subprogram
« Reply #4 on: August 14, 2011, 07:46:06 PM »
I see, so it should add 2 to the stack pointer instead of sub.
Thanks, things are less confusing now.

So, when pushing a value to the stack in DOS, will it push it as 2bytes or 4bytes?
When you did "push a_str_addr", is this labed pushed as 2 or 4 bytes? I gotta know this because after calling the subprogram I will restore the stack pointer by adding ESP.

By the way, is there any way to keep track of all the registers and whats going on in memory runtime when running this code in DOS (I'm using Windows7 with Dosbox)

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: A simple subprogram
« Reply #5 on: August 14, 2011, 08:11:13 PM »
Quote
Since all the registers in DOS mode is 16bits, how come theres no 16-bit SP register (stack pointer)?

You've got a 16-bit stack pointer allright, what doesn't work is "[sp]". Using 16-bit instructions/registers, addressing modes are very limited: an (optional) offset, plus an (optional) base register, plus an (optional) index register. Base registers are bx and bp, index registers are si and di. That's it! We can't even do "mov al, [si + di]"! "bp" is "special" in that it defaults to [ss:bp] - the rest of 'em use ds (by default).

We can use 32-bit instructions/registers, even in 16-bit code. There's an "override prefix" involved (66h for operand size and 67h for address size), but Nasm takes care of generating them for us. 32-bit addressing modes are much more flexible! Any (general purpose) register can be used as "base", and any but esp can be used as "index", and we can add a "scale" factor to be multiplied by the "index" register - "* 1" is implied if we don't specify one, or "* 2", "* 4", or "* 8" can be used. Very handy for addressing arrays of words, dwords (or single precision floats), or double precision floats (or other qword values). 16-bit addressing is a real PITA, and we're generally glad to forget it once we move to 32-bit code. There's a limitation to using 32-bit instructions/registers/addressing in 16-bit code - the "total offset" can't exceed 64k. Doing so causes a "segment overrun exception", which dos doesn't handle, so it usually hangs the machine. There's a workaround to this - "Flat Real Mode" (among other names), but I won't get into it here - you have to be in "real real mode" to do it, and we're often not.

As Rob explains, you're only pushing two bytes in this code, so add (not sub) 2 to "clean up the stack". It probably works anyway, since you don't really "need" the stack to be "clean", but it isn't "right" (as you suspected). As I recall, when you first started using [esp + ?], you were pushing eax, which is four byes - Nasm generated that 66h prefix to make it so. I probably should have explained more then... but it was working...

To do a subroutine with parameters on the stack, in "all 16-bit" code...

Code: [Select]
myfunction:
  push bp ; save caller's reg
  mov bp, sp ; create a "stack frame"
; sub sp, ? ; if you want "local variables"
  mov dx [bp + 4] ; get the first parameter
  ; do whatever
  mov sp, bp  ; not necessary if no local variables
  pop bp ; restore caller's reg
; now the next thing on the stack is our return address, so we're ready to...
  ret

There's a further complication, "call far". If the subroutine is in a different segment than the calling code, both cs and ip are used, and both segment and offset are part of the return address, so the first parameter is at [bp + 6] (and you end with "retf"). You probably don't need this in a .com file, and it's rarely used in 32-bit code, so you probably don't need to worry about it, but be aware that it's a possibility.

Assembly language is really very simple, but you need to know a lot of "simple things" all at once in order to do anything, so it can seem more complicated at first that it really is (IMO).

Best,
Frank