一、传送字符串
1、MOVS指令
创建MOVS指令是为了向程序员提供把字符串从一个内存位置传送到另一个内存位置的简单途
径。MOVS指令有3种格式:
- MOVSB:传送单一字节
- MOVSW:传送一个字(2字节)
- MOVSL:传送一个双字(4字节)
MOVS指令使用隐含的源和目的操作数。隐含的源操作数是ESI寄存器。它指向源字符串的内存
位置。隐含的目标操作数是EDI寄存器。它指向字符串要被复制到的目标内存位置。
有两种方式加载ESI和EDI值。第一种是使用间接寻址:
movl $output, %edi
/*这条指令把output标签的32位内存位置传送给EDI寄存器*/
另一种方式是LEA指令,加载一个对象的有效地址:
leal output, %edi
/*把output标签的32位内存位置加载到EDI寄存器中*/
.section .data
value1:
.ascii "This is a test string\n"
.section .bss
.lcomm output, 23
.section .text
.globl _start
_start:
leal value1, %esi
leal output, %edi
movsb
movsw
movsl
movl $1, %eax
movl $0, %ebx
int $0x80
如果EFLAGS寄存器中的DF位被清零,那么每条MOVS指令执行之后ESI和EDI寄存器就会
递增。如果DF位被设置,那么每条MOVS指令执行之后ESI和EDI寄存器就会递减,可以使用下
面的命令:
- CLD用于将DF标志清零
- STD用于设置DF标志
使用STD指令时,ESI和EDI寄存器在每一条MOVS指令执行之后递减,所以它们应该指向字
符串的末尾,而不是开头。
.section .data
value1:
.ascii "The is a test string\n"
.section .bss
.lcomm output, 12
.section .text
.globl _start
_start:
leal value1+22, %esi
leal output+22, %edi
std
movsb
movsw
movsl
movl $1, %eax
movl $0, %ebx
int $0x80
如果要复制大型字符串,可以把MOVSL指令放到循环中,通过ECX寄存器设置字符串长度来
进行控制。
.section .data
value1:
.ascii "The is a test string\n"
.section .bss
.lcomm output, 23
.section .text
.globl _start
_start:
leal value1, %esi
leal output, %edi
movl $23, %ecx
cld
loop1:
movsb
loop loop1
movl $1, %eax
movl $0, %ebx
int $0x80
2、REP前缀
REP指令的特殊之处在于它不执行什么操作。这条指令用于按照特定次数重复执行字符串指
令,由ECX寄存器中的值进行控制。这和使用循环类似,但是不需要额外的LOOP指令,REP指
令重复地执行紧跟在它后面的字符串指令,直到ECX寄存器中的值为零。这就是为什么称它为前
缀的原因。
1)逐字节地传送字符串
.section .data
value1:
.ascii "The is a test string.\n"
.section .bss
.lcomm output, 23
.section .text
.globl _start
_start:
leal value1, %esi
leal output, %edi
movl $23, %ecx
cld
rep movsb
movl $1, %eax
movl $0, %ebx
int $0x80
2)逐块地传送字符串
也可以使用MOVSW和MOVSL指令在每次迭代中传送1字节以上的数据。
.section .data
value1:
.ascii "The is a test string.\n"
value2:
.ascii "Oops"
.section .bss
.lcomm output, 23
.section .text
.globl _start
_start:
leal value1, %esi
leal output, %edi
movl $6, %ecx
cld
rep movsl
movl $1, %eax
movl $0, %ebx
int $0x80
3)传送大型字符串
.section .data
string1:
.asciz "This is a test of the conversion program!\n"
length:
.int 43
divisor:
.int 4
.section .bss
.lcomm buffer, 43
.section .text
.globl _start
_start:
leal string1, %esi
leal buffer, %edi
movl length, %ecx
shrl $2, %ecx
cld
rep movsl
movl length, %ecx
andl $3, %ecx
rep movsb
movl $1, %eax
movl $0, %ebx
int $0x80
4)按照相反的顺序传送字符串
可以把DF标志设置为对字符串进行向后处理,按相反的方向在内存位置之间传送。
.section .data
value1:
.asciz "This is a test string\n"
.section .bss
.lcomm output, 24
.section .text
.globl _start
_start:
leal value1+22, %esi
leal output+22, %edi
movl $23, %ecx
std
rep movsb
movl $1, %eax
movl $0, %ebx
int $0x80
3、其他REP指令
除了监视ECX寄存器值之外,还有监视零标志(ZF)的状态的REP指令。
REPE和REPZ指令是相同指令的同义词,REPNE和REPNZ指令是同义词。
二、存储和加载字符串
1、LODS指令
LODS指令用于把内存中的字符串传送到EAX寄存器中。也有三种不同的格式:
- LODSB:把一个字节加载到AL寄存器中
- LODSW:把一个字(2字节)加载到AX寄存器中
- LODSL:把一个双字(4字节)加载到EAX寄存器中
LODS指令使用ESI寄存器作为隐含的源操作数。ESI寄存器必须包含要加载的字符串所在的
内存位置。数据传送完成之后,LODS指令按照加载的数据的数量递增或递减(取决于DF标志状
态)ESI寄存器。
2、STOS指令
使用LODS指令把字符串值存到EAX寄存器之后,可以使用STOS指令把它存放
在另一个内存位置中。也有三种格式:
- STOSB:存储AL寄存器中一个字节的数据
- STOSW:存储AX寄存器中一个字(2字节)的数据
- STOSL:存储EAX寄存器中一个双字(4个字节)的数据
STOS指令使用EDI寄存器作为隐含的目标操作数。执行STOS指令时,它按照使
用的数据长度递增或者递减EDI寄存器的值。
STOS指令真正能够提供的方便是和REP指令一起使用,多次把一个字符串复制到大型字符
串值中的时候。
.section .data
space:
.ascii " "
.section .bss
.lcomm buffer, 256
.section .text
.globl _start
_start:
leal space, %esi
leal buffer, %edi
movl $256, %ecx
cld
lodsb
rep stosb
movl $1, %eax
movl $0, %ebx
int $0x80
3、构建自己的字符串函数
STOS和LODS指令可以用于各种字符串操作。通过使ESI和EDI寄存器指向相同的字符串,
可以对字符串执行简单的操作。可以使用LODS指令遍历字符串,一次把一个字符加载到AL寄存
器中,对这个字符执行某些操作,然后使用STOS指令把新的字符加载回字符串中。
.section .data
string1:
.asciz "This is a TEST, of the conversion program!\n"
length:
.int 43
.section .text
.globl _start
_start:
leal string1, %esi #加载string1内存位置到ESI寄存器中
movl %esi, %edi
movl length, %ecx #加载字符串长度到ECX寄存器
cld #设置增址模式
loop1:
lodsb #加载一个字符到AL寄存器
cmpb $'a', %al #小于a的ASCII值(0x61)
jl skip
cmpb $'z', %al #大于z的ASCII值(0x7a)
jg skip
subb $0x20, %al #如果是小写字母,则减去0x20把它转换为大写字母
skip:
stosb #加载EAX寄存器
loop loop1
end:
pushl $string1
call printf
addl $4, %esp
pushl $0
call exit
三、比较字符串
1、CMPS指令
CMPS指令系列用于比较字符串值,也有3种格式:
- CMPSB:比较字节值
- CMPSW:比较字(2字节)值
- CMPSL:比较双字(4字节)值
CMPS指令隐含的源和目标操作数的位置同样存储在ESI和EDI寄存器中,每次执
行CMPS指令时,根据DF标志的设置,ESI和EDI寄存器按照被比较的数据的长度递
增或者递减。
CMPS指令从源字符串中减去目标字符串,并且适当地设置EFLAGS寄存器的进
位、符号、溢出、零、奇偶校验和辅助进位标志。CMPS指令执行之后,可以根据字
符串的值,使用一般的条件跳转到分支。
.section .data
value1:
.ascii "Test"
value2:
.ascii "Test"
.section .text
.globl _start
_start:
movl $1, %eax
leal value1, %esi
leal value2, %edi
cld
cmpsl
je equal
movl $1, %ebx
int $0x80
equal:
movl $0, %ebx
int $0x80
2、CMPS和REP一起使用
REP指令不再两个重复过程之间检查标志的状态,它只关心ECX寄存器中的计数
器。解决方案是使用REP指令系列中的其他指令:REPE、REPNE、REPZ和
REPNZ。
.section .data
value1:
.ascii "This is a test of the CMPS instructions"
value2:
.ascii "This is a test of the CMPS Instructions"
.section .text
.globl _start
_start:
movl $1, %eax
leal value1, %esi
leal value2, %edi
movl $39, %ecx
cld
repe cmpsb
je equal
movl %ecx, %ebx
int $0x80
equal:
movl $0, %ebx
int $0x80
3、字符串不等
最常用于比较字符串的方法称为词典式顺序(lexicographical order)。这通常称
为字典顺序(dictionary order),因为这是字典对词进行排序的标准。基本规则如
下:
- 按字母表顺序、较低的字母小于较高的字母
- 大写字母小于小写字母
比较不同长度的字符串时,按照长度短一些的字符串中的字符数量进行比较。如
果短字符串大于长字符串中相同数量的字符,那么短字符串就大于长字符串。如果短
字符串小于长字符串中相同数量的字符,那么短字符串就小于长字符串。如果短字符
串等于长字符串中相同数量的字符,那么长字符串就大于短字符串。
使用这一规则,下面的例子就为真:
- “test”大于“boomerang”
- “test”小于“velocity”
- “test”小于“test1”
例子如下:
.section .data
string1:
.ascii "test"
length1:
.int 4
string2:
.ascii "test1"
length2:
.int 5
.section .text
.globl _start
_start:
leal string1, %esi
leal string2, %edi
movl length1, %eax
movl length2, %ecx
ja longer
xchg %ecx, %eax
longer:
cld
repe cmpsb
je equal
jg greater
less:
movl $1, %eax
movl $255, %ebx
int $0x80
greater:
movl $1, %eax
movl $1, %ebx
int $0x80
equal:
movl length1, %ecx
movl length2, %eax
cmpl %ecx, %eax
jg greater
jl less
movl $1, %eax
movl $0, %ebx
int $0x80
四、扫描字符串
1、SCAS指令
SCAS指令系列用于扫描字符串搜索一个或者多个字符。也有三个版本:
- SCASB:比较内存中的一个字节和AL寄存器的值
- SCASW:比较内存中的一个字和AX寄存器的值
- SCASL:比较内存中的一个双字和EAX寄存器的值
SCAS指令使用EDI寄存器作为隐含的目标操作数。EDI寄存器必须包含要扫描的
字符串的内存地址。
- REPE:扫描字符串的字符,查找不匹配搜索字符的字符
- REPNE:扫描字符串的字符,查找匹配搜索匹配搜索字符的字符
.section .data
string1:
.ascii "This is a test - a long text string to scan."
length:
.int 44
string2:
.ascii "-"
.section .text
.globl _start
_start:
leal string1, %edi
leal string2, %esi
movl length, %ecx
lodsb
cld
repne scasb
jne notfound
subw length, %cx
neg %cx
movl $1, %eax
movl %ecx, %ebx
int $0x80
notfound:
movl $1, %eax
movl $0, %ebx
int $0x80
2、搜索多个字符
SCASW和SCASL指令扫描字符串,查找AX或者EAX寄存器中的字符序列,但是
它们并不进行逐字符的比较。而是每次比较之后,EDI寄存器要么递增2(对于
SCASW),要么递增4(对SCASL),而不是递增1。如下所示:
.section .data
string1:
.ascii "This is a test - a long text string to scan."
length:
.int 11
string2:
.ascii "test"
.section .text
.globl _start
_start:
leal string1, %edi
leal string2, %esi
movl length, %ecx
lodsl
cld
repne scasl
jne notfound
subw length, %cx
neg %cx
movl $1, %eax
movl %ecx, %ebx
int $0x80
notfound:
movl $1, %eax
movl $0, %ebx
int $0x80
问题如下:
这说明字符序列也必须按照适当的顺序出现在字符串中。
3、计算字符串长度
SCAS指令的一个非常有用的功能是确定零结尾(也称为空结尾)的字符串的长
度。这些字符串经常在C程序中使用,但是也通过.asciz声明在汇编语言程序中使
用。例子如下:
.section .data
string1:
.asciz "Testing, one, two, three, testing.\n"
.section .text
.globl _start
_start:
leal string1, %edi
movl $0xffff, %ecx
movb $0, %al
cld
repne scasb
jne notfound
subw $0xffff, %cx
neg %cx
dec %cx
movl $1, %eax
movl %ecx, %ebx
int $0x80
notfound:
movl $1, %eax
movl $0, %ebx
int $0x80