C/C++教程

【汇编语言与计算机系统结构笔记06】地址计算指令,lea / leal,x86-32与x86-64下的swap对比,汇编的格式对比(Intel/Microsoft Differs from GAS)

本文主要是介绍【汇编语言与计算机系统结构笔记06】地址计算指令,lea / leal,x86-32与x86-64下的swap对比,汇编的格式对比(Intel/Microsoft Differs from GAS),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

本次笔记内容:
07.寻址模式与数据传输指令等-2

文章目录

      • 变址寻址
        • 寻址模式实例
      • 总结mov指令
      • 地址计算指令 lea
      • 整数计算指令
      • 将leal指令用于计算
        • 实例1
        • 实例2
      • x86-32与x86-64的数据类型宽度
      • x86-64的通用寄存器
        • x86-32与x86-64下的swap对比
      • 小结:x86指令的特点
      • 扩展:x86汇编的格式
        • Intel/Microsoft Format
        • AT&T Format
        • Intel/Microsoft Differs from GAS
      • 练习题
      • 答疑:movl与leal中地址表达式

变址寻址

承接上次笔记内容,对变址寻址进行讨论:

D(Rb, Ri, S) : Mem[Reg[Rb] + S * Reg[Ri]]
  • D为常量(地址偏移量);
  • Rb为基址寄存器:8个通用寄存器其之一;
  • Ri为索引寄存器:%esp不作为索引寄存器,一般%ebp也不做这个用途;
  • S为比较因子1, 2, 3或8。

其他变形还有:

(Rb, Ri)	: Mem[Reg[Rb] + Reg[Ri]]
D(Rb, Ri)	: Mem[Reg[Rb] + Reg[Ri] + D]
(Rb, Ri, S)	: Mem[Reg[Rb] + S * Reg[Ri]]

寻址模式实例

首先,已知%edx = 0xf000,%ecx = 0x100.

Expression Computation Address
0x8(%edx) 0xf000 + 0x8 0xf008
(%edx, %ecx) 0xf000 + 0x100 0xf100
(%edx, %ecx, 4) 0xf000 + 4*0x100 0xf400
0x80 (, %edx, 2) 2*0xf000 + 0x80) 0x1e080)

总结mov指令

MOV S, D 表示将S移动到D(在AT&T汇编格式中):

  • movb:传送字节;
  • movw:传送字;
  • movl:传送双字。

MOVS S, D 表示将符号扩展的S移动到D(在AT&T汇编格式中):

  • movsbw:将做了符号扩展的字节传送到字;
  • movsbl:将做了符号扩展的字节传送到双字;
  • movswl:将做了符号扩展的字传送到双字。

MOVZ S, D 表示将零扩展的S移动到D(在AT&T汇编格式中):

  • movzbw:将做了零扩展的字节传送到字;
  • movzbl:将做了零扩展的字节传送到双字;
  • movzwl:将做了零扩展的字传送到双字。

pushl S 表示将双字压栈;popl D 表示将双字出栈。之后的课程会具体讨论。

地址计算指令 lea

leal Src, Dest

  • Src是地址计算表达式;
  • 计算出来的地址赋给Dest。

使用实例:

  • 地址计算(无需访存)如:translation of p = %x[i];
  • 进行x + k * y这一类型的整数运算:k = 1, 2, 4 or 8。

整数计算指令

双操作数指令:

Format Computation
addl Src, Dest Dest = Dest + Src
subl Src, Dest Dest = Dest - Src
imull Src, Dest Dest = Dest * Src
sall Src, Dest Dest = Dest << Src 与shll等价
sarl Src, Dest Dest = Dest >> Src 算数右移
shrl Src, Dest Dest = Dest >> Src 逻辑右移
xorl Src, Dest Dest = Dest ^ Src 异或
andl Src, Dest Dest = Dest & Src
orl Src, Dest Dest = Dest

单操作数指令:

Format Computation
incl Dest Dest = Dest + 1
decl Dest Dest = Dest - 1
negl Dest Dest = - Dest
notl Dest Dest = ~ Dest

将leal指令用于计算

实例1

