Search code examples
assemblydosx86-16tasmreal-mode

TASM program outputs garbage and may hang upon exit


I have the following code, which takes a number in hexadecimal format and prints out its decimal format.

This program runs kinda fine in Turbo Debugger, but when I run it in DOS, I see some extra symbols in output after my number output:

.model small
.stack 100h
.486
.code

save proc
    itm:
        xor dx,dx
        mov bx,10
        div bx
        add dl,30h
        inc cx
        pop bx
        push dx
        push bx
        cmp ax,0
        jne itm
   ret
save endp

start:
    mov ax, 0FFFFh
    call save
    mov ah, 02
    print:
        pop dx
        int 21h
        loop print
       int 20h
end start

Output:

C:\TASM>lab31 65535 ò Φ■╤  9°☻░╧♠UWSîÄPA÷0ó♥┴∞└εê$♦ó♥α♦▲ê$ó♦Σê←ë♦ó♥☻╨sÄ÷♣t╣ ╞ ÷┤ⁿ8sê¬ê²≤&mî│░ⁿ┘╗♥÷ t<sÿ☻╪╟♣I☼>♥b!├─4&Gê&_ëΩî∞[┴éΦ z│Φ ☺\│Φ ♀fδ[♥3¡ïA1èG┴├═≥uè ç♦└┌é─Ω╕↕ëX╪♥♦♫↕Y^▼Z╖ ←tÇ5▲♦▼δá♦├☻├ █ ☻┬! C└(A∞1▬:↕ÿ├ƒ♥╞[%█☼C└≥░Φ 1357                46$♦♦

As you can see 65535 prints ok, but then garbage appears. When I run the program in Turbo Debugger, it hangs after writing out 65535.


Solution

  • There are a couple of problems with your code. You use CX as your character counter, however in this code you increment without initialization:

    save proc
        itm:
            xor dx,dx
            mov bx,10
            div bx
            add dl,30h
            inc cx                 ; increment CX, without initialization
            pop bx
            push dx
            push bx
            cmp ax,0
            jne itm
       ret
    save endp
    

    To fix this, you can set CX to zero by initializing it outside the main loop in your save procedure:

    save proc
            xor cx,cx              ; Explicitly clear CX counter.            
        itm:
            xor dx,dx
    

    To see information about the state of registers when an EXE loads see the bottom of this article in section register contents at program entry:

    Register  Contents
    AX        If loading under DOS: AL contains the drive number for the first FCB 
              in the PSP, and AH contains the drive number for the second FCB.
    BX        Undefined.
    CX        Undefined.
    DX        Undefined.
    BP        Undefined.
    SI        Undefined.
    DI        Undefined.
    IP        Initial value copied from .EXE file header.
    SP        Initial value copied from .EXE file header.
    CS        Initial value (relocated) from .EXE file header.
    DS        If loading under DOS: segment for start of PSP.
    ES        If loading under DOS: segment for start of PSP.
    SS        Initial value (relocated) from .EXE file header.
    

    Register CX is considered undefined (so is BP,BX,DX,SI,DI) upon entry to your EXE program. AX likely will be non-zero since it is used to pass information to your EXE. Because CX is undefined it may or may not contain 0.

    Sometimes when you run a debugger against an executable the value of CX might be different than running without the debugger. In the case of Turbo Debugger, CX appears to be zero upon entry. If you run your program outside the debugger it may not be zero, and would cause problems like you encountered. I recommend initializing your general purpose registers before using them.

    As for the hang at the end of your program that is because you are using int 20h. Your code suggests you are generating .EXE file (not .COM). The typical way to exit an .EXE program is to use int 21h where AH=04ch and AL is your exit code. If you replace int 20h with the following code it will exit with return value of 0:

       mov ax, 4c00h
       int 21h
    

    With TASM you can also use the .exit ndirective (n = exit return value) as an alternative. This should generate the appropriate assembler code to exit back to DOS.

    int 20h is often used (retn more common) in .COM programs. int 20h relies on CS:0 being the address of the PSP block. This will be true by default in a .COM program, but in an .EXE program this isn't the case. More information on this can be found here and here:

    Int 21h Function 4Ch

       Notes:         It is best to use INT 21h Function 4Ch to exit from
                      '.exe' programs since Function 4Ch doesn't require
                      that CS point to the PSP.
    

    Int 20h

       Notes:         This function is an historical remnant offering no
                      advantages over Function 4Ch. It's better to use
                      function 4Ch, which returns an error code that other
                      programs can access (via Function 4Dh or the
                      ERRORLEVEL statement in batch files); also, CS need
                      not be set first when using Function 4Ch. (INT 20h
                      is equivalent to Function 0.)