学堂在线《汇编语言程序设计》题解及答案

《汇编语言程序设计》(http://www.xuetangx.com/courses/course-v1:TsinghuaX+20240103X+sp)是鄙系的张悠慧老师开的一门自主进度的课程,它以计算机系小学期《汇编语言程序设计》为蓝本,但是课程的设计并不太好,结构有些杂乱,而且,习题没有答案,很多地方都非常坑爹。所以我决定来写个题解啦……

课程内容

在清华计算机系,汇编语言程序设计通常作为本科生接触到的第一门计算机系统课程,被定位为该系列的入门课,起到“承上启下”的作用。
主要的授课内容包括:
(1)汇编语言与计算机系统结构、指令集初步、数制与整数表示、浮点数表示;
(2)80x86计算机组织与保护模式、X86指令系统与寻址方式、C与X86汇编、X86汇编编程;
(3)MIPS汇编。
课程强调汇编语言的软硬件分界与桥梁作用,使得学习者能把程序的执行与计算机的工作过程紧密联系起来,以便深入地感知、理解和体会计算机的逻辑功能以及各种软件系统的原理,逐步形成软件系统功能构筑在其上,硬件系统功能运行于其下的计算机系统思维能力。
与以往的讲法不同,本课程突出了“承上启下”这一理念,在内容编排上进一步突出了与相关课程的衔接,尤其是与C语言编程、编译原理、组成原理的衔接。比如说,(1)强化与高级语言的联系,从典型的C语言代码段入手,通过编译成汇编代码来详细解释程序员角度的X86结构运行模型。掌握这两种语言的对应可以将程序的执行与计算机的工作过程紧密联系起来,直接体现汇编语言本身固有的特点,即它是最易于将“程序”和“机器”统一起来的一个结合点。(2)进一步的通过对不同汇编代码的解释来给出微体系结构方面的差异。比如,同一段C代码通过不同的编译器/编译器开关所生成的代码是不一样的,为什么不一样?这就可以通过处理器微体系结构的差异来简单解释。这种做法可以为后续课程,如编译原理、计算机组成原理等提供一些先导知识,并有利于学生从整个系统构成的角度来理解各个课程的作用与位置。(3)加入MIPS汇编(包括部分体系结构的内容),为后续的以MIPS为核心的计算机组成原理、编译原理、操作系统等专业课程奠定MIPS汇编语言的基础。

课程内容与目标 题解

文字填空题

请写出ISA这个缩写的全名(单词首字母大写,单词间以"/"间隔)


答案:= Instruction/Set/Architecture

指令集简介 部分题解

单选题

只有LOAD和STORE指令可以访问存储器的指令系统属于RISC还是CISC?

  • RISC(√)
  • CISC

分析:这是RISC和CISC指令集的重要区别。

单选题

X86指令系统属于RISC还是CISC?

  • CISC(√)
  • RISC

单选题

X86指令至多只有一个操作数在()中。

  • 内存(√)
  • 寄存器

分析:而MIPS32指令的操作数必须都在寄存器中。

整数的机器表示 题解

单选题

X86处理器的存储字节序是()。

  • 小端(√)
  • 大端

分析:小端表示即所谓“低对低,高对高”(高位有效数字存在内存的高位中)。X86的字节序为小端,ARM、MIPS和IA64的字节序可配置,网络传输一般采取大端序。详见https://en.wikipedia.org/wiki/Endianness

无符号整数与带符号整数 题解

文字填空题

已知某32位整数X,其值为-101(十进制),则其16进制补码为(),另一32位整数Y的补码为0xFFFFFF6A,则X+Y的16进制补码(32位)为(),X-Y的16进制补码为()。(本题一共三个空,十六进制表示示例:0xFFFFFFFF 注意开头与字母大小写。答案之间以‘/‘符号隔开)
答案:0xFFFFFF9B/0xFFFFFF05/0x00000031
分析:正数的补码与原码相等,负数的补码等于原码取反+1。
$X = -101 = -(00000000 00000000 00000000 01100101)_2$
$\overline{X} = (11111111 11111111 11111111 10011010)_2 + 1 = (11111111 11111111 11111111 10011011)_2 = 0xFFFFFF9B$
$\overline{X+Y} = \overline{X} + \overline{Y} = 0xFFFFFF9B + 0xFFFFFF6A = 0xFFFFFF05$
$Y = -\overline{\overline{Y} - 1} = -\overline{(11111111 11111111 11111111 01101010)_2 - 1} = -(00000000 00000000 00000000 10010110)_2 = -0x00000096 = -150$
$\overline{X-Y} = \overline{X} - Y = 0xFFFFFF9B + 0x00000096 = 0x00000031$
(其实我这道题一直没有通过,但我觉得计算结果是对的)

判断题

以下几道判断题都与本题条件相同。
已知x、y为int类型; unsigned int ux = x; unsigned int uy = y.
判断以下等式是否成立?
(x>y)==(-x<-y)

  • 正确
  • 错误(√)

分析:若y=-2147483648,则-y=-2147483648,等式不成立。

判断题

(x|-x)>>31 == -1

  • 正确
  • 错误(√)

分析:取x=0,显然原式是错的。不过,当x不等于0时,原式是正确的,因为x和-x中至少有一个是负数,则(x|-x)的最高位必然为1,算术右移31位后,就全是1了。

判断题

~x+~y == ~(x+y)

  • 正确
  • 错误(√)

分析:随便举个例子就会发现这是错的。

判断题

(int)(ux-uy) == -(y-x)

  • 正确(√)
  • 错误

分析:可以这样思考:无符号整数和带符号整数的内部表示形式和运算规则都是一样的,区别只在于解释的方法。

浮点数的机器表示 题解

数值填空题

单精度浮点数的exp域的位宽是()位?
答案:8

数值填空题

单精度浮点数的frac域位宽是()位。
答案:23

判断题

已知 int x = …; float f = …; double d = …;且d 与 f 都不是 NaN。判断以下关系式是否成立?
x == (int)(float) x

  • 正确
  • 错误(√)

分析:float的frac域宽度不够,可能会有精度损失。

判断题

条件同上题
x == (int)(double) x

  • 正确(√)
  • 错误

分析:double的frac域足够放下int,不会有精度损失。

判断题

f == (float)(double) f

  • 正确(√)
  • 错误

分析:float转换成double不会有精度损失。

判断题

d == (float) d

  • 正确
  • 错误(对)

分析:double转换成float可能会有精度损失。

判断题

f == -(-f)

  • 正确(√)
  • 错误

分析:浮点数取负只改变符号位,不会溢出。

判断题

2/3 == 2/3.0

  • 正确
  • 错误(√)

分析:2/3的结果是整数,但2/3.0的结果是浮点数。

判断题

(d <0.0) == ((d*2) <0.0)

  • 正确(√)
  • 错误

分析:浮点数的下溢是逐步下溢,不像整数会从负数变成正数。

判断题

(d > f) == (-f > -d)

  • 正确(√)
  • 错误

分析:浮点数取负不会溢出。

判断题

(d+f)-d == f

  • 正确
  • 错误(√)

分析:d+f可能会溢出,损失精度。

80x80汇编与C语言-2 题解

文字填空题

已知寄存器edx内的数值为0x8000,ecx内的则为0x10;请给出地址表达式 0x4 (%edx,%ecx,4)所表示的地址值。
答案:0x8044
分析:D(Rb, Ri, S) = Rb + S*Ri +D

数值填空题

x86-64体系结构具有()个通用寄存器,而x86-32只有()个。
答案:16;8

判断题

leal (%edx,%eax),%ecx 这条指令在执行过程中需要访问内存数据。

  • 正确
  • 错误(√)

分析:leal指令只计算地址,不访存。因此leal指令也可以用于整数运算。

单选题

请问哪个条件码可用于检测无符号整数运算的溢出?

  • CF(√)
  • SF
  • ZF
  • OF

分析:CF(Carry Flag),无符号整数运算溢出时置位;SF(Sign Flag),计算结果<0时置位;ZF(Zero Flag),计算结果=0时置位;OF(Overflow Flag),带符号整数运算溢出时置位。

单选题

seta、setb指令适用于无符号还是带符号整数的条件码读取?

  • 无符号(√)
  • 带符号

分析:seta和setb中的a、b分别指的是above和below。带符号整数对应的是setg和setl(greater和less)。

文字填空题

请补充与下图中C语言对应的汇编代码中的遗漏部分(x86-32结构下编译生成)。

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
absdiff.jpg

答案: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则传送)。

