当前位置: 首页 > news >正文

程序的机器级表示part3——算术和逻辑操作

目录

1.加载有效地址

2. 整数运算指令

2.1 INC 和 DEC

2.2 NEG 

2.3 ADD、SUB 和 IMUL

3. 布尔指令

3.1 AND

3.2 OR

3.3 XOR

3.4 NOT

4. 移位操作

4.1 算术左移和逻辑左移

4.2 算术右移和逻辑右移

5. 特殊的算术操作 


1.加载有效地址

指令效果描述
leaq    S, DD ← &S加载有效地址

加载有效地址load effective address )指令leaq是movq指令的变形,在64位系统下地址长度为64位,因此lea指令的大小后缀为q,没有其他变种,其目标操作数必须是一个寄存器

leaq指令非常特别,它的一般格式是 leaq (寄存器)  寄存器,看上去像是从内存中读取数据到寄存器,实际上leaq从不发生内存引用,也就是说leaq指令不访问内存,以下面的程序来说明

int main() 
{int x = 10;int *ptr = &x;return 0;
}00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 f4 0a 00 00 00 	movl   $0xa,-0xc(%rbp)4004f8:	48 8d 45 f4          	lea    -0xc(%rbp),%rax  // 取a的地址放进%rax4004fc:	48 89 45 f8          	mov    %rax,-0x8(%rbp)400500:	b8 00 00 00 00       	mov    $0x0,%eax400505:	5d                   	pop    %rbp400506:	c3                   	retq   400507:	66 0f 1f 84 00 00 00 	nopw   0x0(%rax,%rax,1)40050e:	00 00 

4004f8:    48 8d 45 f4              lea    -0xc(%rbp),%rax

这条指令在mov指令中表示:以%rbp内存放的值作为基地址,加上偏移量0xc作为地址,再取这个地址处的数据,并将数据传送到寄存器%rax内

但在leaq指令则表示:以%rbp内存放的值作为基地址,加上偏移量0xc作为地址,将这个地址传送到寄存器内,也就是 &x 这一行为

%rbp是帧寄存器,保存着main函数栈帧的栈顶位置

假设%rbp的值是10000,地址1000c处保存的值是10

  • movq指令将10传送到寄存器%rax
  • leaq指令将1000c传送到寄存器%rax

leaq指令完成简单的基地址和偏移量的相加,实际上,leaq指令不光能完成地址的相加,也常用于普通的算术操作,比如下面一条指令

leaq 7(%rdx, %rdx, 4), %rax

假设寄存器%rdx的值为x,这条指令的意思是将 %rax的值设置成 x + 4x +7 ,参考linux下的寻址模式的计算,比如下面的代码

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
*/
scale:leaq (%rdi,%rsi,4), %rax     x + 4*yleaq (%rdx,%rdx,2), %rdx     z + 2*z = 3*zleaq (%rax,%rdx,4), %rax     (x+4*y) + 4*(3*z) = x + 4*y + 12*zret

因此,leaq指令也能完成加法和有限的乘法计算,需要注意的一点是,寻址模式中的比例因子只能是1,2,4,8,说明leaq指令完成乘法时,也只能与1,2,4,8相乘,上面代码中,第二行不能使用 leaq(%rax, %rdx, 12) 一步完成计算,而是要分成两步也正是因为这个原因

2. 整数运算指令

指令效果描述
INC    DD ← D + 1加1
DEC    DD ← D - 1减1
NEG    DD ← - D 取负
NOT    DD ← ~ D 取补
ADD    S, DD ← D + S
SUB    S, DD ← D - S
IMUL    S, DD ← D * S

这些整数操作随着操作数大小的不同在使用时要加上操作数大小描述符,因而有四种不同的指令

前四条指令inc,dec,neg 和 not的操作数都只有一个,即是源又是目的,因此称为一元操作,这个操作数可以是一个寄存器,也可以是一个内存位置

后三条指令add,sub,imul 的操作数有两个,其中第二个操作数即作为源使用,又作为目的使用,因此称为二元操作

