labweek04
实验内容
验证实验 Blum’s Book: Sample programs in Chapter 08, 10 (Basic Math Functions and Using Strings)
实验一:addtest1.s
add指令相加两个整数
对于不同位数,可以添加助记符,b字节相加,w(字),l(双字)
as --32 --gstabs -o addtest1.o addtest1.s
ld -m elf_i386 -dynamic-linker/lib/ld-linux.so.2 -o addtest1 addtest1.o
gdb -q addtest1
可见程序能成功执行加法,如data原本为40,在执行 addl %eax, data后变为110。调试结果符合预期。
实验二:addtest2.s
as --32 --gstabs -o addtest2.o addtest2.s
ld -m elf_i386 -dynamic-linker/lib/ld-linux.so.2 -o addtest2 addtest2.o
gdb -q addtest2
观察结果和程序运行的预计结果相同,如data为-210,加上立即数210后变为0。
实验三:addtest3.s
当整数相加时,ALU的结果会影响eflags寄存器
对于无符号数,二进制加法有进位时,进位标志设置为1
对于带符号数,出现溢出情况时,溢出标志设置为1,否则为0。
as --32 --gstabs -o addtest3.o addtest3.s
ld -m elf_i386 -dynamic-linker/lib/ld-linux.so.2 -o addtest3 addtest3.o
gdb -q addtest3
运算结果超出了8位最大值255,出现了进位,jc指令发生了跳转。
实验四:addtest4.s
jo指令用于检测溢出标志,判断是否进行跳转。
as --32 --gstabs -o addtest4.o addtest4.s
ld -m elf_i386 -dynamic-linker/lib/ld-linux.so.2 -o addtest4 addtest4.o
gdb -q addtest4
由程序输出结果可知两数相加发生了负溢出,jo指令发生了跳转。
实验五:adctest.s
adc指令为两个整数加法,但是保留前一个ADD指令的进位标志。
同样有位数助记符 b、w、l 等。
as --32 --gstabs -o adctest.o adctest.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o adctest -lc adctest.o
gdb -q adctest
可见adc指令执行1+2=3,没有发生溢出,但上次运算的溢出标志被保留了下来。程序输出期望结果
实验六: subtest1.s
sub指令用作减法,有 b,w,l 助记符
as --32 --gstabs -o subtest1.o subtest1.s
ld -m elf_i386 -dynamic-linker/lib/ld-linux.so.2 -o subtest1 subtest1.o
gdb -q subtest1
可见sub指令能实现减法。
如最后data=40 - (-30) =70
实验七: subtest2.s
as --32 --gstabs -o subtest2.o subtest2.s
ld -m elf_i386 -dynamic-linker/lib/ld-linux.so.2 -o subtest2 subtest2.o
gdb -q subtest2
结果为-3,发生了借位,jc发生了跳转。
实验八:subtest3.s
as --32 --gstabs -o subtest3.o subtest3.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o subtest3 -lc subtest3.o
gdb -q subtest3
起初汇编失败,加上-lc后能汇编,但运行时显示找不到文件。
再次安装 i386 后问题解决。
ebx的值减去eax的值产生负溢出,jo指令检测到溢出标志,跳转至over。
实验九: sbbtest.s
sbb指令为无符号整数减法。
as --32 --gstabs -o sbbtest.o sbbtest.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o sbbtest -lc sbbtest.o
gdb -q sbbtest
通过拆分大整数前32位与后32位,存于4个寄存器中,对低位做sub,高位做sbb,实现了计算data2-data1。
实验十:multest.s
mul指令用于两个无符号整数的相乘,有 b,w,l 助记符。
对于8位的源操作数,目标操作数为AL,结果储存于AX寄存器
对于16位的源操作数,目标操作数为AX,结果储存于DX寄存器
对于32位的源操作数,目标操作数为EAX,结果储存于EDX寄存器
as --32 --gstabs -o multest.o multest.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o multest -lc multest.o
gdb -q multest
程序将data1的值加载到eax中,后用mul指令与data2相乘,并将结果分两次转移到64位内存result中。
实验十一:imultest.s
imul指令可用于带符号和无符号整数,但不使用目标的最高有效位,有三种格式。
也有 b,w,l 助记符。
imul source
source可以是8位,16位,32位寄存器或者内存中的值,与隐含的寄存器(ax,dx,edx)的值相乘,结果存放于ax,dx,edx,eax中。
imul source,destination
imul multiplier,source,destination,其中 multiplier为立即数
as --32 --gstabs -o imultest.o imultest.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o imultest -lc imultest.o
gdb -q imultest
ebx与ecx相乘得到-350
将400转到edx后乘以2,得到800。
结果符合预期 。
实验十二:imultest2.s
乘法同样有溢出标记,可以用jo指令检查溢出。
as --32 --gstabs -o imultest2.o imultest2.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o imultest2 -lc imultest2.o
gdb -q imultest2
尝试用680*100,结果大于65535,溢出标志为1,jo指令跳转至over处。
实验十三. divtest.s
div指令用于无符号整数的除法操作,格式为
div divisor
约束条件同mul
as --32 --gstabs -o divtest.o divtest.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o divtest -lc divtest.o
gdb -q divtest
商储存在ax/eax,余数储存在dx/edx
可见该指令能算出商和余数,并存在相应寄存器。
实验十四:saltest.s
SAL用于左移,SHL用于右移
sal destination // 左移1位
sal %cl,destination //左移cl位,cl为寄存器
sal shifter,destination //左移shifter位
as --32 --gstabs -o saltest.o saltest.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o saltest -lc saltest.o
gdb -q saltest
根据离散数学知识,左移1位即乘以2。验证了三种格式的左移命令。
实验十五:dastest.s
处理打包BCD值时可用daa调整add或者adc的结果
或用das调整sub或者sbb指令的结果
as --32 --gstabs -o dastest.o dastest.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o dastest -lc dastest.o
gdb -q dastest
减法之后,eax值为14,执行das指令之后,eax的值变为8 。
可见das指令能成功打包运算结果。
实验十六:cpuidtest.s
test指令在8位,16位或者32位值之间执行按位与操作,并设置符号,零和奇偶标志,但不修改参数的值。
as --32 --gstabs -o cpuidtest.o cpuidtest.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o cpuidtest -lc cpuidtest.o
gdb -q cpuidtest
前面一轮运算使得id标志为改变了,eax的值不为0,按位与的结果也不为0。jnz指令实现了跳转。
实验十七:movstest1.s
movs指令能在内存间传输字符串。有 b,w,l 标识符
MOVSB:传送单一字节
隐含的源操作数是 esi 寄存器。
隐含的目标操作数是 edi 寄存器。
可用 mov 指令将32位内存位置传送给 edi。
也可用 lea 指令把32位内存位置加载到 edi 中。
as --32 --gstabs -o movstest1.o movstest1.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movstest1 -lc movstest1.o
gdb -q movstest1
可见结合不同的助记符,能传递不同长度的字符串到目标位置。
如movsl能传递8字节。
实验十八:movstest2.s
若DF标志为0,movs指令执行之后 esi 和 edi 会递增。如果DF标志被设置,那么每条movs指令执行后esi 和 edi 会递减。
cld用于将DF标志清零,std用于设置DF标志
as --32 --gstabs -o movstest2.o movstest2.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movstest2 -lc movstest2.o
gdb -q movstest2
esi与edi初始指向末尾,设置了std后,每次递减。可见最终能传递最后4个字符。
实验十九:movstest3.s
as --32 --gstabs -o movstest3.o movstest3.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movstest3 -lc movstest3.o
gdb -q movstest3
循环23次,每次执行movsb指令,直到传输完整个串。
实验二十:reptest1.s
rep指令用于按照特定次数重复执行字符串指令。
as --32 --gstabs -o reptest1.o reptest1.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o reptest1 -lc reptest1.o
gdb -q reptest1
rep重复了23次,整个字符串传输完成。
实验二十一:reptest2.s
as --32 --gstabs -o reptest2.o reptest2.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o reptest2 -lc reptest2.o
gdb -q reptest2
可见,最后一次执行movsl指令时,产生了一点错误,因为它读取了以esi为起始地址的连续2字节,产生了越界
实验二十二: reptest3.s
as --32 --gstabs -o reptest3.o reptest3.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o reptest3 -lc reptest3.o
gdb -q reptest3
movsl每次传送4位,先用长度右移2,得到movsl的执行次数,剩余部分用movsb传输,理论上可以加快效率。
实验二十三:reptest4.s
as --32 --gstabs -o reptest4.o reptest4.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o reptest4 -lc reptest4.o
gdb -q reptest4
反向传输,用rep与movsb指令配合,传输整个字符串
实验二十四:stostest1.s
lods指令用于把内存中的字符串值传送到eax中。有b,w,l助记符
lods指令以esi为隐含的源操作数要加载的字符串所的内存地址。
执行完后esi递增或递减(取决于DF标志)
stos指令把eax寄存器存入内存。有b,w,l助记符。
以edi为隐含的目标操作数。
执行后edi递增或者递减(取决于DF标志)
as --32 --gstabs -o stostest1.o stostest1.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o stostest1 -lc stostest1.o
gdb -q stostest1
可见lodsb把空格字符成功加载到了al寄存器
stosb成功把空格传输到buffer中。
实验二十五:convert.s
as --32 --gstabs -o convert.o convert.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o convert -lc convert.o
./convert
先将string1加载到esi和edi中,把字符串长度加载到ecx中。然后逐个字符检查。
即把每个字符加载到al中,并且判断它是否小于0x61,或者大于Ox7a,若为小写就减去0x20把它转换大写。
实验二十六:cmpstest1.s
as --32 --gstabs -o cmpstest1.o cmpstest1.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o cmpstest1 -lc cmpstest1.o
gdb -q cmpstest1
cmps用于指令用于比较字符串值,有 b,w,l 助记符
隐含的源和目标操作数存储在esi和edi中。
根据DF标志, esi和edi会递增或者递减
同时设置eflags的进位、符号、溢出、零、奇偶校验和辅助进位标志.
两个字符串相等,cmp相减后为0,跳转至equal段。
实验二十七:cmpstest2.s
repe会在重复过程中会检查零标志位,如果被设置,就停止重复。
可以在不匹配的地方结束程序。
as --32 --gstabs -o cmpstest2.o cmpstest2.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o cmpstest2 -lc cmpstest2.o
gdb -q cmpstest2
可见在ecx=11时结束了匹配,刚好为第一个大小写不同字符的位置。
实验二十八:strcomp.s
cmp指令其实是比较字典序
xchg指令为交换
as --32 --gstabs -o strcomp.o strcomp.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o strcomp -lc strcomp.o
gdb -q strcomp
由于“test”小于“test1”,程序跳转至less除,并结束。
实验二十九:scastest1.s
scas指令用于在字符串中搜索若干个字符。
scasb比较内存中字节和al寄存器
scasw比较一个字
scasl比较双字
edi和esi为隐含操作数
repe扫描字符串的字符,查找不匹配搜索字符的字符
repne扫描字符串的字符,查找匹配搜索字符的字符
as --32 --gstabs -o scastest1.o scastest1.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o scastest1 -lc scastest1.o
gdb -q scastest1
cx结果为-16。输出结果显示在字符串的第16个位置找到“-”字符,没有跳转至 notfound 和预计的结果相同。
实验三十:scastest2.s
scasw和scasl指令用于搜索2个或者4个字符的序列。
scasw后edi递增2
scasl后edi递增1
as --32 --gstabs -o scastest2.o scastest2.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o scastest2 -lc scastest2.o
gdb -q scastest2
程序在字符串中查找字符序列“test”。但因为string1中的 test 并没有位于同一个四字节中,因此与每一个双字都不匹配,返回0。
实验三十一:strsize.s
scas指可以确定零结尾的字符串长度
as --32 --gstabs -o strsize.o strsize.s
ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o strsize -lc strsize.o
gdb -q strsize
```

先把字符串加载到**edi**中,**ecx**记录查找到结尾的零经过了多少次迭代。
如果**scasb**指令找到了零,就必须通过ECX寄存器的值计算它的位置。
最后与初值相减,存到**cx**中。可见输出了字符串长度35。
## 实验总结
通过这次实验更加熟悉了x86汇编语言,熟悉了加减乘除指令,**esi** **edi**寄存器的应用。字符串的传输,比较等,没有遇到特别大的困难。