深入理解计算机系统(v3) 第三章、程序的机器级表示(Linux)

14 篇文章 0 订阅
4 篇文章 0 订阅
  • 程序调试:gcc -Og -o p p1.c p2.c
    • 反汇编 objdump -d p
  • 寄存器
    • 函数参数第1到第6个参数:%rdi %rsi %rdx %rcx %r8 %r9
    • 返回值 %rax
    • 栈指针 %rsp
    • 被调用者保存 %rbx %rbp %r12 %r13 %r14 %r15
    • 调用者保存:所有其他寄存器,除了栈指针%rsp,都分类为调用者保存寄存器
  • 操作数格式
    • $lmm 立即数寻址
    • %rax 寄存器寻址
    • lmm 内存绝对寻址 (lmm)
    • (%rax) 间接寻址
    • lmm(%rax) 基址+偏移量寻址 (%rax+lmm)
    • (%rax,%rsi) 变址寻址 (%rax+%rsi)
    • (,%rsi,s) 比例变址寻址 (s*%rsi)
    • (%rax,%rsi,s) 比例变址寻址 (%rax+s*%rsi)
  • 数据传输指令:mov,字节b,字w,双字l,四字q
    • 将一个值从一个内存位置复制到另一个内存位置需要两条指令——第一条指令将源值加载到寄存器中,第二条将该寄存器值写入目的地址。
  • 间接引用指针就是将指针放在一个寄存器中,然后在内存引用中使用这个寄存器。
    • 压栈 pushq:将栈指针减8,然后将值写到新的栈顶地址
    • 出栈 popq:从栈顶读出数据,然后将栈指针加8
  • 算法指令
    • inc 加1
    • dec 减1
    • neg 取负
    • not 取补
    • add sub imul div 加减乘除
    • xor 异或
    • or 或
    • and 与
  • 加载有效地址(leaq):从内存读取数据到寄存器,但是不引用内存。它的第一个操作数看上去是内存引用,但是该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数
long scale(long x,long y,long z){
    long t = x + 4 *y + 12 *z;
    return t;
}
// 汇编代码 x rdi ; y rsi; z rdx
scale:
	leaq	(%rdi,%rsi,4), %rax ; rax=x + 4*y
	leaq	(%rdx,%rdx,2), %rdx ; rdx=3*z
	leaq	(%rax,%rdx,4), %rax ; rax=x+4*y + 4*(3*z)
  • 移位操作:sal(shl) 左移;sar 算术右移(填符号位),shr 逻辑右移(填0)
    • 移位量可以是一个立即数,也可以放在单字节寄存器%cl中(只允许这个%cl寄存器)
    • 移位操作对w位长的数据值进行操作,移位量由%cl寄存器的低m位决定,这里2m=w。当%cl=0xFF,对于salb,23=8,所以会左移7位,salw会移15位,sall会移31位,而salq会移63位
  • 乘法:
    • imulq %rdx, %rsi;有符号乘法生成64位,高位溢出丢掉
    • 全乘法:要求一个参数必须在寄存器%rax中,而另一个作为指令的源操作数给出。然后乘积存放在寄存器%rdx(高64位)和%rax(低64位中)。
      • 无符号全乘法:mulq
      • 有符号全乘法:imulq
// mul.c
// cc mul.c -O2 -S
#include <inttypes.h>
typedef unsigned __int128 uint128_t;
typedef  __int128 int128_t;
void store_uprod(uint128_t *dest,uint64_t x,uint64_t y){
        *dest = x * (uint128_t) y;
}
void store_uprod2(int128_t *dest,int64_t x,int64_t y){
        *dest = x *  (int128_t)y;
}

store_uprod:
	movq	%rsi, %rax
	mulq	%rdx
	movq	%rax, (%rdi)  ;存储低8字节在目的地 小端序机器
	movq	%rdx, 8(%rdi) ;存储高8字节在目的地+8
	ret
store_uprod2:
	movq	%rsi, %rax
	imulq	%rdx
	movq	%rax, (%rdi)
	movq	%rdx, 8(%rdi)
	ret
  • 除法:
    • 有符号除法指令idivq将寄存器%rdx(高64位)和%rax(低64位)中的128位作为被除数,而除数作为指令的操作数给出,指令将商存储在寄存器%rax,将余数存储在寄存器%rdx中。
    • 无符号除法指令idivq将寄存器%rdx(高64位,置为0)和%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;
}
void remdiv2(unsigned long x,unsigned long y,unsigned long *qp,unsigned long *rp){
	long q=x/y;
	long r=x%y;
	*qp = q;
	*rp = r;
}
// x rdi,y rsi,qp rdx,rp rcx

