【问题标题】:Copying strings in assembly在汇编中复制字符串
【发布时间】:2018-05-22 17:40:56
【问题描述】:

我正在学习汇编,我正在尝试制作一种可以在汇编中生成字符串的方法。 我开始尝试将一个字符串复制到另一个字符串并将副本存储在一个变量中以备将来使用。

我正在使用emu8086,我收到以下错误:

跳过未知操作码:64

不是 8086 指令 - 尚不支持。

org 100h

jmp start

msg:    db      "Hello, World!", 0 
a:      db       0 
start: 


        mov     si,msg
        mov     di, a
        call    _make_str
        mov     a, di
        mov     dx, a
        mov     ah, 09h 
        int     21h 

        mov     ah, 0   
        int     16h 

        ret
_make_str:      
    pusha
_next:
        mov     al, [si]                  
        mov     [di], al                  
        inc     si
        inc     di
        cmp      al, 0                      
        jne    _next
        popa
        ret 

这个错误是什么意思,我做错了什么?

【问题讨论】:

  • 不熟悉emu8086和可用模式,但是pusha/popa直到80186/80188处理器才可用,所以如果你纯粹在8086/8088模式下也不可用。
  • @BrianKnoblauch pusha/popa 从 80186 开始工作,因此您指出正确,但 pusha/popa 是操作码 0x60/0x61。 0x64是FS段前缀,0x40+r(64)是INC,所以我猜错误中提到的64是FS前缀操作码,也是ASCII值'd'。这看起来与结尾 World! 以及覆盖后 d 的位置相关。看起来更像 emu8086 确实包含(至少一些)186 条指令。

标签: assembly x86-16


【解决方案1】:

你有点幼稚地期望字符串是什么。

msg:    db      "Hello, World!", 0 

这被组装成 14 个字节的机器码(每个字符在 ASCII 编码中是一个字节(有不同的编码具有不同的特性,但 emu8086 是基于 ASCII 的),还有一个用于终止零。

a:      db       0 

这被组装成一个零值的字节。

然后您的代码开始并在_make_str 内,它将从地址msg 复制14 个字节到地址a。但是地址a+1 等于start,所以这13 个字符正在为代码本身覆盖您的机器代码,越过call _make_str 并跟随mov a,di。然后在ret,代码返回mov a,di应该在的地址,但是字符串中已经有字节64,所以报告了未知的操作码。

emu8086 具有内置调试器,因此请使用单步指令和内存视图来查看字符串是如何编译的,以及您的代码做了什么。

在汇编程序中没有变量,db/dw/dd/... 前面的那些名称是“符号”,它们就像计算机内存中的书签,包含该值的第一个字节的地址。

如果您需要 20 字节的内存来存储最长 19 个字符的字符串,那么您需要分配/保留 20 字节的内存,内存不会自动增长,或者重新寻址内容以为意外数据腾出空间.


如果我不知道字符串可能有多长,我该如何初始化改变长度的变量?

您不使用未知长度的数据。要么你知道数据可能有多长,要么你必须设置一个最大值(高级编程语言也有限制,如果你足够努力,你会很容易打破“无限”的概念)。

根据最大值的类型,方法可能会有所不同。

例如,如果您正在读取用户名,您可以说最大为 80 个字节,并在数据区域中保留 80 个字节,仅此而已,名称较长的用户会导致您的应用崩溃,或者如果您的代码正确,他将只能输入 79 或 80 个字符(取决于您是否需要零终止符,在 80 字节数组内)。

如果您正在阅读诸如商店商品描述之类的小文本,您不知道确切的最大值,但您确定它在数千以内,或者出现严重错误,那么您可以动态保留堆栈空间,例如 @ 987654334@ 并将文本存储在堆栈区。

如果您正在读取长数据,例如 2+GB 文件的二进制编辑器,您将必须有一些数组来描述您当前在内存中的哪些文件块,并使用所有可用内存动态换入/换出操作系统提供缓存(“堆”类型的内存分配),但如果堆用完,现代操作系统将通过使用存储在磁盘上的虚拟内存(交换)来增加它。但是最好通过更好的算法和应用程序设计来避免这种情况。

同样在 emu8086 中,16 位 x86 实模式的所有其他限制都适用,即在您的类似 COM 的示例中,您的整个代码+数据+堆栈必须适合单个 64kiB 段(从 0x100 偏移开始,甚至不是完整的 64kiB),除非您的代码将检查 DOS 以获得更多可用段,并使用更多段(在普通 DOS 中加载 COM 文件作为第一个应用程序后,您通常在当前段之后有大约 500-580kiB 的可用内存)。

生活在 16 位世界并询问“不知道尺寸”是额外的痛苦,在 16 位世界中你必须提前知道尺寸,并为它们做好计划,否则你很快就会遇到问题。


编辑:实际上...您可能遇到了不同的错误,或者很难分辨是哪一个。

    mov     si,msg
    mov     di, a

这些设置sidi 寄存器具有来自内存地址msga 的两字节(16 位)值。这是 MASM 语法,内存访问不需要[]

要获得第一个字节的地址,您需要mov si, OFFSET msglea si,[msg] ...所以现在我什至不确定您覆盖了哪个内存以及您覆盖的位置,因为您使用字符数据作为内存指针,但显然您会覆盖重要的东西。

使用 emu8086 调试器查看自己到底发生了什么。

(在您修复代码以正确加载地址后,您将按照我在回答中的描述将代码覆盖)

【讨论】:

  • 如果我需要创建一个新字符串,我可以在哪里保存字符串?这个问题是怎么解决的?
  • @Pantokrator 你提前保留内存,计划使用。或堆栈,但在您的情况下,堆栈正在向后增长到代码中,因此将 64kiB 的数据推入堆栈将从最后覆盖您的代码......以改变情况。一般来说,在 16 位 DOS 中,您总是提前计划,以防万一。现代的 32/64 位操作系统采用高级编程语言会更加宽容,给你一些“无限资源”的错觉,但在 16 位世界里你不能那样生活。您需要提前计划最坏的情况。
  • 我的意思是,在旧的 DOS Pascal 中,您可以拥有最多为 ~32768 大小的整数数组(整数为 16 位 = 2 字节,2*32768 = 65536 = 不适合单个段,包括各种管家数据)。切换到 32 位操作系统后,能够突然写出类似int [100000]; 的东西并观看它编译和工作真是太好了……在 16 位编码中,很大一部分是完全理解任务和所需的数据,并计算了解如何适应 16-40kiB(在 ZX Spectrum 等机器上)或 300-600kiB(16b 模式下的 PC DOS)。包括代码(与当前 MB 长的可执行文件 = 疯狂)。
【解决方案2】:

你需要让你的 a 变量有足够的空间来存储源字符串中的所有字符,现在它只有一个字节。另请查看 rep movsb 指令。

【讨论】:

  • 如果我不知道字符串可能有多长,我该如何初始化改变长度的变量?
  • 现在您知道为什么缓冲区溢出错误如此普遍了。您需要选择一个最大长度值并为其分配,然后确保复制的字节数不会超过您的空间。
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 2021-06-13
  • 1970-01-01
  • 2011-05-20
  • 1970-01-01
  • 1970-01-01
  • 2019-10-11
  • 1970-01-01
相关资源
最近更新 更多