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

Assembly 在DOS中直接将文本读入堆栈,assembly,x86,dos,16-bit,tasm,Assembly,X86,Dos,16 Bit,Tasm,我正在学习16位DOS上的汇编程序TASM,并尝试使用0AHDOS服务将文本直接读入堆栈。它在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

我正在学习16位DOS上的汇编程序TASM,并尝试使用0AHDOS服务将文本直接读入堆栈。它在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.

可能有一些bug。你能编译这个吗

PROC _printNum USES BP AX BX DX SI
    PUSH BP
    MOV BP, SP
    SUB SP, 6  ; Max number to print is 5 + $ sign    
在此,您可以修改sp,即使在本过程结束时mov sp、bp之前未使用sp进行寻址或作为源,因此不需要此指令

    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
为什么会有这样的子sp,7?首先,您应该保持sp与2对齐,其次,如果在本过程结束时,在mov sp、bp之前不将sp用作寻址或源,为什么要在此处修改sp

MOV AH, 0Ah    ; Output to screen
MOV [BP-7], 5  ; Max number of characters to read (4 + 1 for enter)
这里您应该定义操作数的大小,例如mov字节[bp-7],5或mov字[bp-7],5。或者使用ptr,如果TASM需要,我不知道:mov字节ptr[bp-7],5或mov字ptr[bp-7],5

这是没有道理的。根据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。

可能有一些错误。你能编译这个吗

PROC _printNum USES BP AX BX DX SI
    PUSH BP
    MOV BP, SP
    SUB SP, 6  ; Max number to print is 5 + $ sign    
在此,您可以修改sp,即使在本过程结束时mov sp、bp之前未使用sp进行寻址或作为源,因此不需要此指令

    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
为什么会有这样的子sp,7?首先,您应该保持sp与2对齐,其次,如果在本过程结束时,在mov sp、bp之前不将sp用作寻址或源,为什么要在此处修改sp

MOV AH, 0Ah    ; Output to screen
MOV [BP-7], 5  ; Max number of characters to read (4 + 1 for enter)
这里您应该定义操作数的大小,例如mov字节[bp-7],5或mov字[bp-7],5。或者使用ptr,如果TASM需要,我不知道:mov字节ptr[bp-7],5或mov字ptr[bp-7],5

这是没有道理的。根据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。

我没有仔细阅读所有代码,但有一个问题是数据段和堆栈段不同,即程序以ds开头=党卫军

因此,LEA DX,[BP-7]不会为DOS输入函数提供正确的地址,因为ds:DX中需要它,而您的缓冲区位于ss:DX和ds=党卫军。接下来的int 21h调用可能会覆盖一些内存并导致挂起或崩溃

你需要使用正确的地址

更新:一些细节

这是您的程序的一个稍加修改的版本,问题中的代码在我使用TASM 3.2时汇编得不好:

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

与TASM 3.2组装为TASM stkkbinp.asm,无错误或警告

以TLINK/m/s stkkbinp的形式与TLINK 3.01链接,无错误或警告

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

 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段,然后在0x4cb3处运行.stack。就段值而言,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显示相同的段,但链接的可执行文件却有不同的段。但这就是输入无法正常工作的原因。

我没有仔细阅读所有代码,但有一个问题是您的数据段和堆栈段不同,即程序从ds开始=党卫军

因此,LEA DX,[BP-7]不会为DOS输入函数提供正确的地址,因为ds:DX中需要它,而您的缓冲区位于ss:DX和ds=党卫军。接下来的int 21h调用可能会覆盖一些内存并导致挂起或崩溃

你需要使用正确的地址

更新:一些细节

这是您的程序的一个稍加修改的版本,问题中的代码在我使用TASM 3.2时汇编得不好:

更新2:忘了提到所有更改的行都是contai n以;;;;开头的注释

与TASM 3.2组装为TASM stkkbinp.asm,无错误或警告

以TLINK/m/s stkkbinp的形式与TLINK 3.01链接,无错误或警告

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

 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段,然后在0x4cb3处运行.stack。就段值而言,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显示相同的段,但链接的可执行文件却有不同的段。但这就是输入无法正常工作的原因。

要详细说明Alexey Frunze的答案:

您的代码指定了一个小模型。这个指令告诉汇编程序假设DS=SS,但它不能强制执行它

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

DOS缓冲读取函数期望缓冲区位于DS:DX。您已经正确地加载了DX和BP-7作为缓冲区的开始,但是您还需要设置DS← 在进行函数调用之前,请单击SP。你可以用它来做

MOV AX,SS
MOV DS,AX


没有MOV DS,SS指令。

详细说明Alexey Frunze的答案:

您的代码指定了一个小模型。这个指令告诉汇编程序假设DS=SS,但它不能强制执行它

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

DOS缓冲读取函数期望缓冲区位于DS:DX。您已经正确地加载了DX和BP-7作为缓冲区的开始,但是您还需要设置DS← 在进行函数调用之前,请单击SP。你可以用它来做

MOV AX,SS
MOV DS,AX


没有MOV DS,SS指令。

您不应该使用BP来使用堆栈,而应该使用SP。BP只是用于恢复上一个堆栈帧,而不是用于使用您的堆栈帧。整个要点是,BP在进程结束之前是未使用的,但SP用于堆栈访问。@Linuxios在16位程序集中,您不能使用SP进行寻址,您需要使用例如BP。在独立程序集可执行文件中,您可以在处理器体系结构设置的限制范围内,按照自己的意愿使用所有寄存器。中有一个有用的总结,总结了x86处理器所有可能的寻址模式。@nrz:真的吗?这很奇怪。对不起,我做了一点16位asm。但主要是32岁和64岁。我的错!您不应该使用BP来使用堆栈,而应该使用SP。BP只是用于恢复上一个堆栈帧,而不是用于使用您的堆栈帧。整个要点是,BP在进程结束之前是未使用的,但SP用于堆栈访问。@Linuxios在16位程序集中,您不能使用SP进行寻址,您需要使用例如BP。在独立程序集可执行文件中,您可以在处理器体系结构设置的限制范围内,按照自己的意愿使用所有寄存器。中有一个有用的总结,总结了x86处理器所有可能的寻址模式。@nrz:真的吗?这很奇怪。对不起,我做了一点16位asm。但主要是32岁和64岁。我的错!感谢您的详细回复。1关于子SP,7,AFAIK SP用于PUSH/POP调用,因此我添加了它,以防我需要在函数中使用这些指令。如果我不这样做,我就把那部分去掉。2关于AX,是的,它将包含0ABCDh。3 ax:dx部分只是一个不好的注释,我在那里注释是为了记住,两个寄存器都将被更改,以防我忘记。4注释是错误的,我在上面的代码块中修复了它,但忘记修复源代码。再次感谢您的评论,我会尽力解决所有问题。感谢您的详细回复。1关于子SP,7,AFAIK SP用于PUSH/POP调用,因此我添加了它,以防我需要在函数中使用这些指令。如果我不这样做,我就把那部分去掉。2关于AX,是的,它将包含0ABCDh。3 ax:dx部分只是一个不好的注释,我在那里注释是为了记住,两个寄存器都将被更改,以防我忘记。4注释是错误的,我在上面的代码块中修复了它,但忘记修复源代码。再次感谢您的评论,我会尽力解决所有问题。非常感谢您提供的信息。我使用的是旧版本的TASM-2.0,这可能是它在这里编译得很好的原因。非常感谢您提供的信息。我使用的是旧版本的TASM-2.0,这可能是它在这里编译得很好的原因。