remdiv:
	movq	%rdi, %rax    ; x低8字节存储在%rax
	movq	%rdx, %r8     ; qp地址存储在r8
	cqto                  ; 符号扩展%rdx为全0或全1
	idivq	%rsi          ; 除法 y
	movq	%rax, (%r8)   ; 商存储在*qp
	movq	%rdx, (%rcx)  ; 余数存储在*rp
	ret
	
remdiv2:
	movq	%rdx, %r8
	movq	%rdi, %rax
	xorl	%edx, %edx ; 将%rdx置为0
	divq	%rsi
	movq	%rax, (%r8)
	movq	%rdx, (%rcx)
	ret
控制
  • 条件码
    • CF:进位标志。最近的操作使最高位产生了进位。用来检查无符号操作的溢出
    • ZF:零标志。最近的操作得出的结果为0
    • SF:符号标志。最近的操作得到的结果为负数。
    • OF:溢出标志。最近的操作导致一个补码溢出——正溢出或负溢出
  • CMP指令根据两个操作数之差来设置条件码,不会修改目的操作数。CMP S1,S2 ;S2-S1
  • TEST指令根据两个操作数AND来设置条件码,不会修改目的操作数。TEST S1,S2 ; S1&S2
    • testq %rax,%rax 用来检查%rax是负数、零,还是正数
  • 根据条件码的某种组合,将一个字节设置为0或者1.
    • sete 相等或零设置为1
    • setne 不相等或非零设置为1
    • sets 负数设置为0 setns
    • setg setge setl setle:有符号大于 大于等于 小于 小于等于
    • seta setae setb setbe:超过(无符号>) 超过或等于 低于 低于或等于
int comp(int a,int b){
  return a<b;
}

// a in %rdi ,b in %rsi
comp:
	cmpl	%esi, %edi 
	setl	%al
	movzbl	%al, %eax
	
int test(long a){
  return a>=0;
}

// a in %rdi
comp:
	testq	%rdi, %rdi 
	setge	%al
	movzbl	%al, %eax
  • 跳转指令:jmp Label 使用控制的条件转移。但条件满足的时,程序沿着一条执行路径执行,而当条件不满足时,就走另一条路径。
    • 无条件跳转:jmp
      • 直接跳转:跳转目标是作为指令的一部分编码的。jmp Label
      • 间接跳转:跳转目标是从寄存器或内存位置中读出的。jmp *Operand
    • 有条件跳转:只能是直接跳转
      • 相等与不相等:je(jz) jne(jnz)
      • 负数与非负数:js jns
      • 有符号比较:jg(jnle) jge(jnl) jl(jnge) jle(jng)
      • 无符号比较:ja(jnbe) jae(jnb) jb(jnae) jbe(jna)
    • rep(repz):空操作,可以忽略。因为在AMD中,当ret指令通过跳转指令到达时,处理器不能正确的处理它,所以加入rep空操作。
    • jmp 相对寻址,以当前jmp指令的下一条指令地址+jmp后的数字,如下objdump -d中有详细展示
// jmp.c
long lt_cnt = 0;
long ge_cnt = 0;
long absdiff_se(long x,long y){
	long result;
	if (x<y){
		lt_cnt++;
		result = x-y;
	}else{
		ge_cnt++;
		result = x-y;
	}
	return result;
}

// cc jmp.c -Og -S
// x in %rdi ,y in %rsi
// cat jmp.s
absdiff_se:
	cmpq	%rsi, %rdi        ; compare x:y
	jge	.L2                   ; x>=y 跳转 .L2
	addq	$1, lt_cnt(%rip)  ; lt_cnt++
	movq	%rsi, %rax        ; rax = y
	subq	%rdi, %rax        ; rax = y-x
	ret
.L2:
	addq	$1, ge_cnt(%rip)  ; ge_cnt++
	movq	%rdi, %rax        ; rax = x
	subq	%rsi, %rax        ; rax = x-y
	ret
	
