计算机系统实验二:数据的存储与运算
参考教材:计算机系统基础 第二版 袁春风 机械工业出版社
参考慕课:计算机系统基础(四):编程与调试实践 https://www.icourse163.org/learn/NJU-1449521162
计算机系统实验导航
实验一:环境安装 https://blog.csdn.net/weixin_46291251/article/details/122477054
实验二:数据的存储与运算 https://blog.csdn.net/weixin_46291251/article/details/122478255
实验三:程序的机器级表示 https://blog.csdn.net/weixin_46291251/article/details/122478979
实验四:二进制程序逆向工程 https://blog.csdn.net/weixin_46291251/article/details/122479554
实验五:缓冲区溢出攻击 https://blog.csdn.net/weixin_46291251/article/details/122479798
实验六:程序的链接 https://blog.csdn.net/weixin_46291251/article/details/122480049
实验源码: xxx
准备
实验内容:
1 熟悉基本汇编语言程序;
2 不同类型数据在计算机的编码、存储、转换,整型数据加减运算及其计算机底层实现,浮
点数据的表示与运算。
实验目标:
1 理解计算机中数据的表示、存储和运算,熟悉程序的机器级表示;
2 学习和掌握程序的调试方法,强化计算机编程实践能力;
3 掌握 C 语言中位操作语句的使用。
实验任务:
1 学习 MOOC 内容
https://www.icourse163.org/learn/NJU-1449521162
- 第三周 数据的存储与运算
第 1 讲 真值与机器数
第 2 讲 数据的宽度与存储
第 3 讲 数据类型的转换
第 4 讲 整数加减运算
第 5 讲 浮点数的表示和运算 - 第四周 程序的机器级表示
第 1 讲 传送指令
第 2 讲 加减运算指令
第 3 讲 整数乘法指令
2 完成作业
题目一:反汇编解释程序
程序代码和注释说明
1.#include “stdio.h”
2.void main()
3.{
4.int ai=100, bi=2147483648, ci=-100;
5.unsigned au=100, bu=2147483648, cu=-100;
6.printf(“ai =%d, bi=%d, ci=%d\n”, ai, bi, ci);
7.printf(“au =%d, bu=%d, cu=%d\n”, au, bu, cu);
8.}
实验结果记录
对程序进行编译、反汇编、运行得到的结果如下:
执行程序时。ai bi ci 都是非静态局部变量,执行程序时被放在栈帧中。100, 2147483648, -100;可看作程序中的常数,把这些常数赋值给这些整形变量,并且放在栈帧中时,编译程序就把这些常数,直接编码在机器指令中
编译程序对这些常数进行编码的方法 :
编译时已经将这些常数编码在指令中,执行这些指令时就把这些数据直接复制给变量。
进入gdb
根据函数名设置断点在main函数处,运行程序。
R[ebp]- R[esp]+4=44
由于int占4个字节所以:44/4=11
结果分析与讨论
带符号整数bi的输出结果值为何是负数?
bi是int类型的数据,它的机器数是十六进制的0x80000000,程序中要求把bi输出为带符号中整数时,处理程序就把0x80000000当作补码数转换成真值,最高符号位由于为1,所以转换后的数是负数。
无符号整数cu,赋值一个负的数据后,cu输出的结果为什么会是这个值?
cu是无符号整数,cu的机器数是十六进制的0xffffff9c,当程序中要求cu按照无符号整数输出时,处理程序就把0xffffff9c每一位都当作数值位来处理,cu输出的值为429496719。
ai和ci他们的值一个是100一个-100,从真值的角度讲他们只差一个符号位,但他们在计算机内部的机器数差异却很大,ai的机器数是0x00000064,ci的机器数是0xffffff9c,这是因为补码对正数和复数的编码值的差异。
cu在计算机中实际存储的内容是什么?
Cu把-100的编码0xffffff9c赋值给了cu,所以cu的机器数是0xffffff9c,cu输出的真值是0xffffff9c的二进制值。
题目二:回答以下问题
C 语言程序如下,代码运行过程中各变量存储的机器数分别是什么?i1 和 i2 的值是否相同?f1 和 f2 的值是否相同?利用反汇编程序代码对结果进行解释说明。
程序代码和注释说明
1.#include “stdio.h”
2.int main()
3.{
4.int i1=0x7fffffff, i2, itemp;
5.float f1=0x987654321, f2, ftemp;
6.ftemp=i1;
7.i2=ftemp;
8.itemp=f1;
9.f2=itemp;
10.printf(“i1=%d, i2=%d, f1=%f, f2=%f\n”, i1, i2, f1, f2);
11.}
实验结果记录
对程序进行编译、反汇编、运行得到的结果如下:
查看当前栈帧内容
上图可得出i1和i2的机器数,并不相同。
结果分析与讨论
i1的机器数为0x7fffffff,为了转换成float类型的浮点数,要把i1写成尾数和阶码的形式,尾数的有效数字有31位,而float格式的浮点数的有效数字只有24位,所以需要对尾数进行舍入,舍去尾部的7位,然后根据IEEE754的舍入原则,这里执行入的操作,所以在有效数字最低位加1,有效数字就变成了10B,但是这又不符合规格化的尾数了,所以需要把尾数调整为1.0B,阶码需要加1,所以阶码为31,最终i1转化成的浮点数为:符号位为0阶码为31+127尾数为23个0。要把这个浮点数赋值给i2,又要进行浮点格式和补码的转换,这个浮点数的真值就是1.0*2^31(1后面跟31个0),所以这就是i2的机器数,i1与i2机器数不同的原因就是在i1转化成浮点数时进行了近似处理。从机器数的编码来看,i2的编码比i1的编码大1,但是将i2的机器数还原为真值后,i2的真值为负数,与i1的真值差异较大。如下图所示:
由上述反汇编得出的代码可知,f1的机器数是16进制的0x51187654。
f1的初始值是0x987654321用二进制编码有36个二进制位,超过了float浮点数的24位精度,所以这里要进行舍操作,f1的浮点数的编码是符号位为0阶码为35+127尾数是00110000111011001010100三十二位二进制(f1的机器数),f1转换为int类型时需要做编码格式上的转换,f1的机器数对初始数据做过舍操作,所以f1的真值相对于初始值f1的二进制依旧有36位,但是它的低12位全为0,int类型有32位而f1有36位,超过了int的表示范围,f1转换为itemp时溢出。
f2与f1不一样的根本原因是f1超出了int的表示范围,转换为int时,系统赋值最高位为1其余位为1
题目三:分析运行过程中寄存器 eax,ebx,ecx 中的值
程序代码和注释说明
1.#include “stdio.h”
2.void main()
3.{
4.int p[2]={0x12345678,0x11223344};
5.asm
6.(
7.“lea -0x14(%ebp),%eax\n\t”
8.“mov -0x14(%ebp),%ebx\n\t”
9.“mov $1,%ecx\n\t”
10.“lea -0x14(%ebp,%ecx,4),%eax\n\t”
11.“mov -0x14(%ebp,%ecx,4),%ebx\n\t”
12.);
13.printf(“understand mov and lea\n”);
14.}
运行程序:
源代码含义:
首先创建一个大小为2的int类型数组
然后在代码中嵌入汇编语句,其中:
//ebp-10是p0的地址
"lea -0x10(%ebp),%eax\n\t" //将p0的地址赋值给eax
"mov -0x10(%ebp),%ebx\n\t" //将p0地址的值赋值给ebx
"mov $1,%ecx\n\t" //ecx相当于P[i]的下标i
//ebp-10 + ecx*4是p1的地址, int占4个字节
"lea -0x10(%ebp,%ecx,4),%eax\n\t" //p1的地址
"mov -0x10(%ebp,%ecx,4),%ebx\n\t" //p1地址上存储的数据
实验结果记录
先对源代码进行编译和反汇编,打开反汇编得到的代码,如下:
从中可以看出P0被放在-0x10(%ebp)的位置,p1被放在-0xc(%ebp)的位置。
修改后的源代码:
然后打开gdb调试
输出当前的栈帧:
执行两个汇编语句,然后输出eax和ebx的值:
执行三个汇编语句,然后输出eax和ebx的值:
结果分析与讨论
由上述调试结果不难看出:
执行完前两个汇编语句后,lea指令将p0的地址传送给了eax寄存器,而mov指令将p0 的数值赋值给了ebx寄存器。
然后根据首地址和长度计算出p1的地址,再用同样的方法处理p1,得到的结果依然是eax接收p1的地址,ebx接收p1的值.
所以得出结论:Lea指令实现的是地址传送,mov实现的是数据传送。
题目四:只用运算符~和|来实现位的与操作函数:
1.int bitAnd(int x, int y)
2.例如:bitAnd(6, 5) = 4
程序代码和注释说明
1.# include<stdio.h>
2.int bitAnd(int x, int y){
3. return ~ (~x | ~y);
4.}
5.
6.void main(){
7.
8. int ans = bitAnd(6,5);
9. printf("%d\n",ans);
10.}
思路:
要利用运算符~和|来实现bitand的功能,只需要将两个操作数先分别取反,然后做或操作,结果再取反即可。
即 a&b == (a | ~b )
实验结果记录
查看程序内容:
生成可执行文件并运行程序:
可见程序结果满足需求。
题目五:只用运算符! ~ & ^ | + << >>实现比较 x 和 y 的大小的函数:
1.int isLessOrEqual(int x, int y)
2.例如:isLessOrEqual(4, 5) = 1
程序代码和注释说明
1.# include<stdio.h>
2.int isLessOrEqual(int a,int b) {
3. int diff = a ^ b;
4. if (!diff) return 1; //a = b
5.
6. // 001xxxxx -> 00100000
7. diff |= diff >> 1;
8. diff |= diff >> 2;
9. diff |= diff >> 4;
10. diff |= diff >> 8;
11. diff |= diff >> 16;
12. diff ^= diff >> 1;
13.
14. if (!(a & diff))
15. return 1; //a < b
16. else
17. return 0; //a > b
18.
19.}
20.void main(){
21.
22. //int a=1,b=5;
23. int ans = isLessOrEqual(4,5);
24. printf("%d\n",ans);
25.
26.}
比较的原理: 只需要找出第一个从最高位开始找出第一个不同的Bit,这一位是1的数是较大者。
实验结果记录
生成可执行文件并运行程序:
可见程序结果满足需求。