在高級語言中,我們經常操作字符串,比如字符串拷貝、比較、查找等。在彙編語言中也有實現這些操作的命令。這一節講述在彙編語言中字符串傳送相關操作命令。
movs指令可以把字符串從一個內存位置傳送到另一個內存位置,指令後面跟表示長度的字符:movsb(1字節)、movsw(2字節)、movsl(4字節)。該指令使用隱含的源和目標操作數。隱含源操作數是esi寄存器,其指向源字符串的內存位置。隱含目標操作數是edi寄存器,其指向字符串要被複制到的目標內存位置。
在使用GNU彙編時,有兩種方式加載esi和edi的值,第一種使用間接尋址,例如: movl $val, %edi,其將變量val的32位內存地址傳送給edi。
第二種是使用lea命令,lea指令加載一個對象的有效地址,源操作數指向一個內存位置,比如leal val,%edi 把val標籤的32位內存位置加載到edi寄存器中。 如下是一個示例:
# movs.s
.section .data
val:
.ascii "Hello, as world!\n"
.section .bss
.lcomm output, 17
.section .text
.globl _start
_start:
nop
leal val, %esi
leal output, %edi
movsb
movsw
movsl
movl $1, %eax
movl $0, %ebx
int $0x80
make之後調試運行如下:
10 nop
(gdb) s
11 leal val, %esi
(gdb)
12 leal output, %edi
(gdb) s
13 movsb
(gdb) s
14 movsw
(gdb) x/s &output
0x80490a8 <output>: "H"
(gdb) s
15 movsl
(gdb) x/s &output
0x80490a8 <output>: "Hel"
(gdb) s
17 movl $1, %eax
(gdb) x/s &output
0x80490a8 <output>: "Hello, "
(gdb)
可以看到在每一條movs指令之後output的內存情況,在每一次執行movs指令時,數據傳送後,edi和edi寄存器會自動改變,為下一次做準備。在本示例中,寄存器是遞增的,寄存器向遞增還是遞減方向改變取決於EFLAFS寄存器中DF標誌。如果DF標誌被清零,在每條movs指令執行後esi和edi寄存器就會遞增,如果DF標誌被設置,在每條movs指令執行後esi和edi寄存器就會遞減。如果要確保DF被設置為正確的方向,在編寫代碼時,可以顯示去設置:cld指令用於將DF標誌清零,std指令用於設置DF標誌。
如果要複製較長的字符串,為了簡單可以movs指令放到循環當中,通過把ecx寄存器設置為字符串長度來進行控制。如下:
# movs.s
.section .data
val:
.ascii "Hello, as world!\n"
.section .bss
.lcomm output, 17
.section .text
.globl _start
_start:
nop
leal val, %esi
leal output, %edi
movl $17, %ecx
loop_strcpy:
movsb
loop loop_strcpy
movl $1, %eax
movl $0, %ebx
int $0x80
事實上,Intel有提供更簡單的指令:rep。rep指令按照ecx寄存器值執行其次數後面的字符串指令。示例如下:
# rep.s
.section .data
val:
.ascii "Hello, as world!\n"
.section .bss
.lcomm output, 17
.section .text
.globl _start
_start:
nop
leal val, %esi
leal output, %edi
movl $17, %ecx
cld
rep movsb
movl $1, %eax
movl $0, %ebx
int $0x80
make之後調試運行如下:
13 movl $17, %ecx
(gdb)
15 cld
(gdb)
16 rep movsb
(gdb)
18 movl $1, %eax
(gdb) x/s &output
0x80490b0 <output>: "Hello, as world!\n"
(gdb)
實際上可以依次多個字節的傳送,這時候就需要在ecx寄存器中放置正確的次數,以防超出字符串邊界。使用movsl指令傳送字符串可以使效率更高,但是必須知道什麼時候停止使用movsl指令轉回使用movsb指令,這可以通過整數除法來確定。