80x80汇编与C语言-2(续) 题解

文字填空题

左侧的C语言程序段编译为右侧的汇编代码(x86-32体系结构),请填空

switch1.jpg
switch2.jpg

答案:.L8;.L3;.L4;.L9;.L8;.L6;.L6
分析:由switch的特性可知,7个空分别对应的是x=0到x=6时的跳转结果。经过分析可知,.L8对应的是默认情况(x=0及其他);.L6对应的是x=5或6;.L9对应的是x=3;.L4对应的是x=2;.L3对应的是x=1;.L12对应的是返回。

80x80汇编与C语言-3 题解

文字填空题

下图给出了一个C函数,并由gcc编译成相应的汇编代码(AT&T语法格式),请补全这段代码里头被省去的部分。(X86-32结构)

arith1.jpg
arith2.jpg

答案:%esp;8;12;%ecx;%ecx;%edx;%ebx
分析:下面用表格给出每行汇编代码的解释。

汇编 解释
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 12(%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)

文字填空题

在X86-32位体系结构中,当前运行函数的帧(Frame)基址寄存器所指向的栈地址的“上方”(高地址)由低到高存放的是函数返回地址、____;“下方”存放的是________(此处无需考虑顺序)。

答案:输入参数;局部变量;临时存储
分析:其实有很多填法(比如,把临时存储换成“保存的寄存器值”),不过这是标准答案了。

