曲径通幽论坛

标题: 传送字符串指令 [打印本页]

作者: beyes    时间: 2010-8-16 01:05
标题: 传送字符串指令
处理字符串时经常将字符串从一个内存位置复制到另一个内存位置,MOVS 指令为此提供了简单的途径。MOVS 指令有 3 种格式:
Intel 文档使用 MODVSD 传送双字,GNU 汇编器使用 MOVSL 。

MOVS 使用隐含的源和目标操作数。
隐含的源操作数是 ESI 寄存器,它指向源字符串的内存位置。
隐含的目标操作数是 EDI 寄存器,它指向字符串要被复制到的目标内存位置。

两种加载 ESI 和 EDI 的方法:
1. 使用间接寻址。
通过在内存位置标签前面加 $ 符号,内存位置的地址就会被加到 ESI 或 EDI 寄存器中,如:
movl $output, %edi
2. 使用 LEA 指令。
LEA 指令加载一个对象的有效地址,此时源操作数必须指向一个内存位置,如:
leal output, %edi     #把 output 标签的 32 位内存位置加载到 EDI

测试程序:
.section .data
value1:
     .ascii "This is a test string.\n"

.section .bss
     .lcomm output, 23

.section .text

.global _start

_start:
     nop
     leal value1, %esi
     leal output, %edi
     movsb
     movsw
     movsl

     movl $1, %eax
     movl $0, %ebx
     int $0x80
生成可执行程序后,在 gdb 里看到:
(gdb) n
16        movsb
(gdb) n
17        movsw
(gdb) n
18        movsl
(gdb) x/s &output
0x80490b0 <output>:     "Thi"
(gdb) n
20        movl $1, %eax
(gdb) x/s &output
0x80490b0 <output>:     "This is"
正如期望的,字符串按照指令正确的复制到目的地。在每次执行 MOVS 时,数据传送后,ESI 和 EDI 寄存器会自动改变,为下一次传送做准备。需要注意的是,ESI 和 EDI 不仅能自动递增,还能自动递减,这取决于 EFLAGS 寄存器中的 DF 标志。
如果 DF 被清零,那么递增;如果 DF 被置位,则递减。CLD 指令用于将 DF 标志清零,STD 指令用于将 DF 标志置位。

对上面程序做一下修改:
.section .data
value1:
     .ascii "This is a test string.\n"

.section .bss
     .lcomm output, 23

.section .text

.global _start

_start:
     nop
     leal value1+22, %esi
     leal output+22, %edi
     
     std
     movsb
     movsw
     movsl

     movl $1, %eax
     movl $0, %ebx
     int $0x80
在 GDB 里观察:
(gdb) x/23b &output
0x80490b0 <output>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x80490b8 <output+8>:    0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x80490c0 <output+16>:    0x00    0x00    0x00    0x6e    0x67    0x2e    0x0a
由上可见,output 位置的字符串确实是从末尾开始填充的,但在 3 条 MOVS 指令之后,只有 4 个内存位置被填充!原因是:
ESI 和 EDI 是向后计数的,而 MOVSW 和 MOVSL 还是按照向前的顺序获得内存位置。过程是:
当 MOVSB 完成时,ESI 和 EDI 递减 1,'\n' 被复制,ESI 指向 '.' 处。
当 MOVSW 完成时,MOVSW 向前(根据ESI)获得 2 个内存位置,即复制了 '.' 和 '\n',然后送往 EDI 指向位置处,实际上此时 '\n' 被覆盖一次,然后 ESI 和 EDI 递减 2 个位置。
当 MOVSL 完成时,MOVSL 向前(根据ESI)获得 4 个内存位置,即复制了 'n', 'g', '.', '\n',然后送往 EDI 指向位置处,此时实际上是将 '.' 和 '\n' 进行了覆盖。

由上可知,如果使用 STD 向后处理字符串,但 MOVSW 和 MOVSL 指令仍然向前获取内存位置。

如果要复制大型字符串,可用循环来获取全部数据,其中将 ECX 设置为字符串的长度进行循环控制:
.section .data
value1:
     .ascii "This is a test string.\n"

.section .bss
     .lcomm output, 23

.section .text

.global _start

_start:
     nop
     leal value1, %esi
     leal output, %edi
     movl $23, %ecx
loop1:
     movsb
     loop loop1

     movl $1, %eax
     movl $0, %ebx
     int $0x80





欢迎光临 曲径通幽论坛 (http://www.groad.net/bbs/) Powered by Discuz! X3.2