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

程序的机器级表示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

大家好,我是讯享网,很高兴认识大家。

目录

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, D D ← &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 %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 f4 0a 00 00 00 movl $0xa,-0xc(%rbp) 4004f8: 48 8d 45 f4 lea -0xc(%rbp),%rax // 取a的地址放进%rax 4004fc: 48 89 45 f8 mov %rax,-0x8(%rbp) : b8 00 00 00 00 mov $0x0,%eax : 5d pop %rbp : c3 retq : 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*y leaq (%rdx,%rdx,2), %rdx z + 2*z = 3*z leaq (%rax,%rdx,4), %rax (x+4*y) + 4*(3*z) = x + 4*y + 12*z ret

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

2. 整数运算指令

指令 效果 描述
INC    D D ← D + 1 加1
DEC    D D ← D - 1 减1
NEG    D D ← - D  取负
NOT    D D ← ~ D  取补
ADD    S, D D ← D + S
SUB    S, D D ← D - S
IMUL    S, D D ← 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); 10 return 0; }
00000000004004ed <main>: 4004ed: 55 push %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) 4004f8: 8b 45 fc mov -0x4(%rbp),%eax 4004fb: ff c0 inc %eax // 把%eax中的值加1 4004fd: 89 45 fc mov %eax,-0x4(%rbp) : 8b 45 fc mov -0x4(%rbp),%eax : ff c8 dec %eax // 把%eax中的值减1 : 89 45 fc mov %eax,-0x4(%rbp) : b8 00 00 00 00 mov $0x0,%eax 40050d: 5d pop %rbp 40050e: 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 %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: 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,%eax : 5d pop %rbp : c3 retq : 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) : 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 %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) // int a = 10; 4004f8: 8b 45 fc mov -0x4(%rbp),%eax 4004fb: 83 c0 0a add $0xa,%eax // int b = a + 10; 4004fe: 89 45 f8 mov %eax,-0x8(%rbp) : 8b 45 f8 mov -0x8(%rbp),%eax : 83 e8 0f sub $0xf,%eax // int c = b - 15; : 89 45 f4 mov %eax,-0xc(%rbp) 40050a: 8b 45 fc mov -0x4(%rbp),%eax 40050d: 0f af 45 f8 imul -0x8(%rbp),%eax // a = a * b; : 89 45 fc mov %eax,-0x4(%rbp) : 5d pop %rbp : c3 retq : 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1) 40051d: 00 00 00 

3. 布尔指令

C语言里存在位操作符

分别对应下面的指令 

指令 效果 描述
AND    S, D D ← D & S 
OR    S, D D ← D | 1
XOR    S, D D ← D ^ S 异或
NOT    D D ← ~ 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 00001010 int y = x & 8; // 00000000 00000000 00000000 00001000 return 0; } 00000000004004ed <main>: 4004ed: 55 push %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) // int x = 10; 4004f8: 8b 45 fc mov -0x4(%rbp),%eax 4004fb: 83 e0 08 and $0x8,%eax // int y = x & 8; 4004fe: 89 45 f8 mov %eax,-0x8(%rbp) : b8 00 00 00 00 mov $0x0,%eax : 5d pop %rbp : c3 retq : 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 00001010 int y = x | 8; // 00000000 00000000 00000000 00001000 return 0; } 00000000004004ed <main>: 4004ed: 55 push %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) // int x = 10; 4004f8: 8b 45 fc mov -0x4(%rbp),%eax 4004fb: 83 c8 08 or $0x8,%eax // int y = x | 8 4004fe: 89 45 f8 mov %eax,-0x8(%rbp) : b8 00 00 00 00 mov $0x0,%eax : 5d pop %rbp : c3 retq : 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 00001010 int y = x ^ 8; // 00000000 00000000 00000000 00001000 return 0; } 00000000004004ed <main>: 4004ed: 55 push %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) // int x = 10; 4004f8: 8b 45 fc mov -0x4(%rbp),%eax 4004fb: 83 f0 08 xor $0x8,%eax // int y = x ^ 8; 4004fe: 89 45 f8 mov %eax,-0x8(%rbp) : b8 00 00 00 00 mov $0x0,%eax : 5d pop %rbp : c3 retq : 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 00001010 int y = ~x; //     return 0; } 00000000004004ed <main>: 4004ed: 55 push %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) // int x = 10; 4004f8: 8b 45 fc mov -0x4(%rbp),%eax 4004fb: f7 d0 not %eax // ~x; 4004fd: 89 45 f8 mov %eax,-0x8(%rbp) // int y = ~x; : b8 00 00 00 00 mov $0x0,%eax : 5d pop %rbp : c3 retq : 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 40050e: 00 00 

4. 移位操作

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

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

逻辑左移

(等同SAL)

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

移位操作

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

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

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

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

比如此时%cl内是0xFF

%cl 1111 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 00001010 int y = x << 2; // 00000000 00000000 00000000 00 return 0; } 00000000004004ed <main>: 4004ed: 55 push %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) // int x = 10; 4004f8: 8b 45 fc mov -0x4(%rbp),%eax 4004fb: c1 e0 02 shl $0x2,%eax // x << 2 4004fe: 89 45 f8 mov %eax,-0x8(%rbp) // int y = x << 2; : b8 00 00 00 00 mov $0x0,%eax : 5d pop %rbp : c3 retq : 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 00001010 int y1 = x1 >> 2; // 00000000 00000000 00000000 00000010 int x2 = -10; //     int y2 = x2 >> 2; //     return 0; } 00000000004004ed <main>: 4004ed: 55 push %rbp 4004ee: 48 89 e5 mov %rsp,%rbp 4004f1: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) 4004f8: 8b 45 fc mov -0x4(%rbp),%eax 4004fb: c1 f8 02 sar $0x2,%eax // 算术右移,以0填充 4004fe: 89 45 f8 mov %eax,-0x8(%rbp) : c7 45 f4 f6 ff ff ff movl $0xfffffff6,-0xc(%rbp) : 8b 45 f4 mov -0xc(%rbp),%eax 40050b: c1 f8 02 sar $0x2,%eax // 算术右移,以1填充 40050e: 89 45 f0 mov %eax,-0x10(%rbp) : b8 00 00 00 00 mov $0x0,%eax : 5d pop %rbp : c3 retq : 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 ^ y leaq (%rdx,%rdx,2), %rax 3*z salq $4, %rax t2 = 16 * (3*z) = 48*z andl $, %edi t3 = t1 & 0x0F0F0F0F subq %rdi, %rax Return t2 - t3 ret

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

5. 特殊的算术操作 

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

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

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

无符号乘法

cqto R[ %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 multiplicand mulq %rdx Multiply by y movq %rax, (%rdi) Store lower 8 bytes at dest movq %rdx, 8(%rdi) Store upper 8 bytes at dest+8 ret

小讯
上一篇 2025-04-05 09:46
下一篇 2025-01-29 12:31

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/40658.html