我如何在 nasm x86 程序集中为 Linux 复制数组,移植 16 位 DOS 代码?

How can i copy an array in nasm x86 assembly for Linux, porting 16-bit DOS code?

我必须使用 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] 指令

我该怎么做?

回应马克

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

MOV SI, 0
MOV AX, 0

LOOP:       
    MOV AX, [TABLE_A + SI]
    MOV [TABLE_B + SI], AX

    INC SI
    CMP SI, 2
JBE LOOP

使用指向数组的指针(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.

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

How could I do it?

(来自对自我回答的评论的问题)

好吧,首先你要阅读指令参考指南,了解指令的作用,然后如果它符合你的目的,你就可以使用它。这是重要的一步,请经常重新阅读指令详细信息,以验证它确实以您期望的方式修改了寄存器和标志。特别是如果在调试器中您看到 CPU 您没有预料到的变化状态。

正如您在linux中一样,ds/es段寄存器很可能已经设置为合理的值(涵盖.data部分),所以设置后eSiS源地址,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指令确实使用整个32becx,而不是16b部分cx。如果复制前的代码会在 ecx 中使用一些较大的数字来设置某些高 16 位,则您的循环将复制比仅 3.

  3. 更多的字节
  4. 在再次调用 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 模式(当您 link 二进制文件为 32b elf 时,现在 64b 系统可能默认使用 64b,这与 32b 模式略有不同),因此默认情况下使用 32b注册变体。除非你出于特殊原因真的想要 16b/8b 变体,并且你确保代码以后不能使用 32b 寄存器,而你只设置了更少的寄存器(比如 looprep movsbint 80h做)。

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