1

我正在学习汇编程序(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.
4

3 回答 3

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之前您不使用 for 寻址或 a 作为源,您也可以在此处进行修改,因此该指令是不必要的。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],5mov word [bp-7],5。或者ptr,如果 TASM 需要,我不知道:mov byte ptr [bp-7],5mov word ptr [bp-7],5.

LEA DX, [BP-7]
INT 21h

这根本不符合逻辑。根据DOS INT 21h - DOS 功能代码文档 mov ah, 0x0a; int 21是缓冲输入,并且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

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

于 2012-09-19T13:52:52.780 回答
1

我没有把所有的代码都看一遍,但是一个问题是你的数据和堆栈段是不同的,也就是说,程序以ds!=开头ss

因此LEA DX, [BP-7]不会为 DOS 输入函数提供正确的地址,因为它是预期的,ds:dx但您的缓冲区位于!= 。随后的调用可能会覆盖一些内存并导致挂起或崩溃。ss:dxdsssint 21h

您需要使用正确的地址。

更新:一些细节。

这是您的程序的一个稍微修改过的版本(问题中的代码在 TASM 3.2 中对我来说没有很好地组装):

UPDATE2:忘了提到所有更改的行都包含以 . 开头的注释;;;;

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.

组装(使用 TASM 3.2)tasm stkkbinp.asm没有错误或警告。

链接(与 TLINK 3.01)tlink /m /s stkkbinp没有错误或警告。

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

 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

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

在此处输入图像描述

你可以清楚地看到屏幕上ds的不相等ss。它们相隔 4,首先在 0x4caf 处进行.data分段,然后.stack在 0x4cb3 处进行分段。就段值而言,4 的差异相当于 4*0x10=0x40 字节。

如果您查看上面的地图文件,您会发现,实际上,这就是这些线段之间的距离:

 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

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

于 2012-09-19T14:06:39.463 回答
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 指令。

于 2012-09-19T15:06:44.197 回答