MOOC地址:
1 浮点数的机器表示
1.1 填空题 浮点数
单精度浮点数的exp域的位宽是()位?
答案:8
1.2 填空题 浮点数
单精度浮点数的frac域位宽是()位?
答案:23
1.3 判断题
已知
int x = ...;
float f = ...;
double d = ...;
且d
与f
都不是NaN,判断以下关系式是否成立?
x == (int)(float) x
答案:×
解析:float的frac域宽度不够,可能会有精度损失。
1.4 判断题
已知
int x = ...;
判断以下关系式是否成立?
x == (int)(double) x
答案:√
解析:double的frac域足够放下int,不会有精度损失。
1.5 判断题
已知
float f = ...;
且f
不是NaN,判断以下关系式是否成立?
f == (float)(double) f
答案:√
解析:float转换成double不会有精度损失。
1.6 判断题
已知
double d = ...;
且d
不是NaN,判断以下关系式是否成立?
d == (float) d
答案:×
解析:double转换成float可能会有精度损失。
1.7 判断题
已知
float f = ...;
且f
不是NaN,判断以下关系式是否成立?
f == -(-f)
答案:√
解析:浮点数取负只改变符号位,不会溢出。
1.8 判断题
判断以下关系式是否成立?
2/3 == 2/3.0
答案:×
解析:2/3的结果是整数,但2/3.0的结果是浮点数。
1.9 判断题
已知
double d = ...;
且d
不是NaN,判断以下关系式是否成立?
(d < 0.0) == ((d*2) < 0.0)
答案:√
解析:浮点数的下溢是逐步下溢,不像整数会从负数变成正数。
1.10 判断题
已知
float f = ...;
double d = ...;
且d
与f
都不是NaN,判断以下关系式是否成立?
(d > f) == (-f > -d)
答案:√
解析:浮点数取负不会溢出。
1.11 判断题
已知
float f = ...;
double d = ...;
且d
与f
都不是NaN,判断以下关系式是否成立?
(d + f) - d == f
答案:×
解析:d+f可能会溢出,损失精度。
2 80x80汇编与C语言-2
2.1 填空题
已知寄存器edx
内的数值为0x8000
,ecx
内的则为0x10
;请给出地址表达式0x4(%edx, %ecx, 4)
所表示的地址值。
答案:0x8044
解析:D(Rb, Ri, S) = Rb + S*Ri + D
2.2 填空题
x86-64体系结构具有()个通用寄存器,而x86-32只有()个。
答案:
- 16
- 8
2.3 判断题
leal (%edx, %eax), %ecx
这条指令在执行过程中需要访问内存数据。
答案:×
解析:leal
指令只计算地址,不访存。因此leal
指令也可以用于整数运算。
2.4 单选题
请问哪个条件码可用于检测无符号整数运算的溢出?
- CF
- SF
- ZF
- OF
答案:CF
解析:
- CF(Carry Flag),无符号整数运算溢出时置位
- SF(Sign Flag),计算结果<0时置位
- ZF(Zero Flag),计算结果=0时置位
- OF(Overflow Flag),带符号整数运算溢出时置位。
2.5 单选题
seta
、setb
指令适用于无符号还是带符号整数的条件码读取?
- 无符号
- 带符号
答案:无符号
解析:seta
和setb
适用于无符号整数;其中a、b分别指above和below。带符号整数对应的是setg
和setl
(g和l分别指greater和less)。
2.6 填空题
请补充与下图中C语言对应的汇编代码中的遗漏部分(x86-32结构下编译生成)。
/* C */
int absdiff(int x, int y)
{
int result;
if (x > y) {
result = x - y;
} else {
result = y - x;
}
return result;
}
# asm
pushl %ebp
movl %esp, %ebp
pushl %ebx
movl 8(%ebp), %ecx
movl 12(%ebp), %edx
movl %ecx, %ebx
subl %edx, %ebx
movl %edx, %eax
subl %ecx, %eax
cmpl %edx, %ecx
____ %ebx, %eax
popl %ebx
popl %ebp
ret
答案:cmovg
解析:将汇编代码“翻译”如下:
ecx := x
edx := y
ebx := x
ebx := x - y
eax := y
eax := y - x
Compare x to y
if (?) (eax := x - y)
return eax
由分析可知,此处应该填写条件移动指令cmovg
(若x>y则传送)。
3 80x80汇编与C语言-3
3.1 填空题
下图给出了一个C函数,并由gcc编译成相应的汇编代码(AT&T语法格式),请补全这段代码里头被省去的部分。(X86-32结构)
/* C */
int arith(int x, int y, int z)
{
int t1 = x * y;
int t2 = z - t1;
int t3 = x + 40;
int t4 = y * 2;
int t5 = t3 >> t4;
int rval = t2 * t5;
return rval;
}
# asm
pushl %ebp
movl ____, %ebp
movl ____(%ebp), %edx
movl ____(%ebp), %ecx
pushl %ebx
movl 16(%ebp), %eax
movl %edx, %ebx
addl $40, %edx
imull ____, %ebx
addl %ecx, ____
sarl %cl, %edx
subl %ebx, %eax
imull ____, %eax
popl ____
popl %ebp
ret
答案:
%esp
8
12
%ecx
%ecx
%edx
%ebx
解析:
以下是填空完成后的完整汇编代码。推理过程略。
asm | explanation |
---|---|
pushl %ebp |
setup, save old ebp |
movl **%esp**, %ebp |
setup |
movl **8**(%ebp), %edx |
edx = x |
movl **12**(%ebp), %ecx |
ecx = y |
pushl %ebx |
save old ebx |
movl 16(%ebp), %eax |
eax = z |
movl %edx, %ebx |
ebx = edx = x |
addl $40, %edx |
edx += 40 (edx = x + 40 = t3) |
imull **%ecx**, %ebx |
ebx *= y (ebx = x * y = t1) |
addl %ecx, **%ecx** |
ecx += y (ecx = 2*y = t4) |
sarl %cl, %edx |
edx >>= cl (cl = t4, edx = t5) |
subl %ebx, %eax |
eax -= ebx (eax = z - t1 = t2) |
imull **%edx**, %eax |
eax *= edx (eax = t2 * t5 = rval) |
popl **%ebx** |
reset ebx |
popl %ebp |
reset ebp |
ret |
return eax (rval) |
3.2 填空题
在X86-32位体系结构中,当前运行函数的帧(Frame)基址寄存器所指向的栈地址的“上方”(高地址)由低到高存放的是函数返回地址、( );“下方”存放的是( )、( )(此处无需考虑顺序)。
答案:
- 输入参数
- 局部变量
- 临时存储(存在其他正确填法,如“保存的寄存器值”,此处只列出标准答案)
4 80x80汇编与C语言-4
4.1 填空题
请按顺序填写图左侧汇编代码对应的C代码(e.g. 右3)
# asm
foo1:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
sall %$4, %eax
subl 8(%ebp), %eax
movl %ebp, %esp
popl %ebp
ret
foo2:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
testl %eax, %eax
jge .L4
addl $15, %eax
.L4:
shrl $4, %eax
movl %ebp, %esp
popl %ebp
ret
foo3:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
shrl $31, %eax
movl %ebp, %esp
popl %ebp
ret
/* C */
int choice1(int x)
{
return (x < 0);
}
int choice2(int x)
{
return (x << 31) & 1;
}
int choice3(int x)
{
return 15 * x;
}
int choice4(int x)
{
return (x + 15) / 4;
}
int choice5(int x)
{
return x / 16;
}
int choice6(int x)
{
return (x >> 31);
}
答案:
- 右3
- 右5
- 右1
解析:值得注意的是右5和对应的汇编代码。由于算术右移指令对应的除法的取整模式是向下取整,而C语言要求的除法的取整模式是向0取整,因此负数除法的取整会出问题,不能直接右移。正确的计算公式是:
-
a ≥ 0
时,a / 2^b = a >> b
-
a < 0
时,a / 2^b = (a + 2^b − 1) >> b
(此处除16相当于b=4
,因此2^b - 1 = 15
)
这就解释了汇编代码中testl %eax, %eax
和jge .L4
的意义。testl
的功能是,取后面两个操作数的与,根据运算结果置条件码。当%eax >= 0
时,运算结果仍为%eax
,SF为0,OF也为0;当%eax < 0
时,SF为1,OF为0。当条件码满足SF=OF
时,jge .L4
会跳转到.L4
直接右移(不是负数,不用加上15);否则先加上15再右移。
4.2 填空题
已知三个二维矩阵的定义如下,并已初始化。
/* C */
#define n 10
int a[n][n] ;
int b[n][n] ;
int c[n][n] ;
需要进行矩阵乘,即矩阵a x b结果置于c。下面这段C代码是一个矩阵乘函数。
/* C */
void column()
{
int j, k, i;
int r;
for (j=0; j<n; j++)
{
for (k=0; k<n; k++) {
r = b[k][j];
for (i=0; i<n; i++)
c[i][j] += a[i][k] * r;
}
}
}
下面是相应的汇编代码。请填空。
L11:
movl -16(%ebp), %edx
leal (%ecx, %edx), %eax
leal (%ecx, %edi), %edx
movl a_start_addr(, ____, 4), %eax
addl $10, %ecx
imull ____, %edx
addl %edx, c_start_addr(, %eax, 4)
decl %ebx
jns L11
addl $40, -20(%ebp)
incl %edi
cmpl ____, %edi
jle L12
incl -16(%ebp)
cmpl ____, -16(%ebp)
jle L13
addl ____, %esp
popl %ebx
popl %esi
popl %edi
popl %ebp
ret
_matrix:
pushl %ebp
movl ____, %ebp
pushl %edi
pushl %esi
pushl %ebx
subl $8, %esp
movl $0, -16(____)
L13:
movl -16(%ebp), %eax
xorl %edi, ____
leal b_start_addr(, ____, 4), %eax
movl ____, -20(%ebp)
L12:
movl ____(%ebp), %edx
xorl %ecx, %ecx
movl $9, %ebx
movl (%edx), %esi
答案:
%esp
%ebp
%edi
%eax
%eax
-20
%edx
%esi
$9
$9
$8
解析:
以下是填空完成后的完整汇编代码。推理过程略。
asm | explanation |
---|---|
_matrix: |
|
pushl %ebp |
setup, save old ebp |
movl %esp, %ebp |
setup |
pushl %edi |
save edi |
pushl %esi |
save esi |
pushl %ebx |
save ebx |
subl $8, %esp |
esp -= 8 |
movl $0, -16(%ebp) |
j = 0 (-16(%ebp) = j) |
L13: |
the start of j loop |
movl -16(%ebp), %eax |
eax = j |
xorl %edi, %edi |
edi = 0 (edi = k) |
leal b_start_addr(, %eax, 4), %eax |
eax = &b[4*j] |
movl %eax, -20(%ebp) |
-20(%ebp) = &b[4*j + (40*k)] |
L12: |
the start of k loop |
movl -20(%ebp), %edx |
edx = &b[4*j + (40*k)] |
xorl %ecx, %ecx |
ecx = 0 (ecx = i * 10) |
movl $9, %ebx |
ebx = 9 // as "i" |
movl (%edx), %esi |
esi = b[k][j], or r = b[k][j] (esi = r) |
L11: |
the start of i loop |
movl -16(%ebp), %edx |
edx = j |
leal (%ecx, %edx), %eax |
eax = ecx + edx = 10 * i + j |
leal (%ecx, %edi), %edx |
edx = ecx + edi = 10 * i + k |
movl a_start_addr(,%edx, 4), %edx |
edx = a[4*edx] (edx = a[i][k]) |
addl $10, %ecx |
ecx += 10 (ecx = i * 10) |
imull %esi, %edx |
edx *= r (edx = a[i][k] * r) |
addl %edx, c_start_addr(, %eax, 4) |
c[4*eax] += edx (c[i][j] += a[i][k] * r) |
decl %ebx |
ebx-- // as "i" |
jns L11 |
if (ebx > 0) goto L11 (the end of i loop) |
addl $40, -20(%ebp) |
-20(%ebp) += 40 (-20(%ebp) = 40\\*k+4*j) |
incl %edi |
edi++ (k++) |
cmpl $9, %edi |
compare k : 9 |
jle L12 |
if (k <= 9) goto L12 (the end of k loop) |
incl -16(%ebp) |
-16(%ebp)++ (-16(%ebp)=j) (j++) |
cmpl $9, -16(%ebp) |
compare j : 9 |
jle L13 |
if (j <= 9) goto L13 (the end of j loop) |
addl $8, %esp |
esp += 8 |
popl %ebx |
finish, reset ebx |
popl %esi |
finish, reset esi |
popl %edi |
finish, reset edi |
popl %ebp |
finish, reset ebp |
ret |
return |
5 80x80汇编编程-1
5.1 填空题
在X86-32位编程中有一种简单的获得所运行的指令地址的方法(X86-32位结构下eip寄存器是无法直接访问的)。比如说我们要获得下面程序中XXXX这条指令的地址并置于eax寄存器中,那么可以采用如下代码段。请补充完函数GetAddress的第一条语句(AT&T语法)。
Getaddress:
movl ____, ____
ret
call GetAddress
xxxx
答案:
(%esp)
%eax
解析:在call
之后,GetAddress
的返回地址,也就是xxxx
的地址被压栈。此时,只需将栈顶所指向位置的内容((%esp)
,注意括号代表访存)存入eax
中再返回。
平台上第二空给的答案是$eax
,是错的。我18年上这个课的时候就错了,当时向老师反映来着。现在还没改。
5.2 填空题
已知一个C语言结构类型的定义如下图所示,请问在X86 32位Linux系统下变量p
所占的空间大小是____字节,对齐的要求为____字节对齐?
struct S1 {
char c;
int i[2];
my_struct v;
} p;
typedef TagStruct {
int k[2];
char c2;
} my_struct;
答案:
- 24
- 4
解析:
TagStruct
的内存分布:
| k[0] (4 bytes) | k[1] (4 bytes) | c2 (1 byte) | padding (3 bytes) |
| -------------- | -------------- | ----------- | ----------------- |
大小为12字节,4对齐。
S1
的内存分布:
| c (1 byte) | padding(3 bytes) | i[0] (4 bytes) | i[1] (4 bytes) | v (12 bytes) |
| ---------- | ---------------- | -------------- | -------------- | ------------ |
大小为24字节,4对齐。
6 80x80汇编编程-1
6.1 填空题
有如下的C代码以及对应的反汇编出来的汇编代码(x86-32体系结构):
/* copy string x to buf */
void foo(char* x) {
int buf[1];
strcpy((char *)buf, x);
}
void callfoo() {
foo("abcdefghi");
}
080484f4 <foo>:
080484f4: 55 pushl %ebp
080484f5: 89 e5 movl %esp, %ebp
080484f7: 83 ec 18 subl $0x18, $esp
080484fa: 8b 45 08 movl 0x8(%ebp), %eax
080484fd: 83 c4 f8 addl $0xfffffff8, %esp
08048500: 50 pushl %eax
08048501: 8d 45 fc leal 0xfffffffc(%ebp), %eax
08048504: 50 pushl %eax
08048505: e8 ba fe ff ff call 80483c4 <strcpy>
0804850a: 89 ec movl %ebp, %esp
0804850c: 5d popl %ebp
0804850d: c3 ret
08048510 <callfoo>:
08048510: 55 pushl %ebp
08048511: 89 e5 movl %esp, %ebp
08048513: 83 ec 08 subl $0x8, %esp
08048516: 83 c4 f4 addl $0xfffffff4, %esp
08048519: 68 9c 85 04 08 pushl $0x804859c # push string address
0804851e: e8 d1 ff ff ff call 80484f4 <foo>
08048523: 89 ec movl %ebp, %esp
08048525: 5d popl %ebp
08048526: c3 ret
当strcpy
调用完成返回到foo
过程时,buf[0]
、buf[1]
、buf[2]
的值分别是多少?
在执行0x0804850d
的ret
指令前(popl
后),ebp
的值是多少?上述ret
指令执行后,eip
的值是多少?用32位16进制表示,注意大小端。
0x00000000 字符的十六进制转换表如下:
Character | Hex Value |
---|---|
'a' |
0x61 |
'b' |
0x62 |
'c' |
0x63 |
'd' |
0x64 |
'e' |
0x65 |
'f' |
0x66 |
'g' |
0x67 |
'h' |
0x68 |
'i' |
0x69 |
'\0' |
0x00 |
答案:
0x64636261
0x68676665
0x08040069
0x68676665
0x08040069
解析:
通过分析可以画出调用strcpy
之前完整的栈(具体分析过程略)如下表(每个格子代表4个字节,address向下递减):
内容 | 指向该位置的指针 |
---|---|
callfoo过程保存的%ebp | |
empty | |
empty | |
empty | |
empty | |
empty | |
string address (0x0804859c) | |
foo过程的返回地址 (0x08048523) | |
foo过程保存的%ebp | %ebp |
buf | |
empty | |
empty | |
empty | |
empty | |
empty | |
empty | |
empty | |
string address (strcpy的第二个参数) | |
%ebp - 4 (buf) |
可以看出,传递给strcpy
的buf
指针就是%ebp-4
,在复制了字符串"abcdefghi"
之后,会发生溢出,破坏栈中保存的%ebp
和返回地址。但现在的问题是,在考虑大小端之后,栈中的实际内容到底应该是什么样子的呢?
由于X86的字节序为小端(“低对低,高对高”),可以画出从buf
指针开始向上到string address位置中实际的保存内容(一个格子代表一个字节,地址从上往下递减):
描述 | 内容 |
---|---|
string address (高) | 08 |
04 | |
85 | |
string address (低) | 9c |
foo的返回地址 (高) | 08 |
04 | |
85 | |
foo的返回地址 (低) | 23 |
保存的%ebp (高) | ?? |
?? | |
?? | |
保存的%ebp (低) | ?? |
buf[0] (高) | ?? |
?? | |
?? | |
buf[0] (低) | ?? |
在向以buf
开头的地址中写入字符串"abcdefghi\\0"
(转换成16进制,就是0x61626364656667686900
)时,由于char
类型的大小只有一个字节,大小端对它来说是无所谓的,只要从低地址向高地址覆写就可以。于是,我们得到了修改过的栈帧:
描述 | 内容 |
---|---|
string address (高) | 08 |
04 | |
85 | |
string address (低) | 9c |
foo的返回地址 (buf[2]) (高) | 08 |
04 | |
|
|
foo的返回地址 (buf[2]) (低) |
|
保存的%ebp (buf[1]) (高) |
|
|
|
|
|
保存的%ebp (buf[1]) (低) |
|
buf[0] (高) |
|
|
|
|
|
buf[0] (低) |
|
而寄存器会以小端模式来解释内存中的内容,因此可得,buf[0] = 0x64636261
,buf[1] = 0x68676665
,buf[2] = 0x08040069
;popl
后得到的%ebp
为0x68676665
,执行ret
后%eip
的值(也就是要返回到什么地址)为0x08040069
。
7 MIPS32指令集与编程
7.1 填空题
异常(exception)可以分类为 ____ 和____两类,其中系统调用属于____异常、时钟中断属于____异常、Page Fault是 ____异常、机器cold reset是 ____异常。
答案:
- 同步
- 异步
- 同步
- 异步
- 同步
- 异步
解析:同步异常一般是指令引起的,异步异常一般是硬件引起的。
7.2 填空题
位于某个跳转指令的Branch Delay Slot中的指令(这一slot中的指令地址为A)发生了异常,那么异常处理完成后,恢复执行的指令地址是 ____;如果该跳转指令是JAL,那么该跳转指令执行完成后31号寄存器的内容是 ____。
答案:
A-4
A+4
解析:精确异常处理要求,延迟槽中的指令如果发生异常,恢复执行的指令是跳转指令;函数返回到下一条指令。
8 期末考试
我当年学的时候还有一个期末考试,现在已经没有了,不过姑且还是把当时的题整理在这里。
8.1 填空题
X、Y的数据宽度均为16位,计算结果也用16进制表示)已知[X]补=0019H
,[Y]补=FE6AH
,则[X+Y]补=____,[X-Y]补=____。
答案:
FE83H
00AFH
解析:显然,X是正数,Y是负数。
[X+Y]补 = X补+Y补 = 0x0019 + 0xFE6A = 0xFE83
−Y原 = (Y补 − 1)反 = 0x0096
[X−Y]补 = X补 − Y原 = 0x0019 + 0x0096 = 0x00AF
8.2 文字填空题
在X86-32位体系结构中,C语言过程调用的默认传参规则是将过程参数从____至____压入栈,过程返回值(32位)通过____寄存器传出。
答案:
- 右
- 左
%eax
8.3 填空题
给出13/8这一数字的32位浮点数(符合IEEE 754标准)表示,即exp= ____;frac= ____
答案:
01111111
10100000000000000000000
解析:
13/8 = (1101)2/2^3 = (1.101)_2
因此E=0
,exp = E+bias = 0+(2^(E_length−1)−1) = 0+(01111111)_2 = (01111111)_2
省略掉有效数字开头的1,frac=10100000000000000000000
。(后面一共20个0)
8.4 填空题
寄存器EAX,EBX内存储的为带符号32位整数,若%EAX >%EBX
,则指令cmpl %EAX,%EBX
执行后 SF=____,OF= ____。(若不确定,可以填“不确定”)
答案:
- 不确定
- 不确定
解析:cmpl
指令根据%ebx - %eax
的值设置条件码。因为补码加法可能溢出也可能不溢出,带符号数的减法结果可能溢出到正数也可能不溢出,所以两空均为不确定。
8.5 填空题
X86 32位linux系统下的float类型的数据对齐要求是____字节对齐,double类型的是____字节对齐;X86 32位Windows系统下的double类型数据是____字节对齐。
答案
- 4
- 4
- 8
8.6 填空题
lw $t6, 65536($sp)
经过MIPS 32汇编器处理后,产生的代码如下,请补全。
lui $1,____
addu $1, $1,____
lw $t6, 0($1)
答案:
1
$sp
解析:lui
将立即数装载到寄存器的高16位,低16位清零,而65536=(10000000000000000)_2
,因此,执行lui
指令后,$1=65536
。
8.7 填空题
li $6, 0x345678
经过MIPS 32汇编器处理后,产生的代码如下,请补全
lui $1, ____
____ $6, $1, ____
答案:
0x34
-
ori
或addiu
0x5678
解析:lui
的作用同上题;ori
作用的是寄存器的低16位。