80x80汇编与C语言-4 题解

文字填空题

请按顺序填写图左侧汇编代码对应的C代码(e.g. 右3)

foo1.jpg

答案:右3;右5;右1
分析:值得注意的是右5和对应的汇编代码。众所周知,算术右移指令对应的除法的取整模式是向下取整,而C语言要求的除法的取整模式是向0取整,因此负数除法的取整会出问题,正确的计算方法是:a<0时,$a/2^b = (a+2^b-1) >> b$。这就解释了汇编代码中testl %eax, %eax和jge .L4的意义。jge .L4的意义是:当条件码满足SF=OF时,跳到.L4(即不是负数,不用加上15)。testl做的事是,取后面两个操作数的与,根据运算结果置条件码。当%eax>=0时,运算结果仍为%eax,SF为0,OF也为0,满足条件,跳转;当%eax<0是,SF为1,不符合条件,不跳转。

文字填空题

已知三个二维矩阵的定义如下,并已初始化。

#define n 10
int a[n][n] ;
int b[n][n] ;
int c[n][n] ;

需要进行矩阵乘,即矩阵a x b结果置于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;
        }
    }
}
for1.jpg
for2.jpg

答案:%esp;%ebp;%edi;%eax;%eax;-20;%edx;%esi;$9;$9;$8
分析:下面以表格形式给出汇编代码和解释。

汇编 解释
_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

80x80汇编编程-1 题解

文字填空题

在X86-32位编程中有一种简单的获得所运行的指令地址的方法(X86-32位结构下eip寄存器是无法直接访问的)。比如说我们要获得下面程序中XXXX这条指令的地址并置于eax寄存器中,那么可以采用如下代码段。请补充完函数GetAddress的第一条语句(AT&T语法)。

movl ____, ____
ret
call GetAddress
xxxx

答案:(%esp);%eax
分析:在call之后,GetAddress的返回地址,也就是xxxx的地址被压栈。此时,只需将栈顶所指向位置的内容((%esp),注意括号代表访存)存入eax中再返回。(其实第二空我没过,但老师告诉我填%eax是对的。)

数值填空题