int arith(int x, int y, int z)
{
	int t1 = x + y;
	int t2 = z + t1;
	int t3 = x + 4;
	int t4 = y * 48;
	int t5 = t3 + t4;
	int rval = t2 * t5;
	return rval;
}
arith:
	pushl %ebp
	movl %esp, %ebp
	# codes above are for Set up
	
	movl 8(%ebp), %eax
	movl 12(%ebp), %edx
	leal (%edx, %eax), %ecx
	leal (%edx, %edx, 2), %edx
	sall $4, %edx
	addl 16(%ebp), %ecx
	leal 4(%edx, %eax), %eax
	imull %ecx, %eax
	# codes above are for Body

	movl %ebp, %esp
	popl %ebp
	ret
	# Finish

在进入Body之前,获得如下结构:

在这里插入图片描述

其中,ebp表示基址。栈中,返回地址为ebp + 4,x、y、z分别也被压入栈中(符合函数调用传参规范)。

1	movl 8(%ebp), %eax			# eax = x
2	movl 12(%ebp), %edx			# edx = y
3	leal (%edx, %eax), %ecx		# ecx = x + y
4	leal (%edx, %edx, 2), %edx	# edx = 3 * y
5	sall $4, %edx				# edx = 48 * y (t4)
6	addl 16(%ebp), %ecx			# ecx = z + t1 (t2)
7	leal 4(%edx, %eax), %eax	# eax = 4 + t4 + x (t5)
8	imull %ecx, %eax			# eax = t5 * t2 (rval)

对于第3条指令,用lea指令将x+y值放在ecx中。相当于c语言过程的局不变量。如果寄存器够用,将局部变量放在寄存器中,否者放在栈中。

对于第4、5条指令,首先乘3,已达到最终乘以48的效果:

  • 如何乘3?即将edx加edx乘以2,放在edx中,以实现edx乘以3。
  • 之后,左移4位,即乘16。

注意到,第3条指令计算t1,第4、5条t4,第6条t2,第7条t5,与c中的顺序并不一致。

实例2

int logical(int x, int y)
{
	int t1 = x^y;
	int t2 = t1 >> 17;
	int mask = (1<<13) - 7;
	int t4 = y * 48;
	int rval = t2 & mask;
	return rval;
}
logical:
	pushl %ebp
	movl %esp, %ebp
	# codes above are for Set up
	
	movl 8(%ebp), %eax
	xorl 12(%ebp), %eax
	sarl $17, %eax
	andl $8185, %eax
	# codes above are for Body

	movl %ebp, %esp
	popl %ebp
	ret
	# Finish

只看Body,先不讨论栈的分配与回收:

1	movl 8(%ebp), %eax		eax = x
2	xorl 12(%ebp), %eax		eax = x ^ y		(t1)
3	sarl $17, %eax			eax = t1 >> 17	(t2)
4	andl $8185, %eax		eax = t2 & 8185
  • 第2行,进行异或运算;
  • 第3行,t1是一个带符号数,因此右移使用sarl进行算数右移,而非逻辑右移;
  • 2 13 − 7 = 8185 2^{13} - 7 = 8185 213−7=8185,编译器将常数mask计算了出来,而非交给机器去做。

c中,return一个32位整型,放在eax中;如果32位中return一个long long类型(64位),放在edx + eax中。如果在64位机器中,则直接放在rax中。

x86-32与x86-64的数据类型宽度

Size of C Objects (in Bytes)

C Data Type Typical 32-bit Intel IA32 x86-64
unsigned 4 4 4
int 4 4 4
long int 4 4 8
char 1 1 1
short 2 2 2
float 4 4 4
double 8 8 8
long double 8 10/12 16
char * 4 4 8

如果返回数在32位及以下,那么一个eax就够了。

x86-64的通用寄存器

64位进行了扩展:

  • 一方面,扩展现有的,增加了8个新的寄存器;
  • 另一方面,寄存器宽度扩展了,但eax等名称还可以继续使用;
  • %ebp/%rbp不再是专用寄存器。

x86-32与x86-64下的swap对比