// cc jmp.c -Og -c
// objdump -d jmp.o
0000000000000000 <absdiff_se>:
   0:	48 89 f8             	mov    %rdi,%rax
   3:	48 39 f7             	cmp    %rsi,%rdi
   6:	7d 0c                	jge    14 <absdiff_se+0x14> ; 0x14 = 0x08 + 0x0c
   8:	48 83 05 00 00 00 00 	addq   $0x1,0x0(%rip)        # 10 <absdiff_se+0x10>
   f:	01
  10:	48 29 f0             	sub    %rsi,%rax
  13:	c3                   	retq
  14:	48 83 05 00 00 00 00 	addq   $0x1,0x0(%rip)        # 1c <absdiff_se+0x1c>
  1b:	01
  1c:	48 29 f0             	sub    %rsi,%rax
  1f:	c3                   	retq
  • 用条件传送来实现条件分支。 这种方法计算一个条件操作的两种结果,然后再根据条件是否满足从中选取一个。
    • 指令 cmov S,D
      • 相等(或零)与不相等(或非零):comve(comvz) comvne(comvnz)
      • 负数与非负数:comvs comvns
      • 有符号比较:comvg(comvnle) comvge(comvnl) comvl(comvnge) comvle(comvng)
      • 无符号比较:comva(comvnbe) comvae(comvnb) comvb(comvnae) comvbe(comvna)
    • 由于当前的CPU通过使用流水线来获得高性能。当机器遇到条件跳转时,只有当分支条件求值完成之后,才能决定分支往哪边走。处理器采用非常精密的分支预测逻辑来猜测每条跳转指令是否会执行。只要它猜测的比较可靠,指令流水线中就会充满指令。另一方面,错误预测一个跳转,要求处理器丢掉它为该跳转指令后所有指令已做的工作,然后开始用从正确位置处起始的指令去填充流水线。这样一个错误预判会招致很严重的惩罚,浪费15~30个时钟周期,导致程序性能严重下降。
    • 而条件传送,先各分支求值,没有跳转,性能稳定。但是当分支计算复杂时,程序性能可能会下降。
long absdiff(long a,long b){
  if (a>b)
    return a-b;
  else
    return b-a;
}

// cc absdiff.c -O1 -S
// a in %rdi, b in %rsi
absdiff:
	movq	%rdi, %rdx
	subq	%rsi, %rdx ; rdx = a-b
	movq	%rsi, %rax
	subq	%rdi, %rax ; rax = b-a
	cmpq	%rsi, %rdi ; compare a:b
	cmovg	%rdx, %rax ; if a>b, rax = rdx(a-b)
	ret
循环
  • do-while
long fact_do(long n){
	long result = 1;
	do {
		result *=n;
		n = n-1;
	}while(n>1);
	return result;
}
long fact_do_goto(long n){
	long result = 1;
	loop:
	    result *=n;
		n = n-1;
		if (n>1) goto loop;
	return result;
}
// n in %rdi
fact_do:
	movl	$1, %eax
.L2:
	imulq	%rdi, %rax
	subq	$1, %rdi
	cmpq	$1, %rdi ; compare x:1
	jg	.L2           ; if > , goto .L2
	ret
  • while
long fact_do_while(long n){
	long result = 1;
	while(n>1) {
		result *=n;
		n = n-1;
	};
	return result;
}
long fact_do_while_goto(long n){
	long result = 1;
	if(n<=1) goto done;
	loop:
	    result *=n;
		n = n-1;
		if (n>1) goto loop;
	done:
	  return result;
}
fact_do_while:
	movl	$1, %eax
	cmpq	$1, %rdi
	jle	.L4
.L3:
	imulq	%rdi, %rax
	subq	$1, %rdi
	cmpq	$1, %rdi
	jne	.L3
	ret
.L4:
	ret
  • 判断二进制数字中1的奇偶个数
// 返回0表示有偶数个1,返回1奇数个1
long func_a(unsigned long x){
	long val=0;
	while(x){
		val ^= x;
		x >>= 1;
    }
    return val&1;
}
long func_a_goto(unsigned long x){
	long val=0;
	goto test;
	loop:
		val ^= x;
		x >>= 1;
    test:
      if (x!=0) goto loop;
    return val&1;
}
func_a:
	movl	$0, %eax
	jmp	.L7
.L8:
	xorq	%rdi, %rax
	shrq	%rdi
.L7:
	testq	%rdi, %rdi
	jne	.L8
	andl	$1, %eax
	ret
  • for