已知一个c语言结构类型的定义如下图所示,请问在X86 32位Linux系统下变量p所占的空间大小是多少字节,对齐的要求为多少字节对齐?

struct1.jpg
答案: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对齐。

80x86汇编编程-2(程序链接) 题解

文字填空题

有如下的C代码以及对应的反汇编出来的汇编代码(x86-32体系结构):
当strcpy调用完成返回到foo过程时,buf[0]、buf[1]、buf[2]的值分别是多少?
在执行0x0804850d的ret指令前(popl后),ebp的值是多少?
上述ret指令执行后,eip的值是多少?
用32位16进制表示,注意大小端。e.g. 0x00000000 字符的十六进制转换表已给出

link1.jpg
link2.jpg
link3.jpg

答案: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
85 00
foo的返回地址 (buf[2]) (低) 23 69
保存的%ebp (buf[1]) (高) ?? 68
?? 67
?? 66
保存的%ebp (buf[1]) (低) ?? 65
buf[0] (高) ?? 64
?? 63
?? 62
buf[0] (低) ?? 61

而寄存器会以小端模式来解释内存中的内容,因此可得,buf[0] = 0x64636261,buf[1] = 0x68676665,buf[2] = 0x08040069;popl后得到的%ebp为0x68676665,执行ret后%eip的值(也就是要返回到什么地址)为0x08040069。

MIPS32指令集与编程 题解

文字填空题

异常(exception)可以分类为 ________两类,其中系统调用属于____异常、时钟中断属于____异常、Page Fault是 ____异常、机器cold reset是 ____异常。

答案:同步;异步;同步;异步;同步;异步
分析:同步异常一般是指令引起的,异步异常一般是硬件引起的。

文字填空题

位于某个跳转指令的Branch Delay Slot中的指令(这一slot中的指令地址为A)发生了异常,那么异常处理完成后,恢复执行的指令地址是 ____;如果该跳转指令是JAL,那么该跳转指令执行完成后31号寄存器的内容是 ____

答案:A-4;A+4
分析:精确异常处理要求,延迟槽中的指令如果发生异常,恢复执行的指令是跳转指令;函数返回到下一条指令。

期末考试

期末考试中有许多题是重复的,不再一一列出。因为考试不给具体的题目对错,我至今也没有刷到100分,因此也请大家帮忙纠正一下我是不是有题目做错了……

文字填空题

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$

文字填空题

在X86-32位体系结构中,C语言过程调用的默认传参规则是将过程参数从________压入栈,过程返回值(32位)通过____寄存器传出。

答案:右;左;%eax

文字填空题

给出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)

文字填空题

寄存器EAX,EBX内存储的为带符号32位整数,若%EAX >%EBX,则指令cmpl %EAX,%EBX执行后 SF=____,OF= ____。(若不确定,可以填“不确定”)

答案:不确定;不确定
分析:cmpl指令根据%ebx - %eax的值设置条件码。因为补码加法可能溢出也可能不溢出,带符号数的减法结果可能溢出到正数也可能不溢出,所以两空均为不确定。

文字填空题

X86 32位linux系统下的float类型的数据对齐要求是____字节对齐,double类型的是____字节对齐;X86 32位Windows系统下的double类型数据是____字节对齐。

答案:4;4;8

文字填空题

lw $t6, 65536($sp)经过MIPS 32汇编器处理后,产生的代码如下,请补全。

lui $1,____ 
addu $1, $1,____ 
lw $t6, 0($1)

答案:1;$sp
分析:lui将立即数装载到寄存器的高16位,低16位清零,而$65536 = (1 00000000 00000000)_2$,因此,执行lui指令后,$1=65536。

文字填空题

li $6, 0x345678 经过MIPS 32汇编器处理后,产生的代码如下,请补全

lui $1, ____
____ $6, $1, ____

答案:0x34;ori/addiu;0x5678
分析:lui的作用同上题;ori作用的是寄存器的低16位。


新增一则回应
除非特别注明,本页内容采用以下授权方式: Creative Commons Attribution-ShareAlike 3.0 License