Question

I'm learning assembler (TASM on 16 bit DOS) and try to use 0Ah DOS service to read text directly into stack. It works pretty well in emu8086, while when I run it with actual TASM - it does not give any user input (no input at all, seems like skipping INT 21h at all).

Here's how I'm using it:

PROC _readNum USES BP AX BX CX DX SI
    PUSH BP
    MOV BP, SP
    SUB SP, 7

    MOV AH, 0Ah ;Buffered input
    MOV [BP-7], 5 ;Max number of characters to read (4 + 1 for enter)
    LEA DX, [BP-7]
    INT 21h ;This interrupt seems to be doing nothing at all
    ...

What can be the problem? Am I referencing the stack in wrong way? Thanks in advance.

Here's full code just in case:

ascii_offset EQU 30h
.model small
.stack 100h
.data
    ; add your data here!
    outStrA DB "Input A: $"
    outStrB DB "Input B: $"
    resultStr DB "Result of $"
    plusStr DB "+$"
    equalsStr DB " is $"
    eol DB 13, 10, "$"

    cnt DB 10
    rcnt DB 0
    buf DB 11 dup("$")

PRINT MACRO op1
    MOV AH, 09h
    LEA DX, op1
    INT 21h
ENDM

PRINTLN MACRO op1
    PRINT op1   
    PRINT eol
ENDM

PRINTEOL MACRO
    PRINT eol
ENDM

.code

PROC _printNum USES BP AX BX DX SI
    PUSH BP
    MOV BP, SP
    SUB SP, 6 ;Max number to print is 5 + $ sign    

    MOV AX, [BP+2]
    MOV DX, 0h ;Is required to divide a double pair
    MOV BH, 0h
    MOV BL, 10 ;Divisor

    LEA SI, [BP-1] ;Our string stored in memory (from end)
    MOV byte ptr [SI], "$" ;End of str
    _printNum_loop:
    DIV BX ;Result is in AX:DX
    ADD DL, ascii_offset ;Convert to ASCII
    DEC SI
    MOV [SI], DL    
    MOV DX, 0h ;Reset DX to divide again
    CMP AX, 0h ;If AX is 0
    JNE _printNum_loop

    PRINT [SI]

    MOV SP, BP
    POP BP
    RET 2
ENDP

PROC _readNum USES BP AX BX CX DX SI
    PUSH BP
    MOV BP, SP
    SUB SP, 7

    MOV AH, 0Ah ;Output to screen
    MOV [BP-7], 5 ;Max number of characters to read (4 + 1 for enter)
    LEA DX, [BP-7]
    INT 21h 

    MOV AX, 0h  ;Result
    MOV BX, 0h  ;Temporary result
    MOV CX, 0h  ;Loop counter   
    MOV CL, [BP-6] ;Loop counter
    LEA SI, [BP-5] ;Starting position to read number
    _readNum_strloop:
        MOV DX, 10 ; ;Will multiply AX by DX
        MUL DX ; AX = AX * DX
        MOV BL, [SI]        
        SUB BL, 30h
        ADD AX, BX
        INC SI  
    LOOP _readNum_strloop

    MOV SP, BP
    POP BP
    RET 0
ENDP

start:
; set segment registers:
    MOV AX, @data
    MOV DS, AX
    MOV ES, AX

    PUSH 0ABCDh
    JMP _printNum

    MOV AX, 4c00h ; exit to operating system.
    INT 21h    

END start ; set entry point and stop the assembler.
Était-ce utile?

La solution

To elaborate on Alexey Frunze's answer:

Your code specifies a SMALL model. This directive tells the assembler to assume, among other things, that DS = SS, but it doesn't (can't) enforce it.

When DOS loads an EXE, it sets SS:SP to a location found in the file header. It sets DS to the beginning of the PSP so you can get at command-line arguments and so forth.

