深入理解计算机系统

写在前面:
1.这是我的个人作业,在这里写什么样我交上去就是什么样,希望各位不要原样抄袭。
2.这里的题目只是我OCR了方便搜索,真要看题目还是得书。
3.CSDN的Markdown编辑器和我本地的不完全一样,尤其是LaTeX的语法支持,如果有部分语法错误导致公式无法显示的,请告知我~
特别批评CSDN没有汇编的语法高亮,数典忘祖。这里用Java的顶替一下

3.58 简单的由汇编代码还原C代码

—个函数的原型为long decode2(long x, long y, long z);
GCC 产生如下汇编代码:

参数x y 和z 通过寄存器%rdi, %rsi和%rdx传递。代码将返回值存放在寄存器%rax中。
写出等价于上述汇编代码的decode2 的C 代码。

x in %rdi,y in %rsi,z in %rdx
subq %rdx, %rsi //y=y-z
imulq %rsi, %rdi //x=x*y
movq %rsi, %rax //%rax=y
salq $63, %rax//%rax<<=63
sarq $63, %rax//%rax>>=63
xorq %rdi, %rax//%rax^=x
ret //return %rax
long decode2(long x,long y,long z){y=y-z;x=x*y;long t=y;//t=%raxt<<=63;t>>=63;t^=x;return t;
}

3.60 看汇编代码填写C代码空

考虑下面的汇编代码:

以上代码是编译以下整体形式的C 代码产生的:

你的任务是填写这个c 代码中缺失的部分,得到一个程序等价于产生的汇编代码。回想一下,
这个函数的结果是在寄存器%rax 中返回的。你会发现以下工作很有帮助:检查循环之前、之中和
之后的汇编代码,形成一个寄存器和程序变量之间一致的映射。
A.哪个寄存器保存着程序值 x、n、result和mask?
B. result 和mask 的初始值是什么?
C. mask 的测试条件是什么?
D. mask 是如何被修改的?
E. result 是如何被修改的
F. 填写这段C 代码中所有缺失的部分。

long loop(long x, int n)
x in %rdi, n in %esi// %rdi=x %esi=n
loop:movl %esi, %ecx //%esi->%ecx即%rcx=nmovl $1, %edx //$1->%edx即%rdx=1movl $0, %eax //$0->%rax即%rax=0jmp .L2 //无条件跳转到.L2过程
.L3movq %rdi, %r8 //%rdi->%r8即x->%r8andq %rdx, %r8 //%r8 &= %rdx,在loop中,%rdx被赋值了1orq %r8, %rax //%rax |= %r8,在loop中,%rax被赋值了0salq %cl, %rdx //%rdx <<= %cl
.L2 testq %rdx, %rdx // flag=%rdx & %rdxjne .L3 // flag不等于0时,跳转到L3,jne->jump when not equal 0rep; ret // return %rax

分析:

  1. 由于.L2中有ret指令和条件判断指令,认定其为for循环的跳出条件和结果的return,由于return的是%rax的值,所以推理出result = %rax
  2. 由于.L2中出现了条件跳转到.L3,并且.L3中的 orq 命令为取或,与代码中 result |=… 一致,推理出.L3过程为循环体,包含for的第三个语句和循环语句。
  3. 由过程名和排除法判断,loop是函数开始的准备阶段。
  4. .L3的salq命令后于循环体运行,推理出salq是for的第三个表达式,所以%rdx是mask,其操作是左移%cl位
  5. 回到loop中,result对应的%rax被赋了初值0,所以C代码中第一行result被赋值为0
  6. mask对应的%rdx被赋值了1,所以for循环的第一个语句是mask=1
  7. .L2中说明了循环继续的条件 mask & mask !=0,由自幂律,等价于mask != 0,这是for的第二个表达式
  8. 分析循环体, %r8每次存储了%rdi的值,即%r8=x,然后%r8与%dx做与,即%r8=x & mask,然后再有result |=%r8,得result |= x &mask
  9. %ecx中存储的是n(32位),%cl是n的低16位,说明n是一个不大的数。每次循环体执行完后,执行mask <<= n

故对应关系为

x = %rdi        n=%esi         reuslt=%rax        mask=%rdx

完整代码为

long loop(long x, int n){long result=0;long mask;for (mask = 1; mask   !=0    ;mask <<=n   ){result |= x & mask ;}return result;
}

题目答案:
A : x = %rdi n=%esi reuslt=%rax mask=%rdx
B: result初始值为0,mask初始值为1
C: mask的测试条件是mask != 0
D: mask每次左移n位
E: result = |= x &mask
F: 见上

3.63 由汇编代码还原swtich多分支

这个程序给你一个机会,从反汇编机器代码逆向工程一个switch 语句。在下面这个过程中,去掉了switch 语句的主体:

