我如何在 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
使用指向数组的指针(SI
、DI
)和 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
部分),所以设置后eSi
到S源地址,eDi
到D目的地地址,eCx
到Count,你写 COPY_LOOP:
而不是 rep movsb
... 然后退出槽 int 80h (eax=1)
。 (注意寄存器名称中强调的字母,英特尔特意选择了这些字母以便于回忆)
顺便说一句,我刚才注意到,您在代码中写了一些错误:
inc si/di
应该是inc esi/edi
,因为你用esi/edi来寻址。如果你要复制超过 64k 内存边界的数组,inc si
会环绕它。
将ecx
设置为3,在32b模式下loop
指令确实使用整个32becx
,而不是16b部分cx
。如果复制前的代码会在 ecx
中使用一些较大的数字来设置某些高 16 位,则您的循环将复制比仅 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
如果您阅读了有关寄存器的文档,您应该已经了解 eax
和 ax
之间的区别。在 Linux 中,您处于 32b 模式(当您 link 二进制文件为 32b elf 时,现在 64b 系统可能默认使用 64b,这与 32b 模式略有不同),因此默认情况下使用 32b注册变体。除非你出于特殊原因真的想要 16b/8b 变体,并且你确保代码以后不能使用 32b 寄存器,而你只设置了更少的寄存器(比如 loop
、rep movsb
和 int 80h
做)。
它通常也使代码更快,因为在 32b 模式下使用 16b ax
需要在指令前额外的操作码字节,例如 mov eax,ebx
是 2 字节操作码 89 D8
,mov ax,bx
是 3 字节操作码 66 89 D8
.
我必须使用 x86 汇编器编写一个程序,将一个数组复制到另一个数组中
原始代码是用 MSDOS 的 TASM 为 8086 处理器编写的,但我想使用 i386 处理器
将其移植到 Linux NASMTASM 中的代码是这样的:
.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
使用指向数组的指针(SI
、DI
)和 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
部分),所以设置后eSi
到S源地址,eDi
到D目的地地址,eCx
到Count,你写 COPY_LOOP:
而不是 rep movsb
... 然后退出槽 int 80h (eax=1)
。 (注意寄存器名称中强调的字母,英特尔特意选择了这些字母以便于回忆)
顺便说一句,我刚才注意到,您在代码中写了一些错误:
inc si/di
应该是inc esi/edi
,因为你用esi/edi来寻址。如果你要复制超过 64k 内存边界的数组,inc si
会环绕它。将
ecx
设置为3,在32b模式下loop
指令确实使用整个32becx
,而不是16b部分cx
。如果复制前的代码会在ecx
中使用一些较大的数字来设置某些高 16 位,则您的循环将复制比仅 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
如果您阅读了有关寄存器的文档,您应该已经了解 eax
和 ax
之间的区别。在 Linux 中,您处于 32b 模式(当您 link 二进制文件为 32b elf 时,现在 64b 系统可能默认使用 64b,这与 32b 模式略有不同),因此默认情况下使用 32b注册变体。除非你出于特殊原因真的想要 16b/8b 变体,并且你确保代码以后不能使用 32b 寄存器,而你只设置了更少的寄存器(比如 loop
、rep movsb
和 int 80h
做)。
它通常也使代码更快,因为在 32b 模式下使用 16b ax
需要在指令前额外的操作码字节,例如 mov eax,ebx
是 2 字节操作码 89 D8
,mov ax,bx
是 3 字节操作码 66 89 D8
.