long fact_for(long n){
	long i;
	long result = 1;
	for(i=2;i<=n;i++){
		result *= i;
	}
	return result;
}
long fact_for_goto(long n){
	long i = 2;
	long result = 1;
	goto test;
	loop:
	  result *= i;
	  i++;
	test:
	  if (i<=n) goto loop;
	return result;
}

// n in %rdi
fact_for:
	movl	$1, %edx
	movl	$2, %eax
	jmp	.L10
.L11:
	imulq	%rax, %rdx
	addq	$1, %rax
.L10:
	cmpq	%rdi, %rax
	jle	.L11
	movq	%rdx, %rax
	ret
过程
  • P调用Q,共同机制
    • 传递控制:在进入过程Q的时候,程序计数器必须被设置为Q的代码的起始地址,然后在返回时,要把程序计数器设置为P中调用Q后面那条指令的地址
    • 传递数据:P必须能够向Q提供一个或多个参数,Q必须能够向P返回一个值
    • 分配和释放内存:在开始时,Q可能需要为局部变量分配空间,而在返回前,又必须释放这些存储空间
  • 栈帧结构
  • 当x86-64过程需要的存储空间超出寄存器能够存放的大小时,就会在栈上分配空间。这个部分称为过程的栈帧(stack frame)
    • 参数大于6个的部分参数放在栈中
    • 栈上的局部存储
      • 寄存器不足够存放所有的本地数据
      • 对一个局部变量使用地址运算符’&',因此必须能够为它产生一个地址。(leaq 18(%rsp), %r9 ; 参数 &x3)
      • 某些局部变量是数组或结构,因此必须能够通过数组或结构引用被访问到。
void proc(long a1,long *a1p,
		int a2,int *a2p,
		short a3,short *a3p,
		char a4, char *a4p
	 ){
	*a1p += a1;
	*a2p += a2;
	*a3p += a3;
	*a4p += a4;
}

long call_proc(){
	long x1 = 1;
	int x2 = 2;
	short x3 = 3;
	char x4 = 4;
	proc(x1,&x1,x2,&x2,x3,&x3,x4,&x4);
	return (x1+x2)*(x3-x4);
}

// rdi rsi rdx rcx r8 r9
proc:
	movq	16(%rsp), %rax    ; 获取 *a4p
	addq	%rdi, (%rsi)      ; *a1p += a1
	addl	%edx, (%rcx)      ; *a2p += a2
	addw	%r8w, (%r9)       ; *a3p += a3
	movl	8(%rsp), %edx     ; 获取 a4
	addb	%dl, (%rax)       ; *a4p += a4;
	ret
	
call_proc:
	subq	$16, %rsp           ; 分配参数栈空间
	movq	$1, 8(%rsp)         ; 局部变量x1
	movl	$2, 4(%rsp)         ; 局部变量x2
	movw	$3, 2(%rsp)         ; 局部变量x3
	movb	$4, 1(%rsp)         ; 局部变量x4
	leaq	1(%rsp), %rax       ; &x4
	pushq	%rax                ; 参数 &x4
	pushq	$4                  ; 参数 x4
	leaq	18(%rsp), %r9       ; 参数 &x3
	movl	$3, %r8d            ; 参数 x3
	leaq	20(%rsp), %rcx      ; 参数 &x2
	movl	$2, %edx            ; 参数 x2
	leaq	24(%rsp), %rsi      ; 参数 &x1
	movl	$1, %edi            ; 参数 x1
	call	proc
	movslq	20(%rsp), %rax      ; x2 int->long
	addq	24(%rsp), %rax      ; rax=x1+x2
	movswl	18(%rsp), %edx      ; x3 short->int
	movsbl	17(%rsp), %ecx      ; x4 char->int
	subl	%ecx, %edx          ; edx=x3-x4
	movslq	%edx, %rdx          ; edx int->long
	imulq	%rdx, %rax          ; rax = (x1+x2)*(x3-x4)
	addq	$32, %rsp           ; 释放栈空间
	ret
  • 寄存器中的局部存储空间:当寄存器不足于保存临时变量时,使用栈保存。
    • 根据惯例,寄存器%rbx、%rbp和%r12~%r15被划分为被调用者保存寄存器。当过程P调用过程Q时,Q必须保存这些寄存器的值,保证它们的值在Q返回到P时与Q被调用时是一样的。
    • 所有其他寄存器,除了栈指针%rsp,都分类为调用者保存寄存器。这就意味着任何函数都可以修改它们。也就是说,过程P在某个此类寄存器中有局部数据,然后调用过程Q,因为Q可以随意修改这个寄存器,所以在调用之前首先保存好这个数据时P(调用者)的责任。
  • 递归
