Author Topic: how to create and use a variable simple howto in nasm please  (Read 15872 times)

Offline HELLFIX

  • Jr. Member
  • *
  • Posts: 6
im looking for a simple way to make a varaible and output the value using an
interrupt call.

i have this so far

[BITS 16]

[ORG 0100H]

[SECTION .data]

 variable db 5
 
[SECTION .text]

 Start:
 
   mov dx, [variable]
   mov ah, 09h
   int 21h   
   
   mov ax, 04c00h
   int 21h
 
this does not work

please help

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: how to create and use a variable simple howto in nasm please
« Reply #1 on: May 03, 2012, 03:58:04 AM »
int 21h/9 expects the address of a '$'-terminated string in dx, not the number 5.

Easy way:
Code: [Select]
; nasm -f bin -o myfile.com myfile.asm

bits 16 ; the default for "-f bin" anyway
org 100h ; tell Nasm where dos is going to load us

section .data
variable db "5$"

section .text
mov dx, variable ; address, no "[]"s!
mov ah, 9
int 21h
ret ; or int 21h/4Ch
In an OS that supports dos, that ought to work. But I suppose what you want to do is convert a number to a string and display the string...

Code: [Select]
; prints the value of ax as decimal, hex, and binary ascii
; nasm -f bin -o showax.com showax.asm

org 100h

section .data
variable dw 12345

section .text
    mov ax, [variable] ; "[contents]" of variable

    call ax2dec
    call newline
    call ax2hex
    call newline
    call ax2bin

    mov ah, 4Ch
    int 21h
;--------------

;--------------
ax2dec:
    push ax
    push bx
    push cx
    push dx

    mov bx, 10          ; divide by ten
    xor cx, cx          ; zero our counter
.push_digit:
    xor dx, dx          ; clear dx for the div
    div bx              ; dx:ax/bx -> ax quotient, dx remainder
    push dx             ; save remainder
    inc cx              ; bump digit counter
    or ax, ax           ; is quotient zero?
    jnz .push_digit     ; no, do more

    mov ah, 2           ; print character subfunction
.pop_digit:
    pop dx              ; get remainder back
    add dl, '0'         ; convert to ascii character
    int 21h             ; print it
    loop .pop_digit     ; cx times

    pop dx
    pop cx
    pop bx
    pop ax
    ret
;-------------------

;-------------------
ax2hex:
    push cx
    push dx

    mov cx, 4           ; four digits to show

.top
    rol ax, 4           ; rotate one digit into position
    mov dl, al          ; make a copy to process
    and dl, 0Fh         ; mask off a single (hex) digit
    cmp dl, 9           ; is it in the 'A' to 'F' range?
    jbe .dec_dig        ; no, skip it
    add dl, 7           ; adjust
.dec_dig:
    add dl, 30h         ; convert to character

    push ax
    mov ah, 2           ; print the character
    int 21h
    pop ax

    loop .top

    pop dx
    pop cx
    ret
;--------------------------

;--------------------------
ax2bin:
    push cx
    push dx
    mov cx, 16
.top
    rcl ax, 1     ; rotate and set/clear carry
    mov dl, '0'
    adc dl, 0     ; make it '1' if carry set

    push ax
    mov ah, 2     ; print it
    int 21h
    pop ax

    loop .top

    pop dx
    pop cx
    ret
;----------------------------

;----------------------------
newline:
    push ax
    push dx

    mov ah, 2       ; print character in dl
    mov dl, 13      ; carriage return
    int 21h
    mov dl, 10      ; and linefeed
    int 21h

    pop dx
    pop ax
    ret
;----------------------------

That calls the OS for each character - pretty inefficient! Better to save each character in a string as you get it (stosb?) and display that. Don't forget to terminate your string with '$' if you plan to use int 21h/9!

Best,
Frank


Offline HELLFIX

  • Jr. Member
  • *
  • Posts: 6
Re: how to create and use a variable simple howto in nasm please
« Reply #2 on: May 03, 2012, 10:00:56 PM »
can you show me a simple way of using the stack push and pop calls i want to know what your doing to the stack im very interested in how it works

Offline HELLFIX

  • Jr. Member
  • *
  • Posts: 6
Re: how to create and use a variable simple howto in nasm please
« Reply #3 on: May 03, 2012, 10:27:02 PM »
And thanks again for the tip best regards

Offline Frank Kotler

  • NASM Developer
  • Hero Member
  • *****
  • Posts: 2667
  • Country: us
Re: how to create and use a variable simple howto in nasm please
« Reply #4 on: May 05, 2012, 12:42:56 AM »
Well... "the stack" is a perfectly ordinary piece of memory, distinguished only by the fact that ss:sp (or esp or rsp) point to it. You probably know that the stack works downward, from high addresses in memory to low addresses. The memory isn't "upside down" or anything, but the instructions that implicitly use memory - call, ret, push, pop - decrement sp (or esp or rsp) by 2 (or 4 or 8) when putting something "on" the stack (call and push) and decrement sp/esp/rsp by 2/4/8 when taking something "off" the stack...

Code: [Select]
; prints the value of ax as decimal, hex, and binary ascii
; nasm -f bin -o showax.com showax.asm

org 100h
Since we're a .com file, dos picks a single segment (lowest one available, usually) for our entire program. The first 256 (100h) bytes are the "Program Segment Prefix" (PSP). This isn't part of our on-disk .com file - dos creates it as it loads us. The first two bytes are 0CDh, 20h - int 20h - the "return to dos" interrupt. There are some other "interesting" parts of the PSP (segment address of our environment variables, for example)...