图3-53 给出了这个过程的反汇编机器代码。
跳转表驻留在内存的不同区域中。可以从第5 行的间接跳转看出来,跳转表的起始地址为〇x
4006f8。 用调试器GDB, 我们可以用命令x/6gx 0x4006f8 来检査组成跳转表的6 个8 字节字的内存。GDB 打印出下面的内容:

用c 代码填写开关语句的主体,使它的行为与机器代码一致。
汇编代码我会在下面逐句解释,就不贴了

(gdb) x/6gx 0x4006f8
0x4006f8: 0x00000000004005a1 0x00000000004005c3
0x400708: 0x00000000004005a1 0x00000000004005aa
0x400718: 0x00000000004005b2 0x00000000004005bf
//不要被迷惑了,其实是从0x4006f8起连续的六个存储位,每个位置存储了一个8byte地址
//概括起来就是
n=0 -> 跳到第6行    n=1 -> 跳到第17行 n=2 -> 跳到第6行 n=3 -> 跳到第9行
n=4 -> 跳到第12行   n=5 -> 跳到第16行

注:

  1. lea 和 mov 的区别是,如果给定的是间接寻址模式,lea会把计算的结果赋给dst,而mov会寻找计算结果对应的内存,再把内存的内容赋值给dst
  2. 但是实际上寄存器存地址和存数据没区别,所以用lea的实际作用是,利用间接寻址表达式,直接计算数学表达式的值,再直接赋给寄存器,省去了要用mul计算再用mov拷贝的步骤
  3. 注意switch内部的语句刨去case,是连续存储的,case最终变成了跳转表,使得最终呈现出的效果是,switch根据n的值直接找到从哪里开始运行。若case不加break,会从跳转的位置一直执行到break或者switch结束,会运行其他case里的指令。
  4. switch会合并相同的表达式,并按照一定的顺序排列内部的语句,使得从跳转到某个地址时,刚好执行n所决定的所有命令,但是同一条命令可能会被对个n值跳转后执行。你可以把这些重复执行的命令按上一条规则,不加break地排列在switch底部,看起来比较违和;你也可以把这些命令在每个需要它的n值case后面重抄一遍,然后break
long switch_prob(long x, long n)
x in %rdi, n in %rsi
1 0000000000400590 <switch_prob>:
2 400590: 48 83 ee 3c sub $0x3c,%rsi // n=n-60
//减去常数是为了实现坐标空间的平移,从[0x3c,0x3c+n]平移到[0,n],方便地址索引,注意在c代码中要还原
3 400594: 48 83 fe 05 cmp $0x5,%rsi // 保存比较值: n-0x5
4 400598: 77 29 ja 4005c3 <switch_prob+0x33> //若 n-0x5 > 0 ,则跳转到第17行(这个内存地址是由函数名地址<switch_prob>加上0x33(区分33和0x33)得到的)。
//即n>5时,不进入跳转表,直接跳转到第17行
//即n的有效值是0,1,2,3,4,5共六个,所以跳转表才会只有六个地址,而且书上gdb打印时打印6个
5 40059a: ff 24 f5 f8 06 40 00 jmpq *0x4006f8(,%rsi,8) //跳转到 8*n+0x4006f8 处存储的地址的地址,即跳转表首地址+n个8byte,即跳转表[n]处的地址
6 4005a1: 48 8d 04 fd 00 00 00 lea 0x0(,%rdi,8),%rax  //将8*x赋给%rax
7 4005a8: 00 //对齐,没用
8 4005a9: c3 retq //返回%rax
9 4005aa: 48 89 f8 mov %rdi,%rax //将x的值赋给%rax
10 4005ad: 48 c1 f8 03 sar $0x3,%rax //将%rax算数右移3位
11 4005b1: c3 retq //返回%rax
12 4005b2: 48 89 f8 mov %rdi,%rax //将x的值赋给%rax
13 4005b5: 48 c1 e0 04 shl $0x4,%rax //将%rax的值逻辑左移4位(左移是算数还是逻辑没区别)
14 4005b9: 48 29 f8 sub %rdi,%rax //%rax-=x
15 4005bc: 48 89 c7 mov %rax,%rdi //x=%rax
16 4005bf: 48 0f af ff imul %rdi,%rdi //有符号乘法 x*=x
17 4005c3: 48 8d 47 4b lea 0x4b(%rdi),%rax //将x+0x4b赋给%rax
18 4005c7: c3 retq //返回%rax

所以C程序代码是