rfact:
	cmpq	$1, %rdi
	jg	.L8
	movl	$1, %eax
	ret
.L8:
	pushq	%rbx ; save rbx
	movq	%rdi, %rbx ; store n
	leaq	-1(%rdi), %rdi ; n=n-1
	call	rfact
	imulq	%rbx, %rax ; result = n*rfact(n-1)
	popq	%rbx ; restore rbx
	ret
数组
  • 嵌套数组:行主序,对于int P[M][N]类型的数组,P[i][j] 地址为 P+(i*N+j)*4
#define N 16
typedef int fix_matrix[N][N];
int fix_prod_ele(fix_matrix A,fix_matrix B,long i,long k){
	long j;
	int result =0 ;

	for(j=0;i<N;j++){
		result += A[i][j] + B[j][k];
	}
	return result;
}

// A in %rdi ,B in %rsi,i in %rdx ,k in %rcx
fix_prod_ele:
	movq	%rdi, %r9           ; r9=A
	movq	%rsi, %r10          ; r10=B
	movl	$0, %r8d            ; result = 0
	movl	$0, %eax            ; j = 0
	jmp	.L3
.L4:
	movq	%rdx, %rdi          ; rdi = i
	salq	$6, %rdi            ; rdi = 64*i
	addq	%r9, %rdi           ; rdi = A+64*i
	movq	%rax, %rsi          ; rsi = j
	salq	$6, %rsi            ; rsi = j*64
	addq	%r10, %rsi          ; rsi = B+j*64
	movl	(%rsi,%rcx,4), %esi ; M[B+j*64+4*k]【B[j][k]】
	addl	(%rdi,%rax,4), %esi ; esi = M[A+64*i+4*j]+M[B+j*64+4*k]
	addl	%esi, %r8d          ; r8d += esi
	addq	$1, %rax            ; rax ++
.L3:
	cmpq	$15, %rdx           ; compare i:n
	jle	.L4                     ; if <= ,goto .L4
	movl	%r8d, %eax          ; return result
	ret
  • 变长数组:对于int A[m][n],A[i][j]地址为i * n+j,必须用乘法指令对i伸缩n倍(i * j),而不能使用一系列的移位和加法
异质的数据结构
  • 结构struct的所有组成部分都存放在内存中一段连续的区域内,而指向结构的指针就是结构第一个字节的地址
  • 联合
  • 数据对齐,通常为2、4、8
GDB调试器
  • 使用
    • 开始和停止
      • quit 退出GDB
      • run 运行程序(在此给出命令行参数)
      • kill 停止程序
    • 短点
      • break multscore:在函数multscore入口处设置断点
      • break * 0x400540:在地址0x400540处设置断点
      • delete 1:删除断点1
      • delete:删除所有断点
    • 执行
      • stepi:执行一条指令
      • stepi 4:执行4条指令
      • nexti:类似于stepi,但以函数调用为单位
      • continue:继续执行
      • finish:运行到当前函数返回
    • 检查代码
      • disas:反汇编当前函数
      • disas multscore:反汇编函数multscore
      • disas 0x400544:反汇编位于地址0x400544附近的函数
      • disas 0x400540,0x40054d 反汇编指定地址范围内的代码
      • print /x $rip:以16进制输出程序计数器的值
    • 检查数据
      • print $rax:以十进制输出%rax的内容
      • print /t $rax:以二进制输出
      • print 0x100:以十进制输出0x100
      • print /x ($rsp+8):以16进制输出%rsp的内容加上8
      • print *(long *)($rsp+8):输出位于地址%rsp+8处的长整数
      • x/2g 0x7fffffffe818:检查从地址0x7fffffffe818开始的双(8字节)字
      • x/20b multscore:检查函数multscore的前20个字节
    • 有用的信息
      • info frame:有关当前栈帧的信息
      • info registers:所有寄存器的值
      • help
  • 缓冲区溢出:8字节buf,上面是%rbp值,所以当输入小于等于15个字符时,程序不会崩溃
[root@localhost csapp]# ./a.out
123456
123456
[root@localhost csapp]# ./a.out
123456789012345
123456789012345
[root@localhost csapp]# ./a.out
1234567890123456
1234567890123456
Segmentation fault (core dumped)

