写在前面:从腾讯实习回来之后,就感觉到自己的知识体系过于散乱。于是萌生了写一个自己的操作系统这样的心思,此为系列第一章,主要是讲解一些汇编知识的,内容大多从CSAPP中也可以获得。
本篇内容主要讲解汇编指令:算数以及逻辑相关操作指令
加载有效地址
加载有效地址指令leaq
实际上是movq
指令的变形。 它的指令形式是从内存读取数据到寄存器。它的第一个操作数看上去是一个内存引用,但该指令实际是将有效地址写入到目的操作数。
其使用格式如下:
指令 | 效果 | 描述 |
---|---|---|
leaq S,D | D⬅&S | 加载有效地址 |
比如说我们下面的这个例子:
long scale(long x,long y,long z)
{
long t = x+4*y+12*z;
return t;
}
我们展示其反汇编代码:
//long scale(long x,long y, long z)
//x in %rdi,y in %rsi,z in %rdx
leaq (%rdi,%rsi,4),%rax //%rax: 4y+x
leaq (%rdx,%rdx,2),%rdx //%rdx: 3z
leaq (%rax,%rdx,4),%rax //%rax: 12z+4y+x
ret //return %rax
leaq 练习题
刚刚讲了leaq
的基础用法,现在做两个题巩固一下,还是那样:往下滚动查看答案和解析:
练习题答案
一元操作符&二元操作符
一元操作符,顾名思义:只有一个操作数,即是源又是目的。比如incq(%rsp)
会使栈顶的8字节元素+1。
指令 | 效果 | 描述 |
---|---|---|
inc D | D⬅D+1 | 加1 |
dec D | D⬅D-1 | 减1 |
neg D | D⬅-D | 取负数 |
not D | D⬅~D | 取补 |
二元操作符,则是第二个操作数既是源又是目的。比如指令subq %rax,%rdx
使寄存器%rdx
的值减去%rax
的值,等价于:%rdx = %rdx-%rax
。
注:
第一个操作数可以是立即数、寄存器或者内存位置
第二个操作数可以是寄存器或内存位置
但是!当第二个操作数为内存地址时,处理器必须从内存读出值,进行操作之后再写入内存中
指令 | 效果 | 描述 |
---|---|---|
add S,D | D⬅D+s | 加 |
sub S,D | D⬅D-s | 减 |
imul S,D | D⬅D*s | 乘 |
xor S,D | D⬅D^s | 异或 |
or S,D | D⬅Dls | 或 |
and S,D | D⬅D&s | 与 |
一元&二元 练习题
练习题答案
移位操作
移位操作会先给出移位量,然后第二项给出的是要移位的数。移位量可以是一个立即数或者放在单字节寄存器%cl
中。当移位操作对 w 位长的数据值进行操作,移位量是由%cl
寄存器的低 m 位决定的,这里2m = w,而高位则会被忽略。所以,当寄存器%cl
中的值为0xFF时,指令salb
会左移7位,salw
会左移15位,sqll
会左移31位,salq
会左移63位。
如下表所示,左移指令有两个名字:sal
和shl
。两者的效果是一样的,都是将右边填上0 。右移则是不同,sar
执行算术移位,会将扩充位填充符号位,而shr
执行逻辑移位,将扩充位填上0。
指令 | 效果 | 描述 |
---|---|---|
sal k,D | D⬅D<<k | 左移 |
shl k,D | D⬅D<<k | 左移(同sal) |
sar k,D | D⬅D>>k | 算数右移 |
shr k,D | D⬅D>>k | 逻辑右移 |
移位练习题
练习题答案
特殊的算数操作符
下表中的指令提供了一些不常用的特殊的算数操作:
可以在表中看到有两个乘法指令:imulq
、mulq
。一个是有符号乘法,一个是无符号乘法,这两个可以用来计算两个64位的值的全128位乘积。这两个指令都要求一个参数必须在寄存器%rax
中,而另一个作为指令的源操作数给出。之后,乘积存放在寄存器%rdx
(高64位)和%rax
(低64位),下面的例子给出了如何计算乘积:
#include <inttypes .h>
typedef unsigned __int128 uint128_ t;
void store_uprod(uint128 _t *dest, uint64_t x,uint64_t y) {
*dest = x * (uint128_t) y;
}
这个例子得到的汇编代码如下:
//dest in %rdi,x in %rsi,y in %rdx
store_uprod:
movq %rsi, %rax //%rax = x
mulq %rdx //%rax = %rax*%rdx
movq %rax, (%rdi) //低64位存储在dest中
movq %rdx, 8(%rdi) //高64位存储在dest+8中
ret
到目前为止,我们都没有介绍除法操作符和取模操作。这些操作是由单操作数除法指令所提供。有符号除法指令idivl
将寄存器%rdx
(高64位)和%rax
(低64位)中的128位数作为被除数,而除数作为指令的操作数给出。指令将商存储在寄存器%rax
,将余数存在寄存器%rdx
中。以下例子给出了除法的实现:
void remdiv(long x,long y,long *qp,long*rp)
{
long q = x / y;
long r = x % y;
*qp = q;
*rp = r;
}
其汇编实现如下:
//x in %rdi,y in %rsi,qp in %rdx,rp in %rcx
remdiv :
movq %rdx, %r8 //在%r8中存储qp
movq %rdi, %rax //%rax = x
cqto //符号扩展x为8字
idivq %rsi //执行x/y操作,商在%rax中,余数在%rdx中
movq %rax, (%r8) //存储商到%r8中
movq %rdx, (%rcx) //存储余数在%rcx中
ret
注:
无符号除法使用divq
指令,通常寄存器%rdx会事先设置为0
练习题
练习题答案
//x in %rdi,y in %rsi,qp in %rdx,rp in %rcx
movq %rdx, %r8 //在%r8中存储qp
movq %rdi, %rax //%rax = x
movl $0, %edx //无符号除法特性
divq %rsi //执行x/y操作,商在%rax中,余数在%rdx中
movq %rax, (%r8) //存储商到%r8中
movq %rdx, (%rcx) //存储余数在%rcx中
ret
参考文献
[1] 深入理解计算机系统 第三章 程序的机器级表示