The stack is at the top of our one-and-only segment. Dos sets sp to zero and pushes a zero, so our stack looks like:

xxxx:FFFE 0000

Code: [Select]
section .data
variable dw 12345

section .text
    mov ax, [variable] ; "[contents]" of variable

    call ax2dec

The "call" puts the return address (106h in this case) on the stack...

xxxx:FFFE 0000
xxxx:FFFC 0106

... and execution continues at the new address. When we get to the "ret" in our subroutine, the return address is removed from the stack (we're back to just the zero), and execution continues from "call newline".

In the subroutine, I save and restore all the registers that I alter. This isn't necessary - it is usual to return a "meaningful value" in ax, other registers can be preserved (bp, bx, si, and di usually) or trashed. Since I wanted to be able to use this for debugging purposes with minimal side-effects, I save 'em all - pusha and popa could have been used (shorter but slower, probably), but I did it this way...

Code: [Select]
ax2dec:
    push ax
xxxx:FFFE 0000 ; from dos
xxxx:FFFC 0106 ; return address from subroutine
xxxx:FFFA 3039 ; old ax - 12345 decimal
Code: [Select]
    push bx
xxxx:FFFE 0000 ; from dos
xxxx:FFFC 0106 ; return address from subroutine
xxxx:FFFA 3039 ; old ax - 12345 decimal
xxxx:FFF8 0000 ; old bx - probably zero?
Code: [Select]
    push cx
xxxx:FFFE 0000 ; from dos
xxxx:FFFC 0106 ; return address from subroutine
xxxx:FFFA 3039 ; old ax - 12345 decimal
xxxx:FFF8 0000 ; old bx - probably zero?
xxxx:FFF6 ???? ; old cx
Code: [Select]
    push dx
xxxx:FFFE 0000 ; from dos
xxxx:FFFC 0106 ; return address from subroutine
xxxx:FFFA 3039 ; old ax - 12345 decimal
xxxx:FFF8 0000 ; old bx - probably zero?
xxxx:FFF6 ???? ; old cx
xxxx:FFF4 ???? ; old dx

Now we can use these registers for our own purposes, and restore 'em to their original values later...

Code: [Select]
    mov bx, 10          ; divide by ten
    xor cx, cx          ; zero our counter
.push_digit:
    xor dx, dx          ; clear dx for the div
    div bx              ; dx:ax/bx -> ax quotient, dx remainder
    push dx             ; save remainder

Since we get these remainders in the opposite order from which we eventually want 'em, pushing them on the stack and popping them off is a convenient way to reverse the order (there are other ways to do this). When we're done looping through this section, the stack will look like this:

xxxx:FFFE 0000 ; from dos
xxxx:FFFC 0106 ; return address from subroutine
xxxx:FFFA 3039 ; old ax - 12345 decimal
xxxx:FFF8 0000 ; old bx - probably zero?
xxxx:FFF6 ???? ; old cx
xxxx:FFF4 ???? ; old dx
xxxx:FFF2 0005 ; first remainder
xxxx:FFF0 0004 ; etc.
xxxx:FFEE 0003
xxxx:FFEC 0002
xxxx:FFEA 0001

At this point, the quotient in ax is zero...
Code: [Select]
    inc cx              ; bump digit counter
    or ax, ax           ; is quotient zero?
    jnz .push_digit     ; no, do more

    mov ah, 2           ; print character subfunction
.pop_digit:
    pop dx              ; get remainder back

Now we get the digits back in the "proper" order, one at a time. Note that we don't need to pop the same register that we pushed - it just works out that way for int 21h/2. If we wanted to use "stosb" or "int 29h" we could have used "pop ax" just as well. Also note that we're only interested in a single byte, but we can't push/pop a single byte - only 2 bytes (or 4 or 8 in 32- or 64-bit code - there are some options to this, we can actually push/pop 4 bytes in 16-bit code or 2 bytes in 32-bit code. I think we can still push 2 bytes, but not 4 bytes, in 64-bit code. Better to stick to the "native" size, to avoid confusion, IMO). In any case, once we've popped the cx digits we counted while pushing them, the stack is back to:

xxxx:FFFE 0000 ; from dos
xxxx:FFFC 0106 ; return address from subroutine
xxxx:FFFA 3039 ; old ax - 12345 decimal
xxxx:FFF8 0000 ; old bx - probably zero?
xxxx:FFF6 ???? ; old cx
xxxx:FFF4 ???? ; old dx

Code: [Select]
    add dl, '0'         ; convert to ascii character
    int 21h             ; print it
    loop .pop_digit     ; cx times

    pop dx
xxxx:FFFE 0000 ; from dos
xxxx:FFFC 0106 ; return address from subroutine
xxxx:FFFA 3039 ; old ax - 12345 decimal
xxxx:FFF8 0000 ; old bx - probably zero?
xxxx:FFF6 ???? ; old cx
...and dx is back to its original value...
Code: [Select]
    pop cx
    pop bx
    pop ax
etc...
xxxx:FFFE 0000 ; from dos
xxxx:FFFC 0106 ; return address from subroutine

At this point, the next thing on the stack had damwell better be our return address, or we're "off in the weeds" - probably crash!
Code: [Select]
    ret

... and we're back to where we were called from, with just the zero that dos put on the stack.

I don't save/restore ax in the other subroutines, since after being rotated the correct number of times it's back to its original value anyway. I've probably made typos in the above, but it should give the general idea...

The stack is also used for passing parameters. I may get to that... if I get to it. :)

Best,
Frank