// echo.c
#include <stdio.h>

void echo(){
	char buf[8];
	gets(buf);
	puts(buf);
}
int main(){

	echo();
	return 0;
}

// cc echo.c
// objdump -d a.out
...
0000000000401136 <echo>:
  401136:	55                   	push   %rbp
  401137:	48 89 e5             	mov    %rsp,%rbp
  40113a:	48 83 ec 10          	sub    $0x10,%rsp
  40113e:	48 8d 45 f8          	lea    -0x8(%rbp),%rax ; buf
  401142:	48 89 c7             	mov    %rax,%rdi
  401145:	b8 00 00 00 00       	mov    $0x0,%eax
  40114a:	e8 f1 fe ff ff       	callq  401040 <gets@plt>
  40114f:	48 8d 45 f8          	lea    -0x8(%rbp),%rax
  401153:	48 89 c7             	mov    %rax,%rdi
  401156:	e8 d5 fe ff ff       	callq  401030 <puts@plt>
  40115b:	90                   	nop
  40115c:	c9                   	leaveq
  40115d:	c3                   	retq
...
  • 对抗缓冲区溢出
    • 栈随机化:使栈的位置在程序每次运行都有变化。为了在系统中插入攻击代码,攻击者既要插入代码,也要插入指向这段代码的指针,产生这个指针需要知道这个字符串放置的栈地址,栈随机化避免了此类攻击。
      • 可以在攻击代码前加入一段nop指令,运用蛮力攻击,只要地址落在这一段代码上即可实现攻击。
      • nop指令(no operation):是程序计数器加一,没有其他效果
    • 栈破坏检测:程序每次随机产生一个值,当出现缓冲区溢出时,修改了哨兵值,函数运行完会检测此值是否改变。
    • 限制可执行代码区域:限制栈只能读写,不能代码执行
// 栈破坏检测
// gcc echo.c -fstack-protector
[root@localhost csapp]# ./a.out
123456789
123456789
*** stack smashing detected ***: terminated
Aborted (core dumped)

0000000000401146 <echo>:
  401146:	55                   	push   %rbp
  401147:	48 89 e5             	mov    %rsp,%rbp
  40114a:	48 83 ec 10          	sub    $0x10,%rsp
  40114e:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax ; 栈保护
  401155:	00 00
  401157:	48 89 45 f8          	mov    %rax,-0x8(%rbp) ; 金丝雀canary值
  40115b:	31 c0                	xor    %eax,%eax
  40115d:	48 8d 45 f0          	lea    -0x10(%rbp),%rax ; buf
  401161:	48 89 c7             	mov    %rax,%rdi
  401164:	b8 00 00 00 00       	mov    $0x0,%eax
  401169:	e8 e2 fe ff ff       	callq  401050 <gets@plt>
  40116e:	48 8d 45 f0          	lea    -0x10(%rbp),%rax
  401172:	48 89 c7             	mov    %rax,%rdi
  401175:	e8 b6 fe ff ff       	callq  401030 <puts@plt>
  40117a:	90                   	nop
  40117b:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
  40117f:	64 48 2b 04 25 28 00 	sub    %fs:0x28,%rax ; 查看是否栈溢出
  401186:	00 00
  401188:	74 05                	je     40118f <echo+0x49>
  40118a:	e8 b1 fe ff ff       	callq  401040 <__stack_chk_fail@plt>
  40118f:	c9                   	leaveq
  401190:	c3                   	retq
  • 支持变长栈帧:在较早版本的x86代码中,每个函数调用都使用了栈指针(frame pointer)。而现在,只有栈帧长可变的情况才使用
    • pushq %rbp; movq %rsp,%rbp
long vframe(long n,long idx,long *q){
	long i;
	long *p[n];
	p[0]=&i;
	for(i=1;i<n;i++)
		p[i]=q;
	return *p[idx];
}

vframe:
	pushq	%rbp
	movq	%rsp, %rbp      ; 栈指针
	subq	$16, %rsp
	leaq	15(,%rdi,8), %rax
	andq	$-16, %rax      ; rax 取8n最靠近的16倍数
	subq	%rax, %rsp      ; 分配给p
	leaq	7(%rsp), %rcx 
	movq	%rcx, %rax
	shrq	$3, %rax        ; 除8
	andq	$-8, %rcx       ; rcx为rsp向上取8的倍数,即p的起始地址
	leaq	-8(%rbp), %r8   ; &i
	movq	%r8, 0(,%rax,8) ; p[0]=&i 
	movq	$1, -8(%rbp)
	jmp	.L2
