字符串的傳送

在高級語言中,我們經常操作字符串,比如字符串拷貝、比較、查找等。在彙編語言中也有實現這些操作的命令。這一節講述在彙編語言中字符串傳送相關操作命令。

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指令,這可以通過整數除法來確定。


书籍推荐