2.1 INC 和 DEC

INC(Increment)指令从操作数中加1,DEC(Decrement)指令从操作数中减1,二者均不影响CF

指令格式

  • inc reg/mem
  • dec reg/mem 

使用下面的代码查看inc和dec指令的功能

#include <stdio.h>int main() {int x = 10;// printf("The value of x before the increment: %d\n", x);    10__asm__ ( "inc %0\n" : "=r" (x) : "0" (x) );                   // printf("The value of x after the increment: %d\n", x);      11// printf("The value of x before the increment: %d\n", x);     11__asm__ ( "dec %0\n" : "=r" (x) : "0" (x) );// printf("The value of x after the increment: %d\n", x);      10return 0;
}
00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)4004f8:	8b 45 fc             	mov    -0x4(%rbp),%eax4004fb:	ff c0                	inc    %eax             // 把%eax中的值加14004fd:	89 45 fc             	mov    %eax,-0x4(%rbp)400500:	8b 45 fc             	mov    -0x4(%rbp),%eax400503:	ff c8                	dec    %eax             // 把%eax中的值减1400505:	89 45 fc             	mov    %eax,-0x4(%rbp)400508:	b8 00 00 00 00       	mov    $0x0,%eax40050d:	5d                   	pop    %rbp40050e:	c3                   	retq   40050f:	90                   	nop

2.2 NEG 

NEG(negative):将数字转换为对应的二进制补码, 从而求得其相反数,影响的标志位有

进位标志CF、零标志ZF、符号标志SF、溢出标志OF、辅助进位标志AF和奇偶标志PF(结果低8位中,数值1 的个数是否为偶数)。

指令格式

  • neg reg
  • neg mem 

使用下面的代码查看neg指令的功能

int main() 
{int i = 10;i = -i;i = ~i;return 0;
}
00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)4004f8:	f7 5d fc             	negl   -0x4(%rbp)      // i = -i;4004fb:	b8 00 00 00 00       	mov    $0x0,%eax400500:	5d                   	pop    %rbp400501:	c3                   	retq   400502:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)400509:	00 00 00 40050c:	0f 1f 40 00          	nopl   0x0(%rax)

2.3 ADD、SUB 和 IMUL

ADD(addition):指令将同尺寸的源操作数和目的操作数相加

SUB(subtraction):指令将同尺寸的源操作数和目的操作数相减

IMUL(multiplication):指令将同尺寸的源操作数和目的操作数相乘 

指令格式

  • add 源操作数, 目的操作数
  • sub 源操作数, 目的操作数
  • imul 源操作数, 目的操作数

指令的源操作数可以是:立即数,寄存器,内存位置

指令的目的操作数可以是:寄存器,内存位置

使用下面的代码查看add,sub,和 imul 指令的功能

int main() 
{int a = 10;int b = a + 10;int c = b - 15;a = a * b;
}00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)    // int a = 10;4004f8:	8b 45 fc             	mov    -0x4(%rbp),%eax4004fb:	83 c0 0a             	add    $0xa,%eax          // int b = a + 10;4004fe:	89 45 f8             	mov    %eax,-0x8(%rbp)400501:	8b 45 f8             	mov    -0x8(%rbp),%eax400504:	83 e8 0f             	sub    $0xf,%eax          // int c = b - 15;400507:	89 45 f4             	mov    %eax,-0xc(%rbp)40050a:	8b 45 fc             	mov    -0x4(%rbp),%eax40050d:	0f af 45 f8          	imul   -0x8(%rbp),%eax    // a = a * b;400511:	89 45 fc             	mov    %eax,-0x4(%rbp)400514:	5d                   	pop    %rbp400515:	c3                   	retq   400516:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)40051d:	00 00 00 

3. 布尔指令

C语言里存在位操作符

分别对应下面的指令 

指令效果描述
AND    S, DD ← D & S 
OR    S, DD ← D | 1
XOR    S, DD ← D ^ S异或
NOT    DD ← ~ D 取补