long switch_prob(long x, long n){long result=x;//注意n取值的平移还原switch(n){case 60:case 62://这两个跳转的地址是一样的result = 8 * x;break;case 63:result = x >> 3;//由result = x;result >>= 3;进行语义合并break;case 64://汇编直译代码是result = x;result <<= 4;result -= x;x = result;x *= x;result = x + 0x4b;result = 225*x;//result=(x<<4 - x)*(x<<4 - x);的等价代码,汇编借助result存储位做了运算result = x + 0x4b;break;case 65:x *= x;result = x + 0x4b;break;case 61:result = x + 0x4b;break;default:result = x + 0x4b;/* 后面四个也可以把result = x + 0x4b提出来,写成这样case 64:x=15*x;case 65:x*=x;case 61:default:result = x + 0x4b;事实上汇编直译出来就是这样,只是我们把每个case独立break,不要相互串来串去。串来串去并不会提高程序的性能,因为独立break后编译器也会优化成串来串去。直接写C代码时串来串去是不好的习惯,影响人类的可读性。*/}return result;
}

总结:翻译switch汇编到C语言的一般步骤是:

  1. 分析switch变量n的预处理(这里是平移),然后找出每个n值所对应的跳转表行数,将跳转表内容翻译为汇编指令的起始行数,最终找出每个n对应的的汇编指令入口。入口一样的n可以写在一起。
  2. 将switch内部的语句体逐句翻译成简单的C代码,retq翻译成break。
  3. 按照没有break就一直执行的规律,找出每一个n将会运行的C代码。
  4. 进行语义的合并,简化代码,独立case,独立break。
  5. 还原被处理的n,并且把n不符合任何一种case的情况(default)写出来。
    一般会在跳转表的前一句进行比较和条件跳转。避免其进入跳转表。

3.66 阅读矩阵求列和程序,找出矩阵行数和列数的宏函数

考虑下面的源代码,这里NR 和NC 是用#define 声明的宏表达式,计算用参数表示的矩阵A 的
维度。这段代码计算矩阵的第i 列的元素之和。
阅读程序知道,sum_col()完成的是计算矩阵A在第j列的值的总和并返回。

编译这个程序,GCC 产生下面的汇编代码:
汇编代码我会在下面逐句解释,就不贴了
运用你的逆向工程技术,确定NR 和NC 的定义。

注:

  1. 注意过程名.[procedure]:是用于理解的,机器代码中并没有,所以一堆过程放在一起时,是从上到下依次执行的。而不是主函数和子函数的关系。
  2. 矩阵A的行数和列数都是由n生成的,只不过n存的什么是个迷,可以是用特殊规则生成的列数和行数。
  3. 注意内存是按字节编码的,不是按比特编码的,每个long要跳过8个字节,只需要在上一个地址+8就行了。
  4. 寄存器是会复用的,不要拿某个寄存器和某个变量绑定。可以用变量表示传入的初值。如果寄存器里的内容不是初值了,就不要再用变量表示了。这里函数从传入的j和for循环的计数变量i就共用了寄存器%rdx。
long sum_col(long n, long A[NR(n)][NC(n)], long j)
n in %rdi, A in %rsi, j in %rdx
1 sum_col:
2 leaq 1(,%rdi,4), %r8 //%r8=4n+1
3 leaq (%rdi,%rdi,2), %rax //%rax=n+2n=3n
4 movq %rax, %rdi //%rdi=%rax=3n
5 testq %rax, %rax //flag=%rax
6 jle .L4 //<=0则图跳转到.L4,即%rax不为0就去.L4将%rax置为0,因为%rax=3n可能为0也可能不为0.
7 salq $3, %r8 //%r8算数左移3位,即%r8<<=3,结合2,%r8=8*(4n+1),结合14,总列数NC(n)=4n+1
//行数应该体现在for循环中,去for循环中找NR
8 leaq (%rsi,%rdx,8), %rcx //%rcx=A+j*8,将%rcx置为矩阵第1行的第j列的地址
9 movl $0, %eax //%rax=0
10 movl $0, %edx //%rdx=0
11 .L3:
12 addq (%rcx), %rax //%rax+=M[%rcx],这里绝对是result+=A[i][j]
13 addq $1, %rdx //%rdx+=1,即i++,for的第三个参数,说明这里的i用的是j的寄存器%rdx
14 addq %r8, %rcx //%rcx+=%r8,由于%rcx是地址,所以这里是切换到矩阵下一行的第j列
//%r8应该存的是 总列数*单个数据的字节数,long一个占8个字节,所以是 %r8=8*总列数
15 cmpq %rdi, %rdx //flag=%rdx-%rdi,这里是 i < NR(n)的判断。所以得到NR(n)=%rdi=3n
16 jne .L3 //flag!=0,即i!=n时,跳转回.L3,循环执行
17 rep; ret //返回%rax的值
18 .L4:
19 movl $0, %eax //%eax=0,将%rax置为0
20 ret

由第7行和第15行的分析,得NC(n)=4n+1,NR(n)=3n

