在DOS中直接将文本读入堆栈

时间:2012-09-19 13:00:15

标签: assembly x86 dos 16-bit tasm

我正在学习汇编程序(16位DOS上的TASM)并尝试使用0Ah DOS服务将文本直接读入堆栈。它在emu8086中工作得很好,而当我用实际的TASM运行时 - 它没有给任何用户输入(根本没有输入,似乎根本就是跳过INT 21h。)

以下是我使用它的方式:

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
    ...

可能是什么问题?我是以错误的方式引用堆栈吗?提前谢谢。

以下是完整代码:

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.

3 个答案:

答案 0 :(得分:2)

可能存在一些错误。你能编译吗?

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

即使您在此过程结束时未在sp之前使用sp进行寻址或作为源,也修改mov sp,bp,因此不需要此说明。

    MOV AX, [BP+2]

这不是错误,只是想知道...... ax现在应该是0xABCD,这是正确的吗?

    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

div bx之后的结果不在ax:dx中。在ax中,您将获得商数,在dx中,您将获得余数。

然后在这里:

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

为何选择sub sp,7?首先,您应该将sp与2对齐,其次,如果您在此过程结束时未将sp用于寻址或mov sp,bp之前的源,则为什么要修改MOV AH, 0Ah ; Output to screen MOV [BP-7], 5 ; Max number of characters to read (4 + 1 for enter)

mov byte [bp-7],5

在这里你应该定义操作数的大小,例如。 mov word [bp-7],5ptr。或者使用mov byte ptr [bp-7],5,如果TASM需要,我不知道:mov word ptr [bp-7],5LEA DX, [BP-7] INT 21h

mov ah, 0x0a; int 21

这没有意义。根据{{​​3}} ds:dx是缓冲输入,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 应该指向输入缓冲区。您的评论“输出到屏幕”可能不正确吗?

ax

我认为在这个循环中你有溢出的风险。适用于{{1}}等任何16位寄存器的最大数字是2 ^ 16-1 == 65535。

答案 1 :(得分:1)

我没有查看所有代码,但有一个问题是您的数据和堆栈段不同,即程序以ds开始!= ss

因此LEA DX, [BP-7]不会为DOS输入函数提供正确的地址,因为它预期在ds:dx中,而你的缓冲区位于ss:dx和{{1} }!= ds。然后,以下ss调用可能会覆盖某些内存并导致挂起或崩溃。

您需要使用正确的地址。

更新:一些细节。

这是你的程序的一个稍微修改过的版本(问题中的代码对我来说不适合用TASM 3.2):

UPDATE2 :忘记提及所有更改的行都包含以int 21h开头的评论。

;;;;

组装(使用TASM 3.2)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. ,没有错误或警告。

链接(使用TLINK 3.01)为tasm stkkbinp.asm,没有错误或警告。

链接器生成的映射文件是:

tlink /m /s stkkbinp

现在在TD中打开程序并执行前3条指令:

enter image description here

您可以在屏幕上清楚地看到 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 不等于ds。它们相隔4个,首先在0x4caf处进入ss段,然后在0x4cb3处进入.data。在段值方面,这个4的差异相当于4 * 0x10 = 0x40字节。

如果你看一下上面的地图文件,你会看到,确实是这些段之间的距离:

.stack

我无法解释为什么地图文件显示两个(9)的相同段,但链接的可执行文件使它们不同。但这就是输入无法正常工作的原因。

答案 2 :(得分:1)

详细阐述Alexey Frunze的回答:

您的代码指定了一个SMALL模型。该指令告诉汇编器除其他外,假设DS = SS,但它没有(不能)强制执行它。

当DOS加载EXE时,它将SS:SP设置为文件头中找到的位置。它将DS设置为PSP的开头,以便您可以获得命令行参数等等。

DOS缓冲读取函数要求缓冲区为DS:DX。您已使用BP-7(缓冲区的起点)正确加载DX,但在进行函数调用之前还需要设置DS←SP。你可以用

做到这一点
MOV AX,SS
MOV DS,AX

PUSH SS
POP DS

没有MOV DS,SS指令。