【问题标题】:How can i copy an array in nasm x86 assembly for Linux, porting 16-bit DOS code?如何在 Linux 的 nasm x86 程序集中复制一个数组,移植 16 位 DOS 代码?
【发布时间】:2016-10-26 21:13:09
【问题描述】:

我必须编写一个程序,使用 x86 汇编程序将数组复制到其他数组中

原始代码是用 MSDOS 的 TASM 为 8086 处理器编写的,但我想将它移植到使用 i386 处理器的 Linux NASM

TASM中的代码是这样的:

.MODEL SMALL

.DATA

    TABLE_A DB 10, 5, 1
    TABLE_B DB 0, 0, 0

.CODE

    MOV AX, SEG TABLE_B
    MOV DS, AX

    MOV SI, 0

    LOOP:
        MOV AL, TABLE_A[SI]
        MOV TABLE_B[SI], AL

        INC SI
        CMP SI, 2
    JBE LOOP


    MOV AH, 4Ch
    INT 21h

END

我正在尝试用 nasm 重写它,但我无法坐在正确的数组位置,类似于 TABLE_A[SI] 指令

我该怎么做?

【问题讨论】:

  • 技术上,忽略阵列业务。它只是一块内存。您只需要知道数组从哪里开始以及它有多长 - 然后将这两个位置之间的所有字节复制到新的位置。

标签: linux assembly x86 nasm addressing-mode


【解决方案1】:

nasm 中的最终代码是这样的

section .text
global _start
cpu 386

_开始:

MOV ESI, TABLE_A
MOV EDI, TABLE_B
MOV CX, 3

COPY_LOOP:      
    MOV AL, [ESI]
    MOV [EDI], AL

    INC SI
    INC DI
LOOP COPY_LOOP

MOV AX,1
INT 80h

section .data
TABLE_A DB 10, 5, 1
TABLE_B DB 0, 0, 0

【讨论】:

  • 如果您确保dses 指向.data 段(并且DF=DirectionFlag 为零),那么您可以将整个COPY_LOOP 替换为单个以rep 为前缀的指令@987654325 @.
  • 我该怎么做?
  • mov ax,1 可能已损坏;使用mov eax,1 设置呼叫号码,不会在高2 字节中留下任何高位垃圾。 CX 相同;您的循环使用 ECX,但您只设置了低 2 个字节;你可以比你的意思多循环 2^16 的倍数。
  • re: rep movsb: 在 Linux 下 DS 和 ES 已经设置正确,你可以直接运行 rep movsb
【解决方案2】:

我该怎么做?

(来自 cmets 的自我回答问题)

嗯,首先您阅读说明参考指南以了解说明的作用,然后您可以使用它,如果它符合您的目的。这是重要的一步,经常重新阅读指令细节,以验证它是否以您期望的方式修改寄存器和标志。尤其是如果在调试器中您看到 CPU 状态发生了意想不到的变化。

正如您在 linux 中一样,ds/es 段寄存器很可能已经设置为合理的值(覆盖.data 部分),因此在将eSi 设置为 S我们的地址,eDiD 目的地地址,eCxCount,你写而不是 COPY_LOOP: 只是 rep movsb ... 然后退出低谷int 80h (eax=1)。 (注意寄存器名称中强调的字母,英特尔特意选择了这些字母以便于回忆)

顺便说一句,刚才我注意到,您在代码中写了一些错误:

  1. inc si/di应该是inc esi/edi,因为你使用esi/edi来寻址。如果您要在 64k 内存边界上复制数组,inc si 会环绕它。

  2. ecx 设置为 3,在 32b 模式下,loop 指令确实使用整个 32b ecx,而不是仅使用 16b 部分 cx。如果复制前的代码将在ecx 中使用一些较大的数字来设置一些高16 位,那么您的循环将复制比仅3 个更多的字节。

  3. 在再次调用int 80h 之前,您必须将整个eax 设置为函数号,否则您可能会在之前的代码中在eax 的高16 位中有一些垃圾,请求无效的函数。

因此,应用这些代码后,您的代码可能如下所示:

section .text
global _start
cpu 386

_start:
    MOV ESI, TABLE_A
    MOV EDI, TABLE_B
    MOV ECX, 3
    REP MOVSB  ; copy ECX bytes from DS:ESI to ES:EDI

    MOV EAX,1  ; call sys_exit, again FIXED to EAX!
    INT 80h

section .data

TABLE_A DB 10, 5, 1
TABLE_B DB 0, 0, 0

如果您确实阅读了有关寄存器的文档,您应该已经了解 eaxax 之间的区别。在 Linux 中,您处于 32b 模式(当您将二进制文件链接为 32b elf 时,现在 64b 可能是 64b 系统上的默认值,这与 32b 模式略有不同),因此默认使用 32b 寄存器变体。除非您出于特殊原因确实需要 16b/8b 变体,并且您确保代码在稍后仅设置较少的 32b 寄存器时无法使用(例如 looprep movsbint 80h 做)。

它还使代码通常更快,因为在 32b 模式下使用 16b ax 需要在指令前增加额外的操作码字节,例如 mov eax,ebx 是 2 字节操作码 89 D8mov ax,bx 是 3 字节操作码 @987654351 @。

【讨论】:

    【解决方案3】:

    回应马克

    我试过这个表格,没有成功:

    MOV SI, 0
    MOV AX, 0
    
    LOOP:       
        MOV AX, [TABLE_A + SI]
        MOV [TABLE_B + SI], AX
    
        INC SI
        CMP SI, 2
    JBE LOOP
    

    【讨论】:

    • 如果结果不成功,结果是什么?
    • 起初,ld 显示错误:重定位被截断以适应:R_386_16 针对 `.data' 当我将 SI 更改为 ESI 时,它会组装,但在调试中会产生奇怪的结果。在第一次迭代中,EAX 值为 0000050a 在第二次迭代中,值为 00000105 在第三次迭代中,该值为 00000a01
    • 您不能在 32 位寻址模式下使用 16 位寄存器。如果您的数组是固定大小的,为什么不使用双字加载/存储复制 4 个字节,而不是一次循环一个字节?如果你打算将它用于任何事情,复制单个字节会很慢。
    • 我刚刚用@JoseManuelAbarcaRodríguez 解决方案解决了我的问题
    • 那你为什么把al改成ax呢?您一次加载两个字节,而原始 TASM 代码只加载一个字节。然后inc si 仅将偏移量移动 1 个字节,而您已经读取/存储了两个字节(使用ax)。了解rax/eax/ax/ah:al 的不同之处以及它们的位大小(以及它们如何部分共享位,因为它是 CPU 中的同一个寄存器)。见stackoverflow.com/documentation/x86/2122/…
    【解决方案4】:

    使用指向数组的指针(SIDI)和 CX 作为计数器:

    MOV SI, Table_A     ;POINTER TO TABLE_A.
    MOV DI, Table_B     ;POINTER TO TABLE_B.
    MOV CX, 3           ;ARRAY LENGTH.
    REPEAT:       
        MOV AL, [SI]
        MOV [DI], AL
        INC SI
        INC DI
        LOOP REPEAT     ;CX-1. IF CX>0 JUMP TO REPEAT.
    

    【讨论】:

    • 谢谢,我试试
    • 谢谢,现在感觉运行良好
    • 这是 16 位 NASM 代码,但不是您想要的 Linux 下 32 位模式的代码。
    猜你喜欢
    • 1970-01-01
    • 2011-12-22
    • 1970-01-01
    • 2015-01-12
    • 1970-01-01
    • 2016-01-11
    • 2013-05-14
    • 2020-06-18
    相关资源
    最近更新 更多