信息安全系统设计基础第六周学习总结
【学习时间:10小时】
【学习内容:第四章“处理器结构”,主要是在第二、三章基础上对机器执行指令时动作的探究,包括硬件动作】
一、课本内容
1.流水线化的处理器:
将每条指令的执行分解成五步,每个步骤由一个独立的硬件部分或者阶段来处理。指令步经流水线的各个阶段,且每个时钟周期有一条新指令进入流水线。所以处理器可以同时执行五条指令的不同阶段。
2.程序员可见的状态:
Y86程序中的每条指令都会读取或者修改处理器状态中的某些部分。这称为程序员可见状态。
3.Stat
程序状态的最后一个部分是状态码Stat,它表明程序执行的总体状态;它会指示是正常运行还是出现了某种异常。
4.Y86
一个简单的、可以称之为IA32指令集的子集的指令集;只包括四字节整数操作,寻址方式比较少。指令编码长度从1——6字节不等。
关于指令结构,每条指令的第一个字节表明指令的类型;这个字节分为两个部分,每部分四位:高四位是代码部分(0——0xB),第四位是功能部分。这里补充一些缩写:立即数(i),寄存器(r)、存储器(m)。指令附加的寄存器指示符字节依次是数据源(如果是立即数,把这一位设置成0xf)、目的寄存器/基址寄存器。有些指令需要附加四字节的常数字,采用小端法(倒序)编码
5.stat代码可能取值反应了机器的不同状态——
- - AOK:正常操作(除此之外的任何状态都会使得处理器停止执行指令)
- - HLT:处理器执行halt指令
- - ADR:遇到非法地址2015/10/14 17:16:25
- - INS:遇到非法指令
6.命令指明应该将代码或者是数据放在什么位置等。其中,以 .开头的是汇编器命令,它们告诉汇编器调整地址,以便在那儿产生代码或者是插入一些数据。
7.指令模拟器YIS是模拟Y86机器代码程序的执行而不用试图去模拟任何具体处理器实现的行为。
8.pushl会把栈指针减4,并将一个寄存器值写入存储器中。因此,执行pushl %esp 和 popl %esp的结果是不固定的。
9.逻辑门是数字电路的基本计算元素。逻辑门总是活动的,一旦一个门的输入变化了,在很短的时间内,输出就会相应地变化。
10.将很多的逻辑门组合成一个网,就能构建计算块,称为组合电路。限制:
- 两个或者多个逻辑门的输出不能连接在一起;
- 这个网必须无环
11.多路复用电路(MUX):根据输入控制信号的值,从一组不同的数据信号中选出一个。
12.关于HCL的几点说明
- HCL中所有字级的信号都声明为int;
- 在画字级电路的时候,用中等粗度的线表示携带字的每个位的线路,用虚线来表示布尔信号的结果
- 在HCL中,表示默认情况(即所有条件都没有被选中的情况)一般用1
13.算术/逻辑单元(ALU):
根据控制输入的设置(0,1,2,3),电路会对数据输入执行不同的算数或者逻辑操作(+,-,&,^)。
14.时序电路
有状态并且在这个状态上进行计算的系统
15.时钟寄存器&随机访问存储器
- 时钟寄存器存储单个位或者字。时钟信号控制寄存器加载输入值
- 随机访问存储器存储多个字,用地址来选择该读入或者该写哪个字
16.Y86的顺序实现离不开SEQ处理器(顺序处理器)。
将处理一条指令的操作组织成一个特殊的阶段序列;可以设计一个充分利用硬件的处理器。
17.处理操作的阶段——
- 取指:从寄存器读取指令字节,地址为程序计数器的值。计算下一条指令地址等于PC中的值加上已取出指令的长度
- 译码:从寄存器文件中最多读出两个操作数
- 执行:ALU执行指明的操作、引用的有效地址或者是修改栈指针
- 访存:将数据写入存储器或者从存储器读出数据
- 写回:写两个结果到寄存器文件
- 更新PC:将PC设置成下条指令地址
18.关于一些指令的处理步骤
- irmovl指令的处理与rrmovl类似,但是因为是长指令格式,所以程序计数器要加6
- 指令call和ret与之前的popl和pushl类似。对于指令call,我们要将valP也就是call之后的那条指令的地址压入栈中在更新PC阶段,将PC设置为valC,也就是调用目的地
19.SEQ的时序(逐步深化)
- 要控制处理器中活动的时序,只需要寄存器和存储器的时钟控制
- 除了指令存储器只用来读指令故而可以看作组合逻辑之外,剩余的程序计数器、条件码寄存器、数据存储器和寄存器文件需要通过一个时钟信号来控制(控制时序)
- 在每个时钟周期内,程序计数器都会装载新的指令地址;只有执行整数运算指令的时候,才会装载条件码寄存器。只有执行rmmovl,pushl,call时,才会写数据存储器。
- Y86指令集的本质遵循这样一项组织原则:处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态 【如何理解?也就是说,处理器所“经手”的指令中,如果有某些是可以改变机构状态的;那么一定先改变状态之后再执行指令。这样保证了操作的时序性(防止指令执行起来互相颠倒)】
20.SEQ阶段的实现
- 取指阶段包括指令存储器单元。一次从存储器中读出6个字节。第一个字节分为ocode,ifun。根据icode的值,计算:instrvalid(发现不合法指令),needregids,need_valC。instrvalid和imemerror在访存阶段被用来产生状态码。
- SEQ中译码和写回阶段都需要访问寄存器文件。寄存器文件有四个端口,它支持同时进行两个读和两个写;每个端口都一个地址连接和数据连接。如果某个地址端口上的值为特殊标识符0xf,则表明不需要访问寄存器。
- 执行阶段包括算术/逻辑单元(ALU)。这个单元革命家alufun信号的设置,对输入aluA和aluB执行ADD,SUB,AND,XOR运算。ALU的输出就是valE信号。还包括条件码寄存器。每次运行时,ALU都会产生三个与条件码相关的信号——零,符号,溢出。用set_cc来控制是否应该更新条件码寄存器。
- 访存阶段。两个控制块产生存储器地址和存储器输入数据的值;另外两个块产生控制信号表明应该执行读操作还是写操作。
二、练习题筛选
1.确定下面的Y86指令序列的字节编码。.pos 0x100表明这段代码的起始地址应该是0x100
irmovl $15,%ebx
rrmovl %ebx,%ecx
loop:
rmmovl %ecx,-3(%ebx)
addl %ebx,%ecx
jmp loop
解答:
irmovl $15,%ebx ##30f30f000000
rrmovl %ebx,%ecx ##2031
loop:
rmmovl %ecx,-3(%ebx) ##4013fdffff
addl %ebx,%ecx ##6031
jmp loop
2.确定下列每一个字节序列所编码的Y86指令序列。如果有不合法字节,指出其位置。
A.
0x100:30f3fcffffff ##irmovl $-4,%ebx
0x106:406300080000 ##rmmovl %esi,0x800(%ebx)
0x10c:00 ##halt
B.
0x200:a06f ##pushl %esi
0x202:8008020000 ##call proc
0x207:00 ##halt
0x208:
0x208:30f30a000000 ##irmovl $10,%ebx
0x20e:90 ##ret
【此题应该是在熟练掌握各项指令(尤其是特殊指令)的编码基础上进行尝试分解编码序列。例如:在以“a0”开头的编码处,就应该意识到这是一个之后2字节的pushl指令;然后下一个以“80”开头的即call指令(只有五字节),这样就可以得出剩余的00一定是halt。】
C.
0x300:505407000000 ##mrmovl 0x7(%esp),%ebp
0x306:10 ##nop
0x307:f0 ##非法
0x308:b01f ##popl %ecx
3.修改P238图4-6中的Y86代码,使其能计算一个数组的绝对值的和。内循环中使用条件传送。
源代码:
int Sum(int *Start,int Count)
{
int sum = 0;
while(Count)
{
sum+=*Start;
Start++;
Count--;
}
return sum;
}
对于内循环的Y86代码,如要计算绝对值之和,只需要在相对于“sum+=*Start”的部分做下改动,加上条件分支(负数取反,正数不变)。如果要用条件传送,则适合cmovg或者cmovge
loop:
mrmovl (%ecx),%esi
irmovl $0,%edi
subl %esi,%edi
cmovg %edi,%esi ##以上代码处理了x<0的情况,下面即和原代码相同
addl %esi,%eax
irmovl $4,%ebx
addl %ebx,%ecx
irmovl $-1,%ebx
addl %ebx,%edx
jne loop
4.写出信号xor的HCL表达式。输入为a和b
bool xor = (a && !b) || (!a && b)
与bool eq = (!a && !b) || (a && b)的区别为,二者互补
.5写一个电路的HCL代码,对于输入字A,B,C,选择中间值。
int Med3 = [
A<=B && A>=C :A;
A<=C && A>=B :A;
B<=A && B>=C :B;
B<=C && B>=A :B;
1:C;
];
6.根据图4-17中目标代码第9行call处理情况填写具体的处理阶段。
-
取指
icode:ifun<-M1[0x023]=8:0 valC<-M4[0x024] = 0x29 valP<-0x028
-
译码
valB<-R[%esp]=128
-
执行
valE<-128-4=124
-
写回
R[%esp]<-124
-
更新PC
PC<-0x029
效果是将%esp设置为124,将0x028(返回地址)存放到该存储器地址
7.写出SEQ中实现need_valC的HCL代码
bool need_valC = icode in {IIROMVL,IRMMOVL,IMRMOVL,IJXX,ICALL};
8.寄存器IDdestM表明写端口M的目的寄存器,从存储器中读出来的值valM将放在那里。写出destM的HCL代码。
int dstM = [
icode in {IMRMOVL,IPOPL} :rA;
1:RNONE;
]
9.我们希望只为向存储器写数据的指令设置控制信号mem_write。
bool mem_write = icode in {IRMMOVL,IPUSHL,ICALL};
三、实验
1.《深入理解计算机系统》之 实验四
- 要求:编译以下代码。并且: 删除gcc产生代码中以"."开头的编译器指令,针对每条指令画出相应栈帧的情况
-
int add(int x)
{
return x + 6;
}
int mid(int x)
{
return add(x);
}
int main(void)
{
return mid(9) + 1;
}
-
编译(见下图)
-
打开lwr.s文件之后,会发现如下的汇编代码:
-
.file "lwr.c"
.text
.globl add
.type add, @function
add:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
addl $6, %eax
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size add, .-add
.globl mid
.type mid, @function
mid:
.LFB1:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call add
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size mid, .-mid
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $4, %esp
movl $9, (%esp)
call mid
addl $1, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4"
.section .note.GNU-stack,"",@progbits
- 删除编译指令之后,得到如下代码:
add:
pushl %ebp ##保存原来的%ebp(相当于一个副本)
movl %esp, %ebp ##将%ebp设置为目前这个程序的帧指针(值为原%esp值减去4)
movl 8(%ebp), %eax ##把保存在(%ebp+8)处的x值赋给%eax
addl $6, %eax ##执行x+6,结果存入%eax
popl %ebp ##弹出保存%ebp的值,为下一步返回做准备
ret ##返回mid函数;add函数所占用的栈帧“消失”
mid:
pushl %ebp ##保存原来的%ebp(即返回mid的地址,相当于一个副本)
movl %esp, %ebp ##将%ebp设置为目前这个程序的帧指针(值为原%esp值减去4)
subl $4, %esp ##%esp向下挪动4个单位空间;即在栈上分配4字节
movl 8(%ebp), %eax ##把保存在(%ebp+8)处的x值赋给%eax
movl %eax, (%esp) ##把x值交给%esp所指向的位置;为调用add函数之后,被调用程序寻值做准备
call add ##调用mid函数,将返回地址压入栈中,然后跳到函数mid的第一条指令
leave ##为返回准备栈;即,将保存的%ebp的值交由%esp,然后弹出保存的%ebp
main:
pushl %ebp ##主程序,保存原来的%ebp值(即返回main的地址,相当于一个副本)
movl %esp, %ebp ##将%ebp设置为目前这个程序的帧指针(值为原%esp值减去4)
subl $4, %esp ##%esp向下挪动4个单位空间;即在栈上分配4字节
movl $9, (%esp) ##把(十进制)数字9赋值给%esp所指向的位置
call mid ##调用mid函数,将返回地址压入栈中,然后跳到函数mid的第一条指令
addl $1, %eax ##根据表达式return mid(9)+1,最后需加1再返回
leave ##为返回准备栈;即,将保存的%ebp的值交由%esp,然后弹出保存的%ebp
1.《深入理解计算机系统》之 实验五
- 要求:下载Y86模拟器,验证教材上不少于三组HCL代码;构建YIS环境(操作性的代码见下);YIS手册下载;YIS测试
cd ~/Code/shiyanlou_cs413
wget http://labfile.oss.aliyunce.com/courses/413/sim.tar
tar -xvf sim.tar
cd sim
sudo apt-get install tk
sudo ln -s /usr/lib/x86_64-linux-gnu/libtk8.6.so /usr/lib/libtk.so
sudo ln -s /usr/lib/x86_64-linux-gnu/libtk8.6.so /usr/lib/libtk.so
make
- 构建YIS环境的过程如下(实验楼环境下)
(图2)
(图3,失败)
(图4,图5,成功)
- 测试书中P239代码asuml.ys,得到的代码如下
| /* $begin code-yso */
| /* $begin code-ysa */
| # Execution begins at address 0
| # This version uses a leave instruction
0x000: | .pos 0
0x000: 30f400040000 | init: irmovl Stack, %esp # Set up Stack pointer
0x006: 30f500040000 | irmovl Stack, %ebp # Set up base pointer
0x00c: 7024000000 | jmp Main # Execute main program
|
| # Array of 4 elements
0x014: | .align 4
0x014: 0d000000 | array: .long 0xd
0x018: c0000000 | .long 0xc0
0x01c: 000b0000 | .long 0xb00
0x020: 00a00000 | .long 0xa000
|
0x024: 30f004000000 | Main: irmovl $4,%eax
0x02a: a00f | pushl %eax # Push 4
0x02c: 30f214000000 | irmovl array,%edx
0x032: a02f | pushl %edx # Push array
0x034: 803a000000 | call rSum # Sum(array, 4)
0x039: 00 | halt
|
| # int Sum(int *Start, int Count)
0x03a: a05f | rSum: pushl %ebp
0x03c: 2045 | rrmovl %esp,%ebp
0x03e: 30f014000000 | irmovl $20,%eax
0x044: 6104 | subl %eax,%esp
0x046: a03f | pushl %ebx
0x048: 503508000000 | mrmovl 8(%ebp),%ebx
0x04e: 50050c000000 | mrmovl 12(%ebp),%eax
0x054: 6200 | andl %eax,%eax
0x056: 718b000000 | jle L38
0x05b: 30f2f8ffffff | irmovl $-8,%edx
0x061: 6024 | addl %edx,%esp
0x063: 30f2ffffffff | irmovl $-1,%edx
0x069: 6020 | addl %edx,%eax
0x06b: a00f | pushl %eax
0x06d: 30f204000000 | irmovl $4,%edx
0x073: 2030 | rrmovl %ebx,%eax
0x075: 6020 | addl %edx,%eax
0x077: a00f | pushl %eax
0x079: 803a000000 | call rSum
0x07e: 502300000000 | mrmovl (%ebx),%edx
0x084: 6020 | addl %edx,%eax
0x086: 708d000000 | jmp L39
0x08b: 6300 | L38: xorl %eax,%eax
0x08d: 5035e8ffffff | L39: mrmovl -24(%ebp),%ebx
0x093: d0 | leave
0x094: 90 | ret
|
0x400: | .pos 0x400
0x400: | Stack: # The stack goes here
| /* $end code-ysa */
| /* $end code-yso */
四、疑问
1.P249
下面的图展示了一个典型的寄存器文件:……寄存器文件有两个读端口,还有一个写端口。
那么,“读”与“写”都是向这个寄存器中输入数据吗?
2.P253
图4-19显示了rmmovl和mrmovl在顺序实现中的计算。其中在访存阶段,会将寄存器值valA写到存储器,或者从存储器中读出valM。
为什么要有这个步骤呢?看似并无必要。
五、心得
【这次的学习过程是自开课以来最有成就感的一次;不仅仅是因为亲身验证了“书读百遍,其义自见”的古训;更是因为我很欣慰地发现自己之前并没有“欠债”良多,可以凭借之前的基础进行深化学习。我从这周的阅读开始,隐隐发现:书中有部分内容确实是比较晦涩的,并不是语言本身表述性问题,而是中英翻译之间所存在的必然性障碍;中英文信息量差异以及表达习惯的不同使得译文可能与中文仍存在一些差异。所以,反复阅读的效果格外显著。由不懂到懂,相当欢乐。】
附、参考资料
1.《深入理解计算机系统》Randal E. Bryant & David R. O'Hallaron
2.实验楼私有课程《深入理解计算机系统》之 程序的机器级表示&处理器体系结构
3.20135313吴子怡实验楼实验五YIS构建操作问题 - 信息安全系统设计基础@BESTI - 小组 - 博客园
【http://group.cnblogs.com/topic/73183.html】
4.深入理解计算机系统 Y86 模拟器 讨论-CPU与编译器-ChinaUnix.net
【http://bbs.chinaunix.net/thread-2057670-1-1.html】