注:以下内容为学习笔记,多数是从书本、资料中得来,只为加深印象,及日后参考。然而本人表达能力较差,写的不好。因非翻译、非转载,只好选原创,但多数乃摘抄,实为惭愧。但若能帮助一二访客,幸甚!
一.传送字符串
把字符串从一个内存位置复制到另一个内存位置。
1.MOVS
3种格式:MOVSB:传送单一字节
MOVSW:传送一个字(2字节)
MOVSL:传送一个双字(4字节)
MOVS使用隐含的源(ESI)、目的(EDI)操作数。
两种加载ESI、EDI值的方式:
1)间接寻址:
movl $output, %edi
2)lea指令加载一个对象的有效地址
leal output, %esi
每次执行MOVS指令时,数据传送后,ESI和EDI寄存器会自动改变,为另一次传送做准备。
ESI、EDI可能递增也可能递减,这取决于EFLAGS中的DF标志。如果DF被清零则递增,DF被设置,则递减。
CLD将DF清零
STD设置DF标志
示例:
- # An example of the MOVS instructions
- .section .data
- str:
- .ascii "Hi AT&T.\n"
- .section .bss
- .lcomm output, 9
- .section .text
- .global _start
- _start:
- nop
- leal str, %esi
- leal output, %edi
- movsb
- movsw
- movsl
- movl $1, %eax
- movl $0, %ebx
- int $0x80
liury@liury-laptop:~/program/asm/working_with_string/movs_example$ make
as -gstabs -o movs_example.o movs_example.s
ld -o movs_example movs_example.o
liury@liury-laptop:~/program/asm/working_with_string/movs_example$ ls
makefile movs_example movs_example.o movs_example.s
liury@liury-laptop:~/program/asm/working_with_string/movs_example$ gdb ./movs_example
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/liury/program/asm/working_with_string/movs_example/movs_example...done.
(gdb) l
1 # An example of the MOVS instructions
2 .section .data
3 str:
4 .ascii "Hi AT&T.\n"
5
6 .section .bss
7 .lcomm output, 9
8
9 .section .text
10 .global _start
(gdb) b *_start+1
Breakpoint 1 at 0x8048075: file movs_example.s, line 15.
(gdb) r
Starting program: /home/liury/program/asm/working_with_string/movs_example/movs_example
Breakpoint 1, _start () at movs_example.s:15
15 leal str, %esi
(gdb) n
16 leal output, %edi
(gdb) print /x $esi
$1 = 0x8049094
(gdb) print /x str
$2 = 0x41206948
(gdb) print /x &str
$3 = 0x8049094
(gdb) n
18 movsb
(gdb) x /9cb &output
0x80490a0 <output>: 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000' 0 '\000'
0x80490a8 <output+8>: 0 '\000'
(gdb) x /s &output
0x80490a0 <output>: ""
(gdb) n
19 movsw
(gdb) x /s &output
0x80490a0 <output>: "H"
(gdb) n
20 movsl
(gdb) x /s &output
0x80490a0 <output>: "Hi "
(gdb) n
22 movl $1, %eax
(gdb) x /s &output
0x80490a0 <output>: "Hi AT&T"
(gdb) c
Continuing.
Program exited normally.
(gdb) q
对于要传送大型字符串,可用循环:
- leal datas, %esi
- leal output, %edi
- mvol $100, %ecx
- cld
- loop_cp:
- movsb
- loop loop_cp
2.REP
可更简单地传送大型字符串。它自己并不执行什么操作,这条指令用于按照特定次数重复执行字符串指令,由ECX寄存器中的值进行控制。例如- leal datas, %esi
- leal output, %edi
- mvol $100, %ecx
- cld
- rep movsb
MOVSW,MOVSL传送大型字符串效率更高,但小心不能除尽的情况。
有些REP指令除监视ECX外还监视ZF(零标志)的状态。
--------------------------------------------------
指令 描述
-------------------------------------------
REPE 等于时重复
REPNE 不等于时重复
REPNZ 不为0时重复
REPZ 为0时重复
---------------------------------------------------
二.存储和加载字符串
1.LODS
用于把内存中的字符串传送到EAX。三种形式:LODSB:把一个字节加载到AL
LODSW:把一个字加载到AX
LODSL:把一个双字加载到EAX
LODS指令使用ESI寄存器作为隐含的源操作数。ESI必须包含要加载的字符串所在的内存地址。
同样加载后ESI递增或递减取决于DF标志。
2.STOS
把字符串从EAX放到一个内存地址。STOSB:存储AL中一个字节数据
STOSW:存储AX中一个字数据
STOSL:存储EAX中一个双字数据
STOS指令使用EDI作为隐含的目标操作数。可方便地与REP配合:
- leal space, %esi
- leal buffer, %edi
- movl $256, %ecx
- cld
- lodsb
- rep stosb
字符串处理示例,小写变大写:
- # Converting lower to upper case
- .section .data
- str1:
- .asciz "This is a TEST, of the program, hello AT&R!\n"
- len:
- .int . - str1
- .section .text
- .global _start
- _start:
- nop
- leal str1, %esi
- movl %esi, %edi
- movl len, %ecx
- cld
- 1:
- lodsb
- cmpb $'a', %al
- jl skipb
- cmpb $'z', %al
- jg skipb
- subb $0x20, %al
- skipb:
- stosb
- loop 1b
- end:
- pushl $str1
- call printf
- addl $4, %esp
- pushl $0
- call exit
liury@liury-laptop:~/program/asm/working_with_string/str_process$ ./convert
THIS IS A TEST, OF THE PROGRAM, HELLO AT&R!
三.比较字符串
1.CMPS
三种格式:CMPSB,CMPSW,CMPSL隐含的源和目的操作数同样存储在ESI和EDI寄存器中。每次执行CMPS时,根据DF的设置,ESI和EDI递增或递减
CMPS指令从源字符串减去目标字符串,并且适当地设置EFLAGS的寄存器的进位、符号、溢出、零、奇偶校验和辅助进位标志。后面可跟跳转指令。
2.CMPS和REP一起使用
示例:- # An example of using the REPE CMPS instruction
- .section .data
- output:
- .asciz "The len is %d.\n"
- str1:
- .ascii "This is a test of the cmps instructions."
- str2:
- .ascii "This is a test of the CMPS instructions."
- len:
- .int . - str2
- .section .text
- .global _start
- _start:
- nop
- movl len, %ecx
- pushl %ecx
- pushl $output
- call printf
- addl $8, %esp
- leal str1, %esi
- leal str2, %edi
- movl len, %ecx
- movl $1, %eax
- cld
- repe cmpsb
- je equal
- movl %ecx, %ebx
- int $0x80
- equal:
- movl $0, %ebx
- int $0x80
liury@liury-laptop:~/program/asm/working_with_string/cmps_rep$ make
as -gstabs -o cmps_rep.o cmps_rep.s
ld -dynamic-link /lib/ld-linux.so.2 -lc -o cmps_rep cmps_rep.o
liury@liury-laptop:~/program/asm/working_with_string/cmps_rep$ ./cmps_rep
The len is 40.
liury@liury-laptop:~/program/asm/working_with_string/cmps_rep$ echo $?
17
解析:
上面的代码首先尝试来'.'运算符作为当前地址,求字符串长度的方法,为验证其正确性,程序开始处打印来一下即40。
然后比较两个字符串,直到第一个不相同的字符结束,即‘cmps’与‘CMPS’中的c处。
系统调用结束,调用号保存在EAX中,返回值保存在EBX中。则该程序的返回值若比较结束,即所有字符都相同则返回0,若不同则返回ECX(传到EBX返回)的值,本例为17(通过echo $?打印),17为从后往前数,第一个不同的字符的位置,即c的位置。
四.扫描字符串
1.SCAS
用于扫描字符串搜索一个或多个字符。三种形式:SCASB,SCASW,SCASL,分别比较内存中的一个自己、字、双字和AL、AX、EAX的值。
SCAS使用EDI作为隐含的目标操作数。EDI必须包含要扫描的字符串的内存地址。指令执行时EDI按DF值递增或递减。
进行比较时会相应地设置EFLAGS标志。
可与REPE,REPNE一起使用:
REPE:扫描字符串,查找不匹配搜索字符的位置
REPNE:扫描字符串,查找匹配搜索字符的位置
示例:
- # An example of the SCAS instruction
- .section .data
- str:
- .ascii "Hello AT&T!"
- len:
- .int . - str
- char:
- .ascii "&"
- .section .text
- .global _start
- _start:
- nop
- leal str, %edi
- leal char, %esi
- movl len, %ecx
- lodsb
- cld
- repne scasb
- jne notfound
- subw len, %cx # len是长度,cx值为找到的位置距离末尾的位置,
- # 则%cx - len为正着数位置的负数
- neg %cx # neg 为求补指令,即负数变正数
- movl %ecx, %ebx # 作为中断的返回值,可在程序退出后echo $?查看
- movl $1, %eax
- int $0x80
- notfound:
- movl $1, %eax
- movl $0, %ebx
- int $0x80
liury@liury-laptop:~/program/asm/working_with_string/find_char$ make
as -gstabs -o find_char.o find_char.s
ld -o find_char find_char.o
liury@liury-laptop:~/program/asm/working_with_string/find_char$ ./find_char
liury@liury-laptop:~/program/asm/working_with_string/find_char$ echo $?
9
2.搜索多个字符
SCASW,SCASL可以搜索2或4个字符的序列,但他们不会逐字符比较,而是每次递增2或4.如在“abctestaaabb”中搜索用SCASL搜索“test”会用“test”与第一个串中的"abct", "esta", "aabb"依次比较,而不会逐字符增减寻找"test" 所以结果是找不到。
3.计算字符串的长度
SCAS指令的一个非常有用的功能是确定0结尾的字符串的长度。示例:
- # Finding the len of a string useing the SCAS instruction
- .section .data
- output:
- .asciz "The len of the str is : %d.\n"
- str:
- .asciz "I am learning AT&T assembly language."
- len:
- .int . - str - 1 # 减一表示去掉最后的'0'
- .section .text
- .global _start
- _start:
- nop
- pushl len
- pushl $output
- call printf
- addl $8, %esp
- leal str, %edi
- movl $0xffff,%ecx # 支持的最大长度0xfffff = 65535
- movb $0, %al
- cld
- repne scasb # 每次迭代ECX递减
- jne notfound
- subw $0xffff,%cx # %cx-0xffff为进行了多少次迭代的负数
- neg %cx # 求补,即负数变整数
- dec %cx # 减去‘0’,即字符串长度不包含最后的‘0’
- movl $1, %eax
- movl %ecx, %ebx
- int $0x80
- notfound:
- movl $1, %eax
- movl $0, %ebx
- int $0x80
liury@liury-laptop:~/program/asm/working_with_string/str_len$ make
as -o str_len.o str_len.s
ld -dynamic-linker /lib/ld-linux.so.2 -lc -o str_len str_len.o
liury@liury-laptop:~/program/asm/working_with_string/str_len$ ./str_len
The len of the str is : 37.
liury@liury-laptop:~/program/asm/working_with_string/str_len$ echo $?
37
用两种方法求得字符串的长度,两者相等。