BUPT-CSAPP 2019 Fall 3.58 3.60 3.63相关推荐

  1. 亿方云:2019年云综合收入0.60亿元,360的好帮手

    云排名分析:亿方云,2019年云综合收入0.60亿元. 2020年4月,360公司公告全资收购企业文件管理与协作SaaS 服务商亿方云,即杭州亿方云网络科技有限公司.只是收购完成后,亿方云将保持独立发 ...

  2. 北大计算机图灵班,北大2019“图灵班”计划招60人,在北大什么条件才能进图灵班?...

    原标题:北大2019"图灵班"计划招60人,在北大什么条件才能进图灵班? 北京大学图灵班今年又开始内部招生啦,那么达到什么水平才能报名参加图灵班的测试呢.请看今年的要求吧: 一.报 ...

  3. #55 #56 #58 #59 #60 #66

    为什么80%的码农都做不了架构师?>>>    ##55 (= (__ [1 1 2 3 2 1 1]) {1 4, 2 2, 3 1}) (= (__ [:b :a :b :a : ...

  4. 孙钰java_好好学习| 在屯里都上了什么课(2019 Fall)

    是接着作死的一个学期lol Econ 475, Comp 240, Comp 354, Comp 525, Comp 368, Genetics 699, Stat 309 课表如上图,再加上Gene ...

  5. 2019 fall CS224w:01-intro

    1. 如何建模 将现实问题建模成图神经网络的结构是非常重要的,要指定代表node,什么来代表edge. 有些问题,如社交网络,蛋白质结构这种有很独特和明显的grapy结构. 还有些如NLP中的文本,C ...

  6. BUPT CSAPP lab3 缓冲区溢出

    一.实验目的 1.理解C语言程序的函数调用机制,栈帧的结构. 2.理解x86-64的栈和参数传递机制 3.初步掌握如何编写更加安全的程序,了解编译器和操作系统提供的防攻击手段. 3.进一步理解x86- ...

  7. 官方完整HL7 ECG-XML例子及注释翻译(1)

    编者:李国帅 qq:9611153 微信lgs9611153 时间:2019-7-4 背景: 存储心电图数据的格式有很多种,比如HL7,Dicom,EDF,GDF等,其中HL7最是简单,直观,使用Xm ...

  8. 德国风力发电机发电数据集(13w多条数据)

    30多维特征表 Timestamp Gearbox_T1_High_Speed_Shaft_Temperature Gearbox_T3_High_Speed_Shaft_Temperature Ge ...

  9. a newbie in Porto Seguro’s Safe Driver Prediction(solo参赛 TOP 5%)

    关于kaggle  kaggle是一个国际性质的机器学习竞赛平台,跟国内很多出名的别的平台比,kaggle上参与者之间的交流多很多,可以很大程度上提高参赛人员水平.而且kaggle本身难度也比国内的很 ...

最新文章

  1. 【BZOJ4720】【NOIP2016】换教室
  2. UIRemoteNotificationType 参考
  3. DotnetSpider (二) Downloader的设置 Request自定义数据字典
  4. WinForm登录模块设计开发
  5. Windows 2012 - Dynamic Access Control 浅析
  6. 快速提示:使用Chrome开发工具调试GWT应用程序
  7. Log图文详解(Log.v,Log.d,Log.i,Log.w,Log.e)!
  8. brew安装mysql 卸载_Mac卸载mysql并安装mysql升级到8.0.13版本
  9. 《财富》2020中国40岁以下商界精英榜出炉:张一鸣位列榜首
  10. python入门之后须掌握的知识点(excel文件处理+邮件发送+实战:批量化发工资条)【二】
  11. linux php虚拟主机,linux上php虚拟主机(linux搭建虚拟主机)
  12. 知识点 - 后缀数组
  13. 2011-1 开篇,要确定好方向
  14. nginx 请求头转发
  15. 抛砖引玉,扒扒伪基站那些事(转)
  16. Pandas基础:文件读取与写入、Series和Dataframe、常用基本函数、排序
  17. Kali2021双网卡设置
  18. Linux飞鸽传书源码,Linux下的飞鸽传书
  19. 【免费报名】亚洲诚信看雪学院:“走进企业看安全”技术分享沙龙等你来侃~...
  20. Druid数据库连接池就这么简单

热门文章

  1. 〈三〉ElasticSearch的认识:搜索、过滤、排序
  2. 扎进“手机”红海,蔚来改造“生态圈”
  3. win10能上网显示未连接到服务器,Win10网络正常但浏览网页无法连接到互联网怎么办?...
  4. 硬件测试工程师的待遇和前景
  5. echarts 之饼形图配置属性
  6. (译)Xposed模块开发教程
  7. spark常见转换算子(transformation)的操作
  8. 奥维 最新 图源2023
  9. 数据分析常用方法和工具
  10. 【翻译】WhatsApp 加密概述(技术白皮书)