.L3:
	movq	%rdx, (%rcx,%rax,8) ; 循环赋值
	addq	$1, -8(%rbp)
.L2:
	movq	-8(%rbp), %rax
	cmpq	%rdi, %rax
	jl	.L3
	movq	(%rcx,%rsi,8), %rax
	movq	(%rax), %rax
	leave
	ret
浮点代码
  • 媒体寄存器
    • AVX浮点体系结构的16个YMM寄存器(256位):%ymm0~%ymm15
    • SSE XMM寄存器(128位):%xmm0~%xmm15
  • 浮点传送指令:不做任何转换的传送指令。X:XMM寄存器
    • 传送单精度:vmovss M32,X
    • 传送单精度:vmovss X,M32
    • 传送双精度:vmovsd M64,X
    • 传送双精度:vmovsd X,M64
    • 传送对其的封装好的单精度:vmovaps X,X
    • 传送对其的封装好的双精度:vmovapd X,X
float float_mov(float v1,float *src,float *dst){
	float v2 = *src;
	*dst = v1;
	return v2;
}

// v1 in %xmm0, src in %rdi, dst in %rsi
float_mov:
	movaps	%xmm0, %xmm1  ; copy v1
	movss	(%rdi), %xmm0 ; Read v2 from src
	movss	%xmm1, (%rsi) ; Write v1 to dst
	ret                   ; return v2 in %xmm0
  • 浮点数和整数转换即不同浮点格式之间进行转换
    • vcvttss2si X/M32,R32:单精度->整数
    • vcvttsd2si X/M64,R32:双精度->整数
    • vcvttss2siq X/M32,R64:单精度->四字整数
    • vcvttsd2siq X/M64,R64:双精度->四字整数
    • vcvtsi2ss M32/R32,X,X:整数->单精度
    • vcvtsi2sd M32/R32,X,X:四字整数->双精度
    • vcvtsi2ssq M64/R64,X,X:整数->单精度
    • vcvtsi2sdq M64/R64,X,X:四字整数->双精度
    • 对于三个操作数的指令,第一个操作数读自内存或一个通用寄存器,第二个操作数数只会影响结果的高位字节,一般忽略,第三个操作数为目的寄存器
  • 过程中的浮点代码
    • XMM寄存器%xmm0~%xmm7最多可以传递8个浮点参数。多于的使用栈
    • 函数使用%xmm0来返回浮点数
    • 所有的XMM寄存器都是调用者保存。被调用者可以不用保存就覆盖这些寄存器中的任何一个。
    • 当函数包含指针、整数、浮点数混合的参数时,指针和整数通过通用寄存器传递
  • 浮点运算操作
    • vaddss vaddsd:浮点加 单精度 双精度
    • vsubss vsubsd:浮点减
    • vmulss vmulsd:乘
    • vdivss vdivsd:除
    • vmaxss vmaxsd:最大
    • vminss vminsd:最小
    • sqrtss sqrtsd:平方根
double funct(double a,float x,double b,int i){
	return a*x-b/i;
}

funct:
	cvtss2sd	%xmm1, %xmm1  ; x -> double
	mulsd	%xmm0, %xmm1      ; a*x
	pxor	%xmm0, %xmm0      ; 0
	cvtsi2sdl	%edi, %xmm0   ; int -> double
	divsd	%xmm0, %xmm2      ; b/i
	subsd	%xmm2, %xmm1      ; a*x - b/i
	movapd	%xmm1, %xmm0 
	ret
  • 定义和使用浮点常数:AVX浮点数不能以立即数作为操作数。编译器必须为所有的常量分配和初始化存储空间,然后代码再把这些值从内存读入。
double cel2fahr(double temp){
	return 1.8*temp+32.0;
}

cel2fahr:
	mulsd	.LC0(%rip), %xmm0
	addsd	.LC1(%rip), %xmm0
	ret
.LC0:
	.long	-858993459  ; 1.8的低4位
	.long	1073532108  ; 1.8的高四位
.LC1:
	.long	0
	.long	1077936128
  • 位级操作:
    • vxorps xorpd:异或 单精度 双精度
    • vandps andpd
  • 浮点比较
    • vucomiss:单精度比较
    • vucomisd:双精度比较
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值