3.1 AND

 AND 指令在每对操作数的对应数据位之间执行布尔位“与”操作,并将结果存放在目的操作数中

指令格式

  • AND reg/mem/imm, reg/mem

AND指令总是使得CF=0、OF=0,并依据目的操作数的值修改SF、ZF和PF的值 

参考如下代码 

int main() 
{int x = 10;      // 00000000 00000000 00000000 00001010int y = x & 8;   // 00000000 00000000 00000000 00001000return 0;
}00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)    // int x = 10;4004f8:	8b 45 fc             	mov    -0x4(%rbp),%eax4004fb:	83 e0 08             	and    $0x8,%eax          // int y = x & 8;4004fe:	89 45 f8             	mov    %eax,-0x8(%rbp)400501:	b8 00 00 00 00       	mov    $0x0,%eax400506:	5d                   	pop    %rbp400507:	c3                   	retq   400508:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)40050f:	00 

3.2 OR

OR 指令在每对操作数的对应数据位之间执行布尔位“或” 操作,并将结果存放在目的操作数中

指令格式

  • OR reg/mem/imm,  reg/mem

OR指令总是使得CF=0、OF=0,依据目的操作数的值修改SF、ZF和PF的值 

参考如下代码 

int main() 
{int x = 10;      // 00000000 00000000 00000000 00001010int y = x | 8;   // 00000000 00000000 00000000 00001000return 0;
}00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)    // int x = 10;4004f8:	8b 45 fc             	mov    -0x4(%rbp),%eax4004fb:	83 c8 08             	or     $0x8,%eax          // int y = x | 84004fe:	89 45 f8             	mov    %eax,-0x8(%rbp)400501:	b8 00 00 00 00       	mov    $0x0,%eax400506:	5d                   	pop    %rbp400507:	c3                   	retq   400508:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)40050f:	00 

3.3 XOR

XOR 指令在每对操作数的对应数据位之间执行布尔位“异或” 操作,并将结果存放在目的操作数中 

指令格式

  • XOR reg/mem/imm,  reg/mem

OR指令总是使得CF=0、OF=0,依据目的操作数的值修改SF、ZF和PF的值 

参考如下代码 

int main() 
{int x = 10;      // 00000000 00000000 00000000 00001010int y = x ^ 8;   // 00000000 00000000 00000000 00001000return 0;
}00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)    // int x = 10;4004f8:	8b 45 fc             	mov    -0x4(%rbp),%eax4004fb:	83 f0 08             	xor    $0x8,%eax          // int y = x ^ 8;4004fe:	89 45 f8             	mov    %eax,-0x8(%rbp)400501:	b8 00 00 00 00       	mov    $0x0,%eax400506:	5d                   	pop    %rbp400507:	c3                   	retq   400508:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)40050f:	00 

3.4 NOT

NOT 指令将一个操作数的所有数据位取反 

指令格式

  • NOT reg/mem

NOT 指令不修改任何状态标志 

参考如下代码 

int main() 
{int x = 10;      // 00000000 00000000 00000000 00001010int y = ~x;      // 11111111 11111111 11111111 11110101return 0;
}00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)    // int x = 10;4004f8:	8b 45 fc             	mov    -0x4(%rbp),%eax4004fb:	f7 d0                	not    %eax               // ~x;4004fd:	89 45 f8             	mov    %eax,-0x8(%rbp)    // int y = ~x;400500:	b8 00 00 00 00       	mov    $0x0,%eax400505:	5d                   	pop    %rbp400506:	c3                   	retq   400507:	66 0f 1f 84 00 00 00 	nopw   0x0(%rax,%rax,1)40050e:	00 00 

4. 移位操作

C语言中移位操作符分为左移操作符(<<)和右移操作符(>>),而移位操作又分左移和右移

指令效果描述
SAL    k, DD ← D << k算术左移
SHL    k, DD ← D << k

逻辑左移

(等同SAL)

SAR    k, DD ← D >> k算数右移
SHL    k, DD ← D >> k逻辑右移