The DOS buffered read function expects the buffer to be at DS:DX. You've properly loaded DX with BP-7 (the start of the buffer), but you also need to set DS ← SP before making the function call. You can do that with

MOV AX,SS
MOV DS,AX

or

PUSH SS
POP DS

There is no MOV DS,SS instruction.

Autres conseils

There might be a few bugs. Are you able to compile this?

PROC _printNum USES BP AX BX DX SI
    PUSH BP
    MOV BP, SP
    SUB SP, 6  ; Max number to print is 5 + $ sign    

Here you modify sp even if you don't use sp for addressing or a as source before mov sp,bp in the end of this procedure, so this instruction is unnecessary.

    MOV AX, [BP+2]

This is not bug, just wondering... ax should now be 0xABCD, is this correct?

    MOV DX, 0h  ; Is required to divide a double pair
    MOV BH, 0h
    MOV BL, 10  ; Divisor

    LEA SI, [BP-1]          ; Our string stored in memory (from end)
    MOV byte ptr [SI], "$"  ; End of str

_printNum_loop:
    DIV BX                  ; Result is in AX:DX

The result after div bx is not in ax:dx. In ax you will have the quotient and in dx you will have the remainder.

Then here:

PROC _readNum USES BP AX BX CX DX SI
    PUSH BP
    MOV BP, SP
    SUB SP, 7

Why this sub sp,7? First, you should keep sp aligned to 2, and second, why you modify sp here if you don't use it for addressing or as source before mov sp,bp in the end of this procedure?

MOV AH, 0Ah    ; Output to screen
MOV [BP-7], 5  ; Max number of characters to read (4 + 1 for enter)

Here you should define the size of the operand, eg. mov byte [bp-7],5 or mov word [bp-7],5. Or with ptr, if TASM requires it, I don't know: mov byte ptr [bp-7],5 or mov word ptr [bp-7],5.

LEA DX, [BP-7]
INT 21h

This does not make sense. According to DOS INT 21h - DOS Function Codes documentation mov ah, 0x0a; int 21 is buffered input, and ds:dx should to point to the input buffer. Probably your comment "Output to screen" is incorrect?

MOV AX, 0h     ; Result
MOV BX, 0h     ; Temporary result
MOV CX, 0h     ; Loop counter   
MOV CL, [BP-6] ; Loop counter
LEA SI, [BP-5] ; Starting position to read number
_readNum_strloop:
    MOV DX, 10 ; Will multiply AX by DX
    MUL DX ; AX = AX * DX
    MOV BL, [SI]        
    SUB BL, 30h
    ADD AX, BX
    INC SI  
LOOP _readNum_strloop

I think in this loop you have a risk of overflow. The biggest number that fits in any 16-bit register such as ax is 2^16-1 == 65535.

I didn't go over all the code, but one problem is that your data and stack segments are different, that is, the program starts out with ds!=ss.

Therefore LEA DX, [BP-7] isn't going to provide the DOS input function with the correct address, because it's expected it in ds:dx and yet your buffer is at ss:dx and ds!=ss. The following int 21h call can then overwrite some memory and cause a hang or a crash.

You need to use proper addresses.

UPDATE: Some details.

Here's a slightly modified version of your program (the code in the question did not assemble well for me with TASM 3.2):

UPDATE2: Forgot to mention that all the changed lines contain comments starting with ;;;;.

ascii_offset EQU 30h
.model small, C ;;;; added ", C"
.stack 100h
.data
    ; add your data here!
    outStrA DB "Input A: $"
    outStrB DB "Input B: $"
    resultStr DB "Result of $"
    plusStr DB "+$"
    equalsStr DB " is $"
    eol DB 13, 10, "$"

    cnt DB 10
    rcnt DB 0
    buf DB 11 dup("$")

PRINT MACRO op1
    MOV AH, 09h
    LEA DX, op1
    INT 21h
ENDM

PRINTLN MACRO op1
    PRINT op1   
    PRINT eol
ENDM

