《深入理解计算机系统》 练习题3.2-3.5 汇编相关
汇编命令总结
1)%eax,%dx就是寄存器的名字
2)(%rax)。只要是有括号的了,那就是内存引用。注意x86-64中的内存引用总是用四字长寄存器给出的,即寄存器名字开头都是r开头的。(%rax)意思是取寄存器%rax中的存的地址中的存的值,相当于解引用两次,先解引用寄存器,再解引用这地址(我用“解引用”这个词只是为了方便理解)。
3)mov命令中,两个操作数只允许有一个内存引用,即只能有一个带括号的。
4)关于mov命令的后缀,肯定与内存引用中的寄存器的长度无关,而是与另一个寄存器的长度有关
5)b
代表1个字节;w
代表1个字,2个字节;l
代表2个字,4个字节;q
代表4个字,8个字节。
3.2
补充以下mov命令的后缀:
原命令 | 讲解 |
---|---|
mov %eax, (%rsp) | %eax是2个字的,所以应该是movl |
mov (%rax), %dx | %dx是1个字的,所以应该是movw |
mov $0xFF, %bl | %bl是1个字节的,所以应该是movb |
mov (%rsp,%rdx,4), %dl | %dl是1个字节的,所以应该是movb |
mov (%rdx), %rax | %rax是4个字的,所以应该是movq |
mov %dx, (%rax) | %dx是1个字的,所以应该是movw |
3.3
下面的每行汇编命令都是错的,讲出原因
原命令 | 讲解 |
---|---|
movb $0xF, (%ebx) | 内存引用的寄存器必须是四个字的,改成movb $0xF, (%rbx) |
movl %rax, (%rsp) | %rax是四个字而l代表两个字,改成movl %eax, (%rsp) 或者 movq %rax, (%rsp) |
movw (%rax), 4(%rsp) | 两个操作数不能都是内存引用 |
movb %al, %sl | 没有寄存器名字叫%sl |
movq %rax,$0x123 | dest不能作为des操作数 |
movl %eax, %rdx | 原答案为destination operand incorrect size,改成movl %eax, %edx |
movb %si, 8(%rbp) | %si是一个字而b代表一个字节,改成movb %sil, 8(%rbp) 或者 movw %si, 8(%rbp) |
3.4
有以下的c语言程序:
src_t *sp;
dest_t *dp;
*dp = (dest_t) *sp;
假设转换成汇编语言后, sp 的值存在寄存 %rdi, dp 的值存在 %rsi。而第一个指令都是将sp的值先导入到 %rax寄存器的相应字节中的。
注意第一条指令就得选择好是零拓展还是符号拓展(当从小的到大的时,关心的是源操作数的有无符号),当从大到小时,就不需要选择了,毕竟直接截断就好了。
第一条指令,也得先拓展到目标类型的大小。
第二条指令,只需要关心目的类型的大小以此来决定后缀。
src_t | dest_t | 指令 | 讲解 |
---|---|---|---|
long | long | movq (%rdi), %rax movq %rax, (%rsi) |
long认为8字节,所以用q;因为目标为long,所以第二条指令为q |
char | int | movsbl (%rdi), %eax movl %eax, (%rsi) |
从小到大,因为源是有符号的,所以s;因为从char到int,所以是bl; 拓展好了直接第二个指令移过去就好了 |
char | unsigned | movsbl (%rdi),%eax movl %eax,(%rsi) |
和上面一样,但目标符号不同,但这里并不关心 |
unsigned char | long | movzbl (%rdi),%eax movq %rax,(%rsi) |
因为源无符号所以用z。但第一条指令的目的是2个字的 使用l和%eax。之所以不直接复制到4个字; 因为如果更新寄存器的低4字节,那么高4字节会自动置为0 |
int | char | movl (%rdi),%eax movb %al,(%rsi) |
从大到小,先直接全部读,然后再按照目标类型大小截断即可 |
insigned | unsigned char | movl (%rdi),%eax movb %al,(%rsi) |
和上面一样,源有无符号根本无所谓 |
char | short | movsbw (%rdi),%ax movw %ax,(%rsi) |
有符号;第一条命令先从b到w; |
总结:从小到大或一样大,两条指令的寄存器名字相同(这里指不带括号的,因为带括号的叫内存引用),但除了上表第4行,原因不明。猜想原因可能是:“常规的movq指令只能以表示为32补码数字的立即数作为操作数,然后把这个值符号拓展得到64位的值,放到目的位置”。
3.5
将下面的汇编代码转成 C 语言,假设函数函数为 void decode1( long *xp, long *yp, long *zp);汇编代码为:
void decode1(1ong *xp, long *yp, long *zp);
xp in %rdi, yp in %rsi, zp in %rdx
decode1:
movq (%rdi), %r8 --> %r8=*xp;
movq (%rsi) , %rcx --> %rcx=*yp
movq (%rdx), %rax --> %rax=*zp
movq %r8, (%rsi) --> *yp = *xp;
movq %rcx, (%rdx) --> *zp = *yp;
movq %rax, (%rdi) --> *xp = *zp;
ret
简单来说就是,xp的值到yp里,yp的值到zp里,zp的值到xp里。书中标准答案如下:
void decode1(1ong *xp, long *yp, long *zp)
{
long x = *xp;
long y = *yp;
long z = *zp;
*yp = x;
*zp = y;
*xp = z;
}
简化后可写成:
long temp = *zp;
*zp = *yp;
*yp = *xp;
*xp = temp;
return temp;
3.8
原题不写了,其中第三条需要手算10进制转16进制。
def solve(num):
dict = [str(i) for i in range(10)]
dict += ['A','B','C','D','E','F']
#虽然是个list,却是起dict的作用
li = []
result = num//16
while result >=16:
li.append(num%16)
num = result
result = num//16
li.append(num%16)
li.append(result)
while li:
print(dict[li.pop()],end='')
简单地说就是:
1)num除以16,有商和余数。余数是0-15的。
2)如果一个数除以16的商,直接是0-15的,那么先入栈余数,再入栈商。
3)如果一个数除以16的商,是>=16的,那么先入栈余数,然后将商除以16再来判断,如果是3的情况那么重复3,一直到2情况为止。