为什么我的汇编输出中有多个字符串 overlap/overwrite?
Why do multiple strings overlap/overwrite in my output in assembly?
我在汇编 8086 中仅在一行中显示 4 个不同的字符串时遇到问题。输出应该是 "You are"、"first name"、"middle name" 和 "last name".它与前两个工作正常,但后两个与第一个重叠,这意味着 "You are" 最终被 "middle name" 重写,并进一步被 "last name" 重写。如果我在最后两行之前使用下一行,它打印出来很好,但我想在一行中显示所有 4 个字符串,而不是在 3 行中显示。我尝试在网上搜索,但大多数答案仅限于显示 2 个字符串。
;=====output======
mov ah, 09
mov dx, offset crlf ;next line
int 21h
mov ah, 09
mov dx, offset msg4 ;displays "You are"
int 21h
mov ah, 09
mov dx, offset string1 + 2 ;displays inputted "first name"
int 21h
mov ah, 09
mov dx, offset string3 + 2 ; this should appear next to string1,
int 21h not rewrite msg4...
mov ah, 09
mov dx, offset string2 + 2 ; this should appear next to string3, not
int 21h rewrite msg4 and string3
输出结果如下:
Enter 1st name: Helena
Enter last name: Ramos
Enter middle name: Ang
Ramosre Helena ;"Ang" rewrites "You are", and then
"Ramos" rewrites it again
; This is what I want to see:
; You are Helena Ang Ramos
我几乎是汇编的新手,我的教授并不是最有帮助的老师,因为我们没有任何书籍,class讲义只定义了说明和实验练习几乎是复制粘贴他写的代码,所以我的大多数class伙伴都是在实际编程中自学的。这只是作业的一小部分,实际作业要求我们显示中间名而不是中间名,但我什至无法正确显示所有 4 个字符串!在这一点上,我觉得字符串在堆栈中的推送方式存在问题,但我有限的知识使我无法弄清楚原因。
完整代码,如果您感兴趣:
org 100h
.model small
.stack 200
.data
msg1 db "Enter 1st name: $"
string1 db 50,?,50 dup ('$')
msg2 db 0ah, 0dh, "Enter last name: $"
string2 db 50,?,50 dup ('$')
msg3 db 0ah, 0dh, "Enter middle name: $"
string3 db 50,?,50 dup ('$')
msg4 db 0ah, 0dh, "You are $"
crlf db 0ah, 0dh, '$'
.code
mov ax, @data
mov ds, ax
mov ah, 09
mov dx, offset msg1
int 21h
mov ah, 0ah
mov dx, offset string1 ;input first name
int 21h
mov ah, 09
mov dx, offset msg2
int 21h
mov ah, 0ah
mov dx, offset string2 ;input last name
int 21h
mov ah, 09
mov dx, offset msg3
int 21h
mov ah, 0ah
mov dx, offset string3 ;input middle name
int 21h
;=====output======
mov ah, 09
mov dx, offset crlf ;next line
int 21h
mov ah, 09
mov dx, offset msg4 ;displays "You are"
int 21h
mov ah, 09
mov dx, offset string1 + 2 ;displays inputted "first name"
int 21h
mov ah, 09
mov dx, offset string3 + 2 ; this should appear next to string1,
int 21h not rewrite msg4...
mov ah, 09
mov dx, offset string2 + 2 ; this should appear next to string3, not
int 21h rewrite msg4 and string3
你应该检查调试器,会发生什么。
如果你愿意,你会看到在第一个提示中输入"Helena"后,string1
地址的内存内容是:
32 07 48 65 6C 65 6E 61 0D 24 24 24 24 ...
这些数据的重要之处在于地址 string1 + 2 + 7 - 1
处的值 0D
也称为 CR(回车符 return)(+2 获取字符串数据,+7 是输入的长度,-1 向后移动到最后一个字符)。
同样的事情也适用于其他两个输入。
一旦开始输出最后一行,您将在屏幕上书写:
You are Helena
并且出现第一个 CR,它将 return BIOS 光标返回到行首,但后面没有 LF (0Ah
),所以光标也不会向下移动一行,而是第二个输入的输出只会覆盖行的开头。
要修复:在显示用户输入的每个名称之前,执行:
mov ah, 09
mov dx, offset string1 + 2 ;displays inputted "first name"
movzx bx, byte ptr [dx-1] ; bx = length of input
mov byte ptr [bx + dx - 1], '$' ; overwrite last input char (most likely CR) with '$'
int 21h
(如果实模式不支持寻址模式[bx + dx -1]
,就做add bx,dx
然后用[bx-1]
然后...我的记忆是模糊的,我做了很多保护模式 x86 程序集,其中寻址模式更加宽松和通用)。
如果 8086 甚至没有 movzx
,那么 xor bx,bx
mov bl,[dx-1]
可以做同样的事情(从内存中读取字节,将其零扩展为字)。
编辑:实际上你可能想用 SPACE ' '
覆盖最后一个字符,将 space 放在名称之间......并在 string3
您可以用 mov word ptr [bx+dx-1],0A0Dh
覆盖它以在其末尾写入 CR+LF 对以换行,但是您应该在 string3
缓冲区之后再放一个字节以避免内存被最长可能的输入覆盖(完整的 50 个字符).
编辑:更多评论...
don't have any books
现在是互联网时代。教你 8086(我猜可能是在 emu8086 中)本身就是一个残酷的笑话,然后这些知识再次会给你未来任何相关编程的新视角,所以即使教 8086 也是值得的。你应该能够 google 出一些 emu8086 教程(虽然我不确定质量,从 SO 上的一些问题来看很难找到只涵盖基础知识的教程,但正确......然后是 "The Art of Assembly Language Programming" 书,个人使用可以免费阅读它的电子版,它非常全面和详细......但也很大(如果你的课程真的那么糟糕,你可能仍然想检查 16b DOS 版本, 并快速浏览几章以确定您需要进行适当研究的领域)。
I have a feeling there's a problem with how the strings are pushed in the stack
您没有在代码中的任何地方触及堆栈...从这个角度来看,您的评论听起来很可怕,您可能真的应该深入研究那本书,即使这意味着要通读 200-300 页。毕竟你没有用过"urgent",所以你大概有几个月的时间赶上汇编和计算机体系结构的基础知识。
"SPACE EDIT" 的修复:
但是当你用 space 覆盖最后一个字符时,如果用户输入 50 个字符长的名称,名称将不再 '$'
终止,所以你应该将缓冲区定义为在它们之外有固定的终止符,像这样:
string1 db 50,0,51 dup ('$') ; after 50 byte buffer there's one more '$'
这是 ASM 编程中最难的部分之一,以避免任何 buffer/stack 溢出错误,这些错误源于错误的数据定义和不安全的内存使用。始终在调试器中使用 minimum/maximum 输入测试您的代码,并观察内存内容以查看其行为是否符合预期,或者是否发生了一些意外的内存覆盖以及发生在何处。您可能还想在缓冲区之间定义一些保护值,例如:
string1 db 50,0,51 dup ('$')
db 0FFh
msg2 db 0ah, 0dh, "Enter last name: $"
然后,如果您在调试器中看到一些极端情况输入确实使 FF
字节消失,您就知道您的源错误(例如,如果第一个字节是 52 而用户将输入 52字符长名称)。
用有效的8086代码修复实模式(原来建议的寻址方式[dx]
在实模式下是不合法的):
首先在代码末尾添加一个过程,它将覆盖最后输入的字符+一个类似pascal的字符串的另一个字节(长度在字符串本身之前的字节中存储在内存中):
; input:
; dx = address of string ([dx-1] must contain length of string)
; ax = two chars to be written at the end of string (al = first, ah = second)
changeEndOfInputString:
push si ; preserve original si and bx values
push bx
mov si,dx ; use SI for addressing in real mode
xor bx,bx ; bx = 0
mov bl,[si-1] ; bx = (zero extended) string length
mov [si+bx-1],ax ; overwrite last inputted char + one more
pop bx ; restore bx and si and return
pop si
ret
现在名称字符串的显示将使用它来添加 space 和需要的新行。
;=====output======
;display "You are "
mov ah, 9
mov dx, offset msg4
int 21h
;display inputted "first name" with space added
mov dx, offset string1 + 2
mov ax, 2420h ; 20h = ' ', 24h = '$' (ASCII encoding)
call changeEndOfInputString
mov ah, 9
int 21h
;display inputted "middle name" with space added
mov dx, offset string3 + 2
mov ax, 2420h ; 20h = ' ', 24h = '$' (ASCII encoding)
call changeEndOfInputString
mov ah, 9
int 21h
;display inputted "last name" with CR+LF added
mov dx, offset string2 + 2
mov ax, 0A0Dh ; 0Dh = CR, 0Ah = LF (DOS "new line")
call changeEndOfInputString
mov ah, 9
int 21h
并确保您的字符串缓冲区末尾有额外的字节以适应这些修改:
string1 db 50,0,51 dup ('$') ; first name buffer
string2 db 50,0,52 dup ('$') ; last name buffer
string3 db 50,0,51 dup ('$') ; middle name buffer
"last name"缓冲区需要52个'$',因为如果用户输入完整的50个字符名称,第50个和第51个字符将被覆盖为CR+LF,所以第52个'$'将保存为int 21h,9
服务的字符串终止符。
第一个+中间名缓冲区可以使用 51 个“$”,因为输入将用 ' '+'$'
修改,因此如果输入 50 个字符,第 51 个字符将保持设置为“$”,甚至修改后
还要将0Ah
服务缓冲区的第二个字节设置为0
,而不是?
,因为它在某些DOS版本中实际上是输入值,告诉DOS缓冲区内容有多少对输入编辑有效,并且在缓冲区内调用之前未提供任何有效字符串。
最后的注释:
crlf db 0ah, 0dh, '$'
这是 LF+CR(顺序错误),DOS "new line" 应该是 CR+LF,即 13、10。10、13 大部分按预期工作,但在汇编世界中这是任何错误方式,你很幸运,它没有产生更大的影响。 (你在所有字符串定义中都错了)。
so I've tried replacing it with bx, and there's no error but it still overwrites.
你绝对必须学习使用调试器,这样你就有机会检查代码哪里没有按照你的预期执行,以及内存的内容是什么并在这种情况下注册。
你没有机会在没有调试器的情况下用汇编程序编程,我花了 我 三遍阅读你的原始问题并在我的脑海中进行了彻底的缓慢(大约 10 分钟)模拟(太懒了搜索与您的语法兼容的 8086 仿真器和汇编器),直到我弄清楚为什么您的字符串被覆盖,因为 "only CR" 不容易从源代码中发现。我做了多年的 x86 汇编编程,编写了数兆字节的 ASM 源代码。而且发现这个错误仍然很棘手。如果我有调试器并 运行 通过它,我会在用户输入后检查 string1
缓冲区的内容后立即发现问题。
或者用不同的例子来说明调试器的必要性,当我学习ASM编程时,我没有电脑,所以我不得不在纸上写代码,然后我在学校有几分钟的时间尝试一下通常每周出去一次。通常它会因为一些错误而崩溃,但我没有足够的时间在机器上调试它,所以我不得不在家里的论文中找到错误(并且在 1985 年左右没有互联网可以询问 SO)。我通常需要 3-5 周的时间才能拥有修复所有错误的工作版本。如果我可以随意使用计算机和调试器,我可能会在 1 小时内完成所有修复。话又说回来,现在我只是通过阅读源代码(即使是其他编程语言)就看到了很多错误,这就是我的大脑在纸上经历之后的方式,注意每个点、昏迷和数字...
我在汇编 8086 中仅在一行中显示 4 个不同的字符串时遇到问题。输出应该是 "You are"、"first name"、"middle name" 和 "last name".它与前两个工作正常,但后两个与第一个重叠,这意味着 "You are" 最终被 "middle name" 重写,并进一步被 "last name" 重写。如果我在最后两行之前使用下一行,它打印出来很好,但我想在一行中显示所有 4 个字符串,而不是在 3 行中显示。我尝试在网上搜索,但大多数答案仅限于显示 2 个字符串。
;=====output======
mov ah, 09
mov dx, offset crlf ;next line
int 21h
mov ah, 09
mov dx, offset msg4 ;displays "You are"
int 21h
mov ah, 09
mov dx, offset string1 + 2 ;displays inputted "first name"
int 21h
mov ah, 09
mov dx, offset string3 + 2 ; this should appear next to string1,
int 21h not rewrite msg4...
mov ah, 09
mov dx, offset string2 + 2 ; this should appear next to string3, not
int 21h rewrite msg4 and string3
输出结果如下:
Enter 1st name: Helena
Enter last name: Ramos
Enter middle name: Ang
Ramosre Helena ;"Ang" rewrites "You are", and then
"Ramos" rewrites it again
; This is what I want to see:
; You are Helena Ang Ramos
我几乎是汇编的新手,我的教授并不是最有帮助的老师,因为我们没有任何书籍,class讲义只定义了说明和实验练习几乎是复制粘贴他写的代码,所以我的大多数class伙伴都是在实际编程中自学的。这只是作业的一小部分,实际作业要求我们显示中间名而不是中间名,但我什至无法正确显示所有 4 个字符串!在这一点上,我觉得字符串在堆栈中的推送方式存在问题,但我有限的知识使我无法弄清楚原因。
完整代码,如果您感兴趣:
org 100h
.model small
.stack 200
.data
msg1 db "Enter 1st name: $"
string1 db 50,?,50 dup ('$')
msg2 db 0ah, 0dh, "Enter last name: $"
string2 db 50,?,50 dup ('$')
msg3 db 0ah, 0dh, "Enter middle name: $"
string3 db 50,?,50 dup ('$')
msg4 db 0ah, 0dh, "You are $"
crlf db 0ah, 0dh, '$'
.code
mov ax, @data
mov ds, ax
mov ah, 09
mov dx, offset msg1
int 21h
mov ah, 0ah
mov dx, offset string1 ;input first name
int 21h
mov ah, 09
mov dx, offset msg2
int 21h
mov ah, 0ah
mov dx, offset string2 ;input last name
int 21h
mov ah, 09
mov dx, offset msg3
int 21h
mov ah, 0ah
mov dx, offset string3 ;input middle name
int 21h
;=====output======
mov ah, 09
mov dx, offset crlf ;next line
int 21h
mov ah, 09
mov dx, offset msg4 ;displays "You are"
int 21h
mov ah, 09
mov dx, offset string1 + 2 ;displays inputted "first name"
int 21h
mov ah, 09
mov dx, offset string3 + 2 ; this should appear next to string1,
int 21h not rewrite msg4...
mov ah, 09
mov dx, offset string2 + 2 ; this should appear next to string3, not
int 21h rewrite msg4 and string3
你应该检查调试器,会发生什么。
如果你愿意,你会看到在第一个提示中输入"Helena"后,string1
地址的内存内容是:
32 07 48 65 6C 65 6E 61 0D 24 24 24 24 ...
这些数据的重要之处在于地址 string1 + 2 + 7 - 1
处的值 0D
也称为 CR(回车符 return)(+2 获取字符串数据,+7 是输入的长度,-1 向后移动到最后一个字符)。
同样的事情也适用于其他两个输入。
一旦开始输出最后一行,您将在屏幕上书写:
You are Helena
并且出现第一个 CR,它将 return BIOS 光标返回到行首,但后面没有 LF (0Ah
),所以光标也不会向下移动一行,而是第二个输入的输出只会覆盖行的开头。
要修复:在显示用户输入的每个名称之前,执行:
mov ah, 09
mov dx, offset string1 + 2 ;displays inputted "first name"
movzx bx, byte ptr [dx-1] ; bx = length of input
mov byte ptr [bx + dx - 1], '$' ; overwrite last input char (most likely CR) with '$'
int 21h
(如果实模式不支持寻址模式[bx + dx -1]
,就做add bx,dx
然后用[bx-1]
然后...我的记忆是模糊的,我做了很多保护模式 x86 程序集,其中寻址模式更加宽松和通用)。
如果 8086 甚至没有 movzx
,那么 xor bx,bx
mov bl,[dx-1]
可以做同样的事情(从内存中读取字节,将其零扩展为字)。
编辑:实际上你可能想用 SPACE ' '
覆盖最后一个字符,将 space 放在名称之间......并在 string3
您可以用 mov word ptr [bx+dx-1],0A0Dh
覆盖它以在其末尾写入 CR+LF 对以换行,但是您应该在 string3
缓冲区之后再放一个字节以避免内存被最长可能的输入覆盖(完整的 50 个字符).
编辑:更多评论...
don't have any books
现在是互联网时代。教你 8086(我猜可能是在 emu8086 中)本身就是一个残酷的笑话,然后这些知识再次会给你未来任何相关编程的新视角,所以即使教 8086 也是值得的。你应该能够 google 出一些 emu8086 教程(虽然我不确定质量,从 SO 上的一些问题来看很难找到只涵盖基础知识的教程,但正确......然后是 "The Art of Assembly Language Programming" 书,个人使用可以免费阅读它的电子版,它非常全面和详细......但也很大(如果你的课程真的那么糟糕,你可能仍然想检查 16b DOS 版本, 并快速浏览几章以确定您需要进行适当研究的领域)。
I have a feeling there's a problem with how the strings are pushed in the stack
您没有在代码中的任何地方触及堆栈...从这个角度来看,您的评论听起来很可怕,您可能真的应该深入研究那本书,即使这意味着要通读 200-300 页。毕竟你没有用过"urgent",所以你大概有几个月的时间赶上汇编和计算机体系结构的基础知识。
"SPACE EDIT" 的修复:
但是当你用 space 覆盖最后一个字符时,如果用户输入 50 个字符长的名称,名称将不再 '$'
终止,所以你应该将缓冲区定义为在它们之外有固定的终止符,像这样:
string1 db 50,0,51 dup ('$') ; after 50 byte buffer there's one more '$'
这是 ASM 编程中最难的部分之一,以避免任何 buffer/stack 溢出错误,这些错误源于错误的数据定义和不安全的内存使用。始终在调试器中使用 minimum/maximum 输入测试您的代码,并观察内存内容以查看其行为是否符合预期,或者是否发生了一些意外的内存覆盖以及发生在何处。您可能还想在缓冲区之间定义一些保护值,例如:
string1 db 50,0,51 dup ('$')
db 0FFh
msg2 db 0ah, 0dh, "Enter last name: $"
然后,如果您在调试器中看到一些极端情况输入确实使 FF
字节消失,您就知道您的源错误(例如,如果第一个字节是 52 而用户将输入 52字符长名称)。
用有效的8086代码修复实模式(原来建议的寻址方式[dx]
在实模式下是不合法的):
首先在代码末尾添加一个过程,它将覆盖最后输入的字符+一个类似pascal的字符串的另一个字节(长度在字符串本身之前的字节中存储在内存中):
; input:
; dx = address of string ([dx-1] must contain length of string)
; ax = two chars to be written at the end of string (al = first, ah = second)
changeEndOfInputString:
push si ; preserve original si and bx values
push bx
mov si,dx ; use SI for addressing in real mode
xor bx,bx ; bx = 0
mov bl,[si-1] ; bx = (zero extended) string length
mov [si+bx-1],ax ; overwrite last inputted char + one more
pop bx ; restore bx and si and return
pop si
ret
现在名称字符串的显示将使用它来添加 space 和需要的新行。
;=====output======
;display "You are "
mov ah, 9
mov dx, offset msg4
int 21h
;display inputted "first name" with space added
mov dx, offset string1 + 2
mov ax, 2420h ; 20h = ' ', 24h = '$' (ASCII encoding)
call changeEndOfInputString
mov ah, 9
int 21h
;display inputted "middle name" with space added
mov dx, offset string3 + 2
mov ax, 2420h ; 20h = ' ', 24h = '$' (ASCII encoding)
call changeEndOfInputString
mov ah, 9
int 21h
;display inputted "last name" with CR+LF added
mov dx, offset string2 + 2
mov ax, 0A0Dh ; 0Dh = CR, 0Ah = LF (DOS "new line")
call changeEndOfInputString
mov ah, 9
int 21h
并确保您的字符串缓冲区末尾有额外的字节以适应这些修改:
string1 db 50,0,51 dup ('$') ; first name buffer
string2 db 50,0,52 dup ('$') ; last name buffer
string3 db 50,0,51 dup ('$') ; middle name buffer
"last name"缓冲区需要52个'$',因为如果用户输入完整的50个字符名称,第50个和第51个字符将被覆盖为CR+LF,所以第52个'$'将保存为int 21h,9
服务的字符串终止符。
第一个+中间名缓冲区可以使用 51 个“$”,因为输入将用 ' '+'$'
修改,因此如果输入 50 个字符,第 51 个字符将保持设置为“$”,甚至修改后
还要将0Ah
服务缓冲区的第二个字节设置为0
,而不是?
,因为它在某些DOS版本中实际上是输入值,告诉DOS缓冲区内容有多少对输入编辑有效,并且在缓冲区内调用之前未提供任何有效字符串。
最后的注释:
crlf db 0ah, 0dh, '$'
这是 LF+CR(顺序错误),DOS "new line" 应该是 CR+LF,即 13、10。10、13 大部分按预期工作,但在汇编世界中这是任何错误方式,你很幸运,它没有产生更大的影响。 (你在所有字符串定义中都错了)。
so I've tried replacing it with bx, and there's no error but it still overwrites.
你绝对必须学习使用调试器,这样你就有机会检查代码哪里没有按照你的预期执行,以及内存的内容是什么并在这种情况下注册。
你没有机会在没有调试器的情况下用汇编程序编程,我花了 我 三遍阅读你的原始问题并在我的脑海中进行了彻底的缓慢(大约 10 分钟)模拟(太懒了搜索与您的语法兼容的 8086 仿真器和汇编器),直到我弄清楚为什么您的字符串被覆盖,因为 "only CR" 不容易从源代码中发现。我做了多年的 x86 汇编编程,编写了数兆字节的 ASM 源代码。而且发现这个错误仍然很棘手。如果我有调试器并 运行 通过它,我会在用户输入后检查 string1
缓冲区的内容后立即发现问题。
或者用不同的例子来说明调试器的必要性,当我学习ASM编程时,我没有电脑,所以我不得不在纸上写代码,然后我在学校有几分钟的时间尝试一下通常每周出去一次。通常它会因为一些错误而崩溃,但我没有足够的时间在机器上调试它,所以我不得不在家里的论文中找到错误(并且在 1985 年左右没有互联网可以询问 SO)。我通常需要 3-5 周的时间才能拥有修复所有错误的工作版本。如果我可以随意使用计算机和调试器,我可能会在 1 小时内完成所有修复。话又说回来,现在我只是通过阅读源代码(即使是其他编程语言)就看到了很多错误,这就是我的大脑在纸上经历之后的方式,注意每个点、昏迷和数字...