PRINTEOL MACRO
    PRINT eol
ENDM

.code

_printNum PROC USES BP AX BX DX SI ;;;; reordered PROC and _printNum
    PUSH BP
    MOV BP, SP
    SUB SP, 6 ;Max number to print is 5 + $ sign    

    MOV AX, [BP+2]
    MOV DX, 0h ;Is required to divide a double pair
    MOV BH, 0h
    MOV BL, 10 ;Divisor

    LEA SI, [BP-1] ;Our string stored in memory (from end)
    MOV byte ptr [SI], "$" ;End of str
    _printNum_loop:
    DIV BX ;Result is in AX:DX
    ADD DL, ascii_offset ;Convert to ASCII
    DEC SI
    MOV [SI], DL    
    MOV DX, 0h ;Reset DX to divide again
    CMP AX, 0h ;If AX is 0
    JNE _printNum_loop

    PRINT [SI]

    MOV SP, BP
    POP BP
    RET 2
ENDP

_readNum PROC USES BP AX BX CX DX SI ;;;; reordered PROC and _readNum
    PUSH BP
    MOV BP, SP
    SUB SP, 7

    MOV AH, 0Ah ;Output to screen
    MOV byte ptr [BP-7], 5 ;Max number of characters to read (4 + 1 for enter) ;;;; added "byte ptr"
    LEA DX, [BP-7]
    INT 21h 

    MOV AX, 0h  ;Result
    MOV BX, 0h  ;Temporary result
    MOV CX, 0h  ;Loop counter   
    MOV CL, [BP-6] ;Loop counter
    LEA SI, [BP-5] ;Starting position to read number
    _readNum_strloop:
        MOV DX, 10 ; ;Will multiply AX by DX
        MUL DX ; AX = AX * DX
        MOV BL, [SI]        
        SUB BL, 30h
        ADD AX, BX
        INC SI  
    LOOP _readNum_strloop

    MOV SP, BP
    POP BP
    RET 0
ENDP

start:
; set segment registers:
    MOV AX, @data
    MOV DS, AX
    MOV ES, AX

    PUSH 0ABCDh
    JMP _printNum

    MOV AX, 4c00h ; exit to operating system.
    INT 21h    

END start ; set entry point and stop the assembler.

Assembled (with TASM 3.2) as tasm stkkbinp.asm without errors or warnings.

Linked (with TLINK 3.01) as tlink /m /s stkkbinp without errors or warnings.

The map file produced by the linker is:

 Start  Stop   Length Name               Class

 00000H 00088H 00089H _TEXT              CODE
 00090H 000C5H 00036H _DATA              DATA
 000D0H 001CFH 00100H STACK              STACK


Detailed map of segments

 0000:0000 0089 C=CODE   S=_TEXT          G=(none)  M=STKKBINP.ASM ACBP=48
 0009:0000 0036 C=DATA   S=_DATA          G=DGROUP  M=STKKBINP.ASM ACBP=48
 0009:0040 0100 C=STACK  S=STACK          G=DGROUP  M=STKKBINP.ASM ACBP=74

  Address         Publics by Name


  Address         Publics by Value


Program entry point at 0000:0070

Now opening the program in TD and executing first 3 instructions:

enter image description here

You can clearly see on the screen that ds isn't equal ss. They are 4 apart, first going .data segment at 0x4caf, then going .stack at 0x4cb3. This difference of 4 in terms of segment values is equivalent to 4*0x10=0x40 bytes.

If you look at the map file above, you will see that, indeed, that's the distance between these segments:

 0009:0000 0036 C=DATA   S=_DATA          G=DGROUP  M=STKKBINP.ASM ACBP=48
 0009:0040 0100 C=STACK  S=STACK          G=DGROUP  M=STKKBINP.ASM ACBP=74

I cannot explain why the map file shows the same segment for both (9), but the linked executable has them different. But that's why the input won't work properly.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top