void swap (int *xp, int *yp)
{
	int t0 = *xp;
	int t1 = *yp;
	*xp = t1;
	*yp = t0;
}

32位下:

pushl %ebp
movl %esp, %ebp
pushl %ebx
// 以上为Set Up
movl 12(%ebp), %ecx
movl 8(%ebp), %edx
movl (%ecx), %eax
movl (%edx), %ebx
movl %eax, (%edx)
movl %ebx, (%ecx)
// 以上为Body
movl -4(%ebp), %ebx
movl %ebp, %esp
popl %ebp
ret
// Finsih

将两个地址取出,放在ecx与edx中,再将ecx与edx的地址的数取出,放在eax和ebx中,之后在eax和ebx中的数取出,放在edx与ecx中,即可返回。

而64位下:

movl (%rdi), %edx
movl (%rsi), %eax
movl %eax, (%rdi)
movl %edx, (%rsi)
retq

注意到64位中该功能非常简洁,可以注意到64位情况下传参是用过寄存器来传递的,而32位中寄存器不够用,参数用栈Stack来传递。

64位中,减少访问内存,使用寄存器来传参数。

但最多传6个参数,超过6个部分,依此从右往左压入栈中。

注意到上例中,被操作数仍然是32位,因此使用寄存器%eax和%edx,以及movl指令。

而x86-64下long int类型的swap如下。

void swap_l (long int *xp, long int *yp)
{
	long int t0 = *xp;
	long int t1 = *yp;
	*xp = t1;
	*yp = t0;
}

汇编代码如下:

swap_:
	movq (%rdi), %rdx
	movq (%rsi), %rax
	movq %rax, (%rdi)
	movq %rdx, (%rsi)
	retq

被操作数是64位:

  • 所以使用寄存器%rax与%rdx;
  • 以及movq指令,q表示4字

小结:x86指令的特点

  • 支持多种类型的指令操作数,包括立即数、寄存器和内存数据;
  • 算逻辑指令可以以内存数据作为操作数;
  • 支持多种内存地址计算模式,包括Rb + S * Ri + D,也可用于整数计算如leal指令;
  • 变成指令from 1 to 15 bytes。

扩展:x86汇编的格式

Intel/Microsoft Format

(-masm = intel, Intel 语法)

lea eax, [ecx + ecx * 2]
sub esp, 8
cmp dword ptr [ebp-8], 0
mov eax, dword ptr [eax * 4 + 100h]

AT&T Format

leal (%ecx + %ecx * 2), %eax
subl $8, %esp
cmpl $0, -8(%ebp)
movl $0x100 (, %eax, 4), %eax

Intel/Microsoft Differs from GAS

  • Operands listed in opposite order
    • mov Dest, Src
    • movl Src, Dest
  • Constants not preceded by ‘$’, Denote hex with ‘h’ at end
    • 100h
    • $0x100
  • Operand size indicated by operands rather than operator suffix
    • sub
    • subl
  • Addressing format shows effective address computation
    • [eax * 4 + 100]
    • $0x100 (, %eax, 4)

练习题

一个函数的原型为:

int decode2(int x, int y, int z);

汇编代码为:

movl 16(%ebp), %edx
subl 12(%ebp), %edx
movl %edx, %eax
sall $15, %eax
sarl $15, %eax
xorl 8(%ebp), %edx
imull %edx, %eax

另外,有:

x at %ebp + 8, y at %ebp + 12, z at %ebp + 16

参数x、y和z存放在存储器中相对于寄存器%ebp中地址偏移量为8、12和16的地方,代码将返回值放在寄存器%eax中,写出等价于汇编代码的decode2的c代码。

答案为:

int decode2(int x, int y, int z)
{
	int t1 = z - y;
	int t2 = (t1 << 15) >> 15;
	int t3 = x ^t1;
	int t4 = t2 * t1;
	return t4;

答疑:movl与leal中地址表达式

movl中出现地址表达式是用要去访问相应内存的,而leal中是用于算数的。

这篇关于【汇编语言与计算机系统结构笔记06】地址计算指令,lea / leal,x86-32与x86-64下的swap对比,汇编的格式对比(Intel/Microsoft Differs from GAS)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!