移位操作

  1. 第一个操作数是移位量k,也就是二进制位移动的位数
  2. 第二个操作数是要移位的数

注意:移位量可以是一个立即数,或者放在单字节寄存器%cl中(规定了只能放在这里) 

%cl长8位,可表示0~255,因此移位量的最大可以达到255位,但是显然没有这么长的数据类型,因此实际上移位操作是根据要移动的数的位数来决定取%cl的哪些值的,

x86-64中,移位操作对w位长的数据值进行操作,移位量是由%cl寄存器的低m位决定的,这里2的m次方等于w,高位会被忽略 

比如此时%cl内是0xFF

%cl1111 1111

对于不同的数据类型

  • char类型的数据,长8位,取%cl中的低三位 111,因此会移动7位
  • short类型的数据,长16位,取%cl中低四位 1111,因此会移动15位
  • int类型的数据,长32位,取%cl中低五位 11111,因此会移动31位

4.1 算术左移和逻辑左移

SAL(Arithmetic Left Shift):对目的操作数执行逻辑左移操作,低位填0 ,移出的最高位送CF

SHL(Logic Left Shift):与SAL指令等价

指令格式

  • sal imm8/CL, reg/mem
  • shl imm8/CL, reg/mem

参考如下代码 

int main() 
{int x = 10;      // 00000000 00000000 00000000 00001010int y = x << 2;  // 00000000 00000000 00000000 00101000return 0;
}00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)     // int x = 10;4004f8:	8b 45 fc             	mov    -0x4(%rbp),%eax4004fb:	c1 e0 02             	shl    $0x2,%eax           // x << 24004fe:	89 45 f8             	mov    %eax,-0x8(%rbp)     // int y = x << 2;400501:	b8 00 00 00 00       	mov    $0x0,%eax400506:	5d                   	pop    %rbp400507:	c3                   	retq   400508:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)40050f:	00 

4.2 算术右移和逻辑右移

SHR(Logic Shift Right ):对目的操作数执行逻辑右移操作,移出的数据位以0 填充,最低位被送到CF中 

指令格式 

  • shr imm8/CL, reg/mem

SAL(Arithmetic Right Shift):用最高位填充空出的位,最低位拷贝至CF

指令格式 

  • sar imm8/CL, reg/mem

参考如下代码(这里算术右移最特殊,只演示算术右移)

int main()
{int x1 = 10;       // 00000000 00000000 00000000 00001010int y1 = x1 >> 2;  // 00000000 00000000 00000000 00000010int x2 = -10;      // 11111111 11111111 11111111 11110110int y2 = x2 >> 2;  // 11111111 11111111 11111111 11111101return 0;
}00000000004004ed <main>:4004ed:	55                   	push   %rbp4004ee:	48 89 e5             	mov    %rsp,%rbp4004f1:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)4004f8:	8b 45 fc             	mov    -0x4(%rbp),%eax4004fb:	c1 f8 02             	sar    $0x2,%eax          // 算术右移,以0填充4004fe:	89 45 f8             	mov    %eax,-0x8(%rbp)400501:	c7 45 f4 f6 ff ff ff 	movl   $0xfffffff6,-0xc(%rbp)400508:	8b 45 f4             	mov    -0xc(%rbp),%eax40050b:	c1 f8 02             	sar    $0x2,%eax          // 算术右移,以1填充40050e:	89 45 f0             	mov    %eax,-0x10(%rbp)400511:	b8 00 00 00 00       	mov    $0x0,%eax400516:	5d                   	pop    %rbp400517:	c3                   	retq   400518:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)40051f:	00 

由于算术右移会对有符号数和无符号数进行区分,因此使用算术右移对补码进行操作可以代替一部分的整数运算,对于下面的arith函数

long arith(long x, long y, long z)
{long t1 = x ^ y;long t2 = z * 48;long t3 = t1 & 0x0F0F0F0F;long t4 = t2 - t3;return t4;
}

对应的汇编为 

/*
long arith(long x, long y, long z)
x in %rdi, y in %rsi, z in %rdx
*/arith:xorq %rsi, %rdi             t1 = x ^ yleaq (%rdx,%rdx,2), %rax    3*zsalq $4, %rax               t2 = 16 * (3*z) = 48*zandl $252645135, %edi       t3 = t1 & 0x0F0F0F0Fsubq %rdi, %rax             Return t2 - t3ret

这里用salq $4, %rax 代替乘法,可以加快运算

5. 特殊的算术操作 

两个64位有符号数或者无符号数相乘得到的乘积需要128位来表示。x86-64指令集对128位数的操作提供了一定程度上的支持,Intel将16字节的数称为8字(oct word)

下表是支持产生两个64位数字的全128位乘积以及整数除法的指令

指令效果描述
imuq    SR[ %rdx ]:R[ %rax ] ← S × R[ %rax ] 有符号乘法
mulq    SR[ %rdx ]:R[ %rax ] ← S × R[ %rax ] 

无符号乘法

cqtoR[ %rdx ]:R[ %rax ] ← SignExtend(R[ %rax ])转化为八字
idivq    S

R[ %rdx ] ← R[ %rdx ]:R[ %rax ]mod S

R[ %rax ] ← R[ %rdx ]:R[ %rax ]÷ S

有符号除法
divq    S

R[ %rdx ] ← R[ %rdx ]:R[ %rax ]mod S

R[ %rax ] ← R[ %rdx ]:R[ %rax ]÷ S

无符号除法

两个寄存器%rdx(64位)和%rax(64位)组成一个128位的八字,根据乘积中高部分是否为0设置或清楚CF、OF

对于无符号乘法(mulq)和有符号乘法(imulq)而言,二者都是单操作数乘法指令,都需要将一个参数存放在寄存器%rax中,而另一个则作为指令的源操作数给出,乘积放在寄存器%rdx和%rax中

%rdx(64位)%rax(64位)

下面是一个示例,其中细节见CASPP原书3.5.5小节

#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;
}
/*
void store_uprod(uint128_t *dest, uint64_t x, uint64_t y)
dest in %rdi, x in %rsi, y in %rdx
*/store_uprod:movq %rsi, %rax Copy x to multiplicandmulq %rdx Multiply by ymovq %rax, (%rdi) Store lower 8 bytes at destmovq %rdx, 8(%rdi) Store upper 8 bytes at dest+8ret

http://www.lryc.cn/news/4265.html

相关文章:

  • 基于YOLOV5的钢材缺陷检测
  • Session与Cookie的区别(三)
  • 七大设计原则之接口隔离原则应用
  • 【Shell1】shell语法,ssh/build/scp/upgrade,环境变量,自动升级bmc
  • JavaScript HTML DOM - 改变CSS
  • mycat连接mysql 简单配置
  • Spring常用注解
  • I.MX6ULL内核开发9:kobject-驱动的基石
  • Docker-harbor私有仓库
  • Java之动态规划之子序列问题
  • java ArrayList
  • 前端——周总结系列四
  • Linux重定向符、管道符讲解
  • 【C++】多态
  • 分布式项目-品牌管理(5、6)
  • 自定义ESLint规则开发与使用
  • 【JavaScript】35_包装类与垃圾回收机制
  • 【CS224W】(task3)NetworkX工具包实践
  • ansible的模块详解
  • 《Terraform 101 从入门到实践》 Functions函数
  • 使用kubeadm快速部署一个K8s集群
  • 初探富文本之CRDT协同算法
  • Dubbo和Zookeeper集成分布式系统快速入门
  • 大数据工具Maxwell的使用
  • freesurfer如何将组模板投影到个体空间——如投影 Schaefer2018 到个体空间
  • Matlab傅里叶谱方法求解二维波动方程
  • 【深度学习】卷积神经网络
  • 【C++】六个默认成员函数——取地址重载,const成员函数
  • Win11浏览器无法上网,秒杀网上99.9%教程—亲测完胜
  • Vulkan Graphics pipeline Dynamic State(图形管线之动态状态)