操作系统实验报告2
一、实验内容
验证实验 Blum’s Book: Sample programs in Chapter 06, 07 (Controlling Flow and Using Numbers)
二、实验环境
Ubuntu 18.04(64位)
三、实验过程
1、Sample programs in Chapter 06
第六章介绍了汇编语言如何通过跳转和循环,完成控制执行流程。要实现跳转和循环两个功能,需要对指令指针进行操作。当指令指针在程序指令中移动时,EIP寄存器会递增。程序不能直接修改指令指针,但是,可以利用能够改动指令指针值的分支指令(branch)来改动EIP寄存器的值。
分支指令分为无条件分支和条件分支,它们能控制指令指针和程序逻辑的执行路径。
1)jumptest.s
无条件分支分为跳转、调用、中断。
跳转指令使用单一指令码:
jmp location
location是要跳转到的内存地址。
在终端中输入命令行:
#编译链接运行 $ as --32 -o jumptest.o jumptest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o jumptest -lc jumptest.o $ ./jumptest $ echo $? #使用反汇编器查看每条指令的内存位置 $ objdump -D jumptest #使用调试器对代码进行汇编,查看EIP寄存器的值 $ as --32 -gstabs -o jumptest.o jumptest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o jumptest -lc jumptest.o $ gdb -q jumptest
输出结果:
2)calltest.s
调用指令:
call address address: ret
调用指令分为两部分,第一部分为CALL指令,address操作数引用程序中的标签;调用指令的第二部分是返回指令,在函数中的助记符RET使函数可以返回代码的原始部分。
在终端中输入命令行:
$ as --32 -o calltest.o calltest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o calltest -lc calltest.o $ ./calltest
输出结果:
3)cmptest.s
条件跳转按照EFLAGS寄存器的当前值来确定是否进行跳转。
条件跳转指令:
jxx address
其中xx是1个到3个字符的条件代码,address是程序要跳转到的位置。
比较指令:
cmp operand1, operand2
当前者<=后者时,执行跳转,反之不进行。
在终端中输入命令行:
$ as --32 -o cmptest.o cmptest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o cmptest -lc cmptest.o $ ./cmptest $ echo $?
输出结果:
可见此时前者大于后者,不跳转,EBX寄存器的值不变。
修改文件,将15赋值给EAX寄存器和EBX寄存器,再次编译链接运行:
可见此时比较双方相等,跳转到greater标签,将20赋给了EBX寄存器。
4)paritytest.s
奇偶校验可以作为跳转条件的标志。奇偶校验标志表明数学运算答案中应该为1的位的数目。偶置1,奇置0。
在终端中输入命令行:
$ as --32 -o paritytest.o paritytest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o paritytest -lc paritytest.o $ ./paritytest $ echo $?
输出结果:
为1的位数是奇数,不设置奇偶校验位,指令不跳转。
修改文件中SUBL指令,改为:subl $1, %ebx
输出结果:
生成的结果中为1的位数为偶数,奇偶校验位置1,指令跳转。
5)signtest.s
符号标志可以作为跳转条件的标志。符号标志用于表示寄存器中包含的值的符号改变,在带符号数中,最高位为符号位。
用as进行编译时会出现编译错误,将文件中的add $8, $esp
改为add $8, %esp
即可解决。
在终端中输入命令行:
$ as --32 -o signtest.o signtest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o signtest -lc signtest.o $ ./signtest
输出结果:
6)loop.s
循环是改变程序内指令路径的另外一种方式,循环可以使用单一循环函数编写重复性任务的代码。
循环指令:
loop address
address是要跳转到的程序代码位置的标签名称,由于循环指令只支持8位偏移量,所以只能进行短跳转。
在终端中输入命令行:
$ as --32 -o loop.o loop.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o loop -lc loop.o $ ./loop
输出结果:
修改文件,将ECX寄存器初值设为0:movl $0, %ecx
由于在执行LOOP指令时,它首先把ECX中的值递减1,后检查其是否为0。因此,当ECX初值为0时,LOOP指令会递减使其成为-1,由于其非零,LOOP指令会一直执行,最终在寄存器溢出时退出,并显示错误的值。
7)betterloop.s
为纠正上述问题,在上一个loop程序的基础上,加入检查ECX寄存器包含零值时的特殊条件。
文件有误,按照书本原文程序修改后,在终端中输入命令行:
$ as --32 -o betterloop.o betterloop.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o betterloop -lc betterloop.o $ ./betterloop
输出结果:
解决了ECX寄存器包含零的初值的问题。
8)ifthen.c
在终端中输入命令行:
$ gcc -S ifthen.c $ cat ifthen.s
查看代码如何转换为汇编语言。
输出结果:
➜ chap06 gcc -S ifthen.c ➜ chap06 cat ifthen.s .file "ifthen.c" .text .section .rodata .LC0: .string "The higher value is %d\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $100, -8(%rbp) movl $25, -4(%rbp) movl -8(%rbp), %eax cmpl -4(%rbp), %eax jle .L2 movl -8(%rbp), %eax movl %eax, %esi leaq .LC0(%rip), %rdi movl $0, %eax call printf@PLT jmp .L3 .L2: movl -4(%rbp), %eax movl %eax, %esi leaq .LC0(%rip), %rdi movl $0, %eax call printf@PLT .L3: movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0" .section .note.GNU-stack,"",@progbits
9)for.c
在终端中输入命令行:
$ gcc -S for.c $ cat for.s
查看代码如何转换为汇编语言。
输出结果:
➜ chap06 gcc -S for.c ➜ chap06 cat for.s .file "for.c" .text .section .rodata .LC0: .string "The answer is %d\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $0, -8(%rbp) movl $0, -8(%rbp) jmp .L2 .L3: movl -8(%rbp), %edx movl %edx, %eax sall $2, %eax addl %edx, %eax movl %eax, -4(%rbp) movl -4(%rbp), %eax movl %eax, %esi leaq .LC0(%rip), %rdi movl $0, %eax call printf@PLT addl $1, -8(%rbp) .L2: cmpl $999, -8(%rbp) jle .L3 movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0" .section .note.GNU-stack,"",@progbits
2、Sample programs in Chapter 07
第七章介绍了汇编语言程序中可用的不同数字格式,并演示了如何使用它们。
1)inttest.s
该程序演示把带符号整数存储在寄存器中的3种不同方式,在该过程中,内存或者寄存器中表示的带符号整数经常是难以识别的。
在终端中输入命令行:
$ as --32 -gstabs -o inttest.o inttest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o inttest -lc inttest.o $ gdb -q inttest
输出结果:
EDX寄存器中的值是错误的数字。
2)movzxtest.s
为解决上述问题,需要对无符号整数或有符号整数做扩展。MOVZX指令把长度小的无符号整数(可以在寄存器中,也可以在内存中)传送给长度大的无符号整数值(只能在寄存器中)。
MOVZX指令:
movzx source, destination
其中source可以是8位或16位的寄存器或内存位置,destination可以是16位或32位寄存器。
在终端中输入命令行:
$ as --32 -gstabs -o movzxtest.o movzxtest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movzxtest -lc movzxtest.o $ gdb -q movzxtest
输出结果:
3)movsxtest.s
MOVSX指令和MOVZX指令类似,但它允许扩展带符号整数并保留符号。
MOVSX指令:
movsx source, destination
其中source可以是8位或16位的寄存器或内存位置,destination可以是16位或32位寄存器。
在终端中输入命令行:
$ as --32 -gstabs -o movsxtest.o movsxtest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movsxtest -lc movsxtest.o $ gdb -q movsxtest
输出结果:
4)movsxtest2.s
在终端中输入命令行:
$ as --32 -gstabs -o movsxtest2.o movsxtest2.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o movsxtest2 -lc movsxtest2.o $ gdb -q movsxtest2
输出结果:
5)quadtest.s
.quad
命令可以定义一个或者多个带符号整数值,但是为每个值分配8个字节。
在终端中输入命令行:
$ as --32 -gstabs -o quadtest.o quadtest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o quadtest -lc quadtest.o $ gdb -q quadtest
输出结果:
6)mmxtest.s
MOVQ指令能将数据传送到MMX寄存器中。
MOVQ指令:
movq source, destination
source和destination可以是MMX寄存器、SSE寄存器或者64位的内存位置(但是不能在内存位置之间传送MMX整数)。
在终端中输入命令行:
$ as --32 -gstabs -o mmxtest.o mmxtest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o mmxtest -lc mmxtest.o $ gdb -q mmxtest
输出结果:
7)ssetest.s
MOVDQA指令和MOVDQU指令用于把128位数据传送到XMM寄存器,或者在XMM寄存器之间传送数据。助记符的A和U部分代表对准和不对准。对于对准16个字节边界的数据,使用A(此时SSE指令执行得更快,并且,若对未对准的数据使用该指令,会造成硬件异常),反之使用U。
MOVDQA指令和MOVDQU指令:
movdqa source, destination movdqu source, destination
source和destination可以是SSE 128位寄存器或者128位的内存位置(但是不能再在内存位置之间传送数据)。
在终端中输入命令行:
$ as --32 -gstabs -o ssetest.o ssetest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o ssetest -lc ssetest.o $ gdb -q ssetest
输出结果:
8)bcdtest.s
FBLD指令用于把打包80位BCD值传送到FPU寄存器堆栈中。
FBLD指令:
fbld source
source是80位的内存位置。
该程序演示将BCD值加载到FPU寄存器中以及从FPU寄存器获取BCD值的基本操作。
在终端中输入命令行:
$ as --32 -gstabs -o bcdtest.o bcdtest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o bcdtest -lc bcdtest.o $ gdb -q bcdtest
输出结果:
st0 1234 (raw 0x40099a40000000000000)
st0 2468 (raw 0x400a9a40000000000000)
9)floattest.s
FLD指令用于把浮点值传送出入FPU寄存器。
FLD指令:
fld source
source可以是32位,64位或者80位的内存位置。
该程序演示将BCD值加载到FPU寄存器中以及从FPU寄存器获取BCD值的基本操作。
在终端中输入命令行:
$ as --32 -gstabs -o floattest.o floattest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o floattest -lc floattest.o $ gdb -q floattest
输出结果:
10)fpuvals.s
该程序演示如何使用预置的浮点值,简单地将各个浮点常量压入到FPU寄存器堆栈中。
在终端中输入命令行:
$ as --32 -gstabs -o fpuvals.o fpuvals.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o fpuvals -lc fpuvals.o $ gdb -q fpuvals
输出结果:
11)ssefloat.s
该程序演示如何传送SSE打包单精度浮点值
在终端中输入命令行:
$ as --32 -gstabs -o ssefloat.o ssefloat.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o ssefloat -lc ssefloat.o $ gdb -q ssefloat
输出结果:
12)sse2float.s
该程序演示如何传送SSE2打包双精度浮点值。
在终端中输入命令行:
$ as --32 -gstabs -o sse2float.o sse2float.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o sse2float -lc sse2float.o $ gdb -q sse2float
输出结果:
13)convtest.s
该程序演示转换数据类型。
将第8行的 data:
删除后,在终端中输入命令行:
$ as --32 -gstabs -o convtest.o convtest.s $ ld -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 -o convtest -lc convtest.o $ gdb -q convtest
输出结果:
四、实验总结
通过这次实验,我验证了书本中关于控制执行和数据类型的案例汇编程序,对相关汇编语言知识的了解更深入。同时,对linux环境和命令使用更加熟悉。