汇编语言 字符串

  • 字符串

    在前面的示例中,我们已经使用了可变长度的字符串。可变长度字符串可以根据需要包含任意多个字符。通常,我们通过以下两种方式之一指定字符串的长度:
    • 显式存储字符串长度
    • 使用哨兵字符
    我们可以使用表示位置计数器当前值的$位置计数器符号来显式存储字符串长度。在以下示例中-
    
    msg  db  'Hello, world!',0xa ;我们常见的字符
    len  equ  $ - msg            ;长度
    
    $指向字符串变量msg的最后一个字符之后的字节。因此,$ - msg给出字符串的长度。我们也可以写
    
    msg db 'Hello, world!',0xa ;our dear string
    len equ 13                 ;length of our dear string
    
    另外,您可以存储带有尾部定点字符的字符串来定界字符串,而不是显式存储字符串长度。前哨字符应为不出现在字符串中的特殊字符。
    例如-
    
    message DB 'I am loving it!', 0
    
  • 字符串指令

    每个字符串指令可能需要一个源操作数,一个目标操作数或两者。对于32位段,字符串指令使用ESI和EDI寄存器分别指向源和目标操作数。但是,对于16位段,SI和DI寄存器分别用于指向源和目标。
    有五个用于处理字符串的基本说明。他们是-
    • MOVS-该指令将1字节,字或双字数据从存储位置移到另一个位置。
    • LODS-该指令从存储器加载。如果操作数是一个字节,则将其加载到AL寄存器中;如果操作数是一个字,则将其加载到AX寄存器中,并将双字加载到EAX寄存器中。
    • STOS-该指令将数据从寄存器(AL,AX或EAX)存储到存储器。
    • CMPS-该指令比较内存中的两个数据项。数据可以是字节大小,字或双字。
    • SCAS-该指令将寄存器(AL,AX或EAX)的内容与内存中项目的内容进行比较。
    上面的每个指令都有字节,字和双字版本,并且可以通过使用重复前缀来重复字符串指令。这些指令使用ES:DI和DS:SI对寄存器,其中DI和SI寄存器包含有效的偏移地址,这些地址指向存储在存储器中的字节。SI通常与DS(数据段)相关联,DI通常与ES(额外段)相关联。DS:SI(或ESI)和ES:DI(或EDI)寄存器分别指向源和目标操作数。假定源操作数位于内存中的DS:SI(或ESI),目标操作数位于ES:DI(或EDI)。
    对于16位地址,使用SI和DI寄存器,对于32位地址,使用ESI和EDI寄存器。
    下表提供了各种版本的字符串指令和假定的操作数空间。
    基本指令 操作的寄存器 字节运算 字运算 双字运算
    MOVS ES:DI,DS:SI MOVSB MOVSW MOVSD
    LODS DS:SI LODSB LODSW LODSD
    STOS ES:DI,AX STOSB STOS STOSD
    CMPS DS:SI,ES:DI CMPSB CMPSW CMPSD
    SCAS ES:DI,AX SCASB SCASW SCASD
  • MOVS

    MOVS指令用于将数据项(字节,字或双字)从源字符串复制到目标字符串。源字符串由DS:SI指向,目标字符串由ES:DI指向。
    
    section .text
       global _start        ;must be declared for using gcc
            
    _start:                 ;tell linker entry point
       mov  ecx, len
       mov  esi, s1
       mov  edi, s2
       cld
       rep  movsb
            
       mov  edx,20          ;message length
       mov  ecx,s2          ;message to write
       mov  ebx,1           ;file descriptor (stdout)
       mov  eax,4           ;system call number (sys_write)
       int  0x80            ;call kernel
            
       mov  eax,1           ;system call number (sys_exit)
       int  0x80            ;call kernel
            
    section .data
    s1 db 'Hello, world!',0 ;string 1
    len equ $-s1
    
    section  .bss
    s2 resb 20              ;destination
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    Hello, world!
    
  • LODS

    在密码术中,凯撒密码是最简单的已知加密技术之一。在这种方法中,要加密的数据中的每个字母都被替换为字母下方固定数量位置的字母。在此示例中,让我们通过简单地将其中的每个字母替换为两个字母来加密数据,因此a将被c,b替换为d等。我们使用LODS将原始字符串'password'加载到内存中。
    
    section .text
       global _start         ;must be declared for using gcc
            
    _start:                  ;tell linker entry point
       mov    ecx, len
       mov    esi, s1
       mov    edi, s2
            
    loop_here:
       lodsb
       add al, 02
       stosb
       loop    loop_here          
       cld
       rep     movsb
            
       mov     edx,20        ;message length
       mov     ecx,s2        ;message to write
       mov     ebx,1         ;file descriptor (stdout)
       mov     eax,4         ;system call number (sys_write)
       int     0x80          ;call kernel
            
       mov     eax,1         ;system call number (sys_exit)
       int     0x80          ;call kernel
            
    section .data
    s1 db 'password', 0 ;source
    len equ $-s1
    
    section .bss
    s2 resb 10               ;destination
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    rcuuyqtf
    
  • STOS

    STOS指令将数据项从AL(对于字节-STOSB),AX(对于字-STOSW)或EAX(对于双字-STOSD)复制到目标字符串,该目标字符串由内存中的ES:DI指向。以下示例演示了如何使用LODS和STOS指令将大写字符串转换为其小写值-
    
    section .text
       global _start        ;must be declared for using gcc
            
    _start:                 ;tell linker entry point
       mov    ecx, len
       mov    esi, s1
       mov    edi, s2
            
    loop_here:
       lodsb
       or      al, 20h
       stosb
       loop    loop_here    
       cld
       rep  movsb
            
       mov  edx,20          ;message length
       mov  ecx,s2          ;message to write
       mov  ebx,1           ;file descriptor (stdout)
       mov  eax,4           ;system call number (sys_write)
       int  0x80            ;call kernel
            
       mov  eax,1           ;system call number (sys_exit)
       int  0x80            ;call kernel
            
    section .data
    s1 db 'HELLO, WORLD', 0 ;source
    len equ $-s1
    
    section .bss
    s2 resb 20              ;destination
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    hello, world
    
  • CMPS

    CMPS指令比较两个字符串。该指令比较DS:SI和ES:DI寄存器所指向的两个数据项,即一个字节,一个字或一个双字,并相应地设置标志。您还可以将条件跳转指令与此指令一起使用。以下示例演示了使用CMPS指令比较两个字符串-
    
    section .text
       global _start            ;must be declared for using gcc
            
    _start: ;tell linker entry point
       mov esi, s1
       mov edi, s2
       mov ecx, lens2
       cld
       repe  cmpsb
       jecxz  equal             ;jump when ecx is zero
    
       ;If not equal then the following code
       mov eax, 4
       mov ebx, 1
       mov ecx, msg_neq
       mov edx, len_neq
       int 80h
       jmp exit
            
    equal:
       mov eax, 4
       mov ebx, 1
       mov ecx, msg_eq
       mov edx, len_eq
       int 80h
            
    exit:
       mov eax, 1
       mov ebx, 0
       int 80h
            
    section .data
    s1 db 'Hello, world!',0      ;our first string
    lens1 equ $-s1
    
    s2 db 'Hello, there!', 0     ;our second string
    lens2 equ $-s2
    
    msg_eq db 'Strings are equal!', 0xa
    len_eq  equ $-msg_eq
    
    msg_neq db 'Strings are not equal!'
    len_neq equ $-msg_neq
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    Strings are not equal!
    
  • SCAS

    SCAS指令用于搜索字符串中的特定字符或一组字符。要搜索的数据项应该在AL(对于SCASB),AX(对于SCASW)或EAX(对于SCASD)寄存器中。要搜索的字符串应在内存中,并由ES:DI(或EDI)寄存器指向。查看以下程序以了解概念-
    
    section .text
       global _start        ;must be declared for using gcc
            
    _start:                 ;tell linker entry point
    
       mov ecx,len
       mov edi,my_string
       mov al , 'e'
       cld
       repne scasb
       je found ; when found
       ; If not not then the following code
            
       mov eax,4
       mov ebx,1
       mov ecx,msg_notfound
       mov edx,len_notfound
       int 80h
       jmp exit
            
    found:
       mov eax,4
       mov ebx,1
       mov ecx,msg_found
       mov edx,len_found
       int 80h
            
    exit:
       mov eax,1
       mov ebx,0
       int 80h
            
    section .data
    my_string db 'hello world', 0
    len equ $-my_string  
    
    msg_found db 'found!', 0xa
    len_found equ $-msg_found
    
    msg_notfound db 'not found!'
    len_notfound equ $-msg_notfound   
    
    尝试一下
    编译并执行上述代码后,将产生以下结果-
    
    found!
    
  • 重复前缀

    REP前缀在字符串指令(例如-REP MOVSB)之前设置时,会根据放置在CX寄存器中的计数器使该指令重复。REP执行该指令,将CX减1,然后检查CX是否为零。重复指令处理,直到CX为零为止。
    方向标志(DF)确定操作的方向。
    • 使用CLD(清除方向标志,DF = 0)使操作从左到右。
    • 使用STD(设置方向标志,DF = 1)使操作从右到左。
    REP前缀也有以下变化:
    • REP:这是无条件的重复。重复该操作,直到CX为零为止。
    • REPE或REPZ:这是有条件的重复。当零标志指示等于/零时,它将重复操作。当ZF表示不等于零或CX为零时,它将停止。
    • REPNE或REPNZ:这也是有条件的重复。当零标志指示不等于/零时,它将重复操作。当ZF指示等于/零或CX减为零时,它将停止。