AT&T汇编

x86架构汇编指令通常有两种格式:express

  • Intel汇编sass

    • DOS、Windows,包括咱们以前了解的8086处理器
    • Windwos派系:VC编译器
  • AT&T汇编bash

    • Linux、Unix、Mac OS、iOS模拟器
    • Unix派系:GCC编译器

作为iOS开发工程师,接触到的汇编有两种:架构

  • AT&T汇编->iOS模拟器
  • ARM汇编->iOS真机

寄存器

16个经常使用寄存器

  • %rax, %rbx, %rcx, %rdx, %rsi, %rdi, %rbp, %rsp
  • %r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15

寄存器的具体用途函数

  • %rax作为函数的返回值(同8086汇编的ax)
  • %rsp指向栈顶(同8086汇编的ss:sp)
  • %rdi、%rsi、%rdx、%rcx、%r八、%r9 等寄存器用于存放函数参数

如上图,rax寄存器还能够当eax寄存器来使用,很相似于于8086汇编中ax能够拆为ah和al同样,当寄存器中存入的值只须要32位就能够存储是,直接使用eax就能够,Xcode也是这样作的。post

基本语法

这里主要基于8086汇编的区别来讲明,全部要了解AT&T汇编,首先应该会汇编语言有基本的理解,若是彻底不懂,须要先看从零入门8086汇编,了解基本的汇编基础。测试

寄存器表示

相比8086汇编,寄存器前面加了 % 。优化

; 8086
ax

; AT&T
%eax
复制代码

单位

8086汇编ui

  • byte:字节,8bit
  • word:字,16bit

AT&T汇编spa

  • b:b=byte,8bit
  • s:s=short,16bit integer;32bit floating point
  • l:l=long,32bit integer;64bit floating point
  • q:q=quad,64bit
  • t:t=ten btyes,80bit floating point

; 8086 byte、word
mov byte ah, 4ch
mov word ax, 4ch

; AT&T 
movl %eax, %edx
movb $0x10, %al
复制代码

语法

相比8086汇编,被操做的寄存器放在后面。

; 8086: 将ax寄存器的值存入dx
mov dx, ax

; AT&T: 将eax寄存器的 值存入edx
movl %eax, %edx
复制代码

常数、当即数前面加 $ 。

; 8086: 将3赋值给ax 
mov ax, 3
; 8086: 将0x10赋值给ax
mov ax, 10H

; AT&T: 将3赋值给eax
movl $3, %eax
; AT&T: 将0x10赋值给eax
movl $0x10, %eax
复制代码

lldb经常使用指令

读取寄存器的值

register read/格式

  • x 16进制
  • f 浮点
  • d 十进制
register read/x
复制代码

修改寄存器的值

register write 寄存器名称 数值

register write $rax 0
复制代码

读取内存中的值

x/数量-格式-字节大小 内存地址

字节大小:

  • b - byte 1字节
  • h - half word 2字节
  • w - word 4字节
  • g - giant word 8字节
x/3xw 0x0000010
复制代码

修改内存中的值

memory write 内存地址 数值

memory wirte 0x0000010 10
复制代码

expression 表达式

能够简写 expr

expression $rax
expression $rax = 1
复制代码

po 表达式

po/x $rax
po (int)$rax
复制代码

Xcode反汇编

建立一个Xcode的命令行项目,编写如下简单的代码:

#import <Foundation/Foundation.h>

int sum(int a, int b) {
    return a + b;
}

int main(int argc, const char * argv[]) {
    int c = sum(1, 2);
    
    printf("%d\n", c);
    return 0;
}
复制代码

利用Xcode查看对应的汇编代码方法以下:

添加断点并运行:

Xcode选择Debug-Debug Workflow-Always Show Disassembly:

上如代码对应的AT&T汇编以下:

// 调用main函数
test001`main:
    // rbp寄存器入栈,用于恢复rbp,对应8086汇编中的push bp
    0x100000f40 <+0>:  pushq  %rbp
    // 将rsp赋值给rbp,对应8086汇编中的mov bp, sp
    0x100000f41 <+1>:  movq   %rsp, %rbp
    // 移动栈顶扩大栈容量用于存放临时变量,至关于8086汇编中的sub sp 0x20
    0x100000f44 <+4>:  subq   $0x20, %rsp
    
    0x100000f48 <+8>:  movl   $0x0, -0x4(%rbp)
    0x100000f4f <+15>: movl   %edi, -0x8(%rbp)
    0x100000f52 <+18>: movq   %rsi, -0x10(%rbp)
    
    // 利用edi、esi寄存器位函数传递参数
    // 将1存入edi寄存器
    0x100000f56 <+22>: movl   $0x1, %edi
    // 将2存入esi寄存器
    0x100000f5b <+27>: movl   $0x2, %esi
    // 调用sum函数
    0x100000f60 <+32>: callq  0x100000f20               ; sum 
    
// 调用sum函数
test001`sum:
    // rbp寄存器入栈,用于恢复rbp,对应8086汇编中的push bp
    0x100000f20 <+0>:  pushq  %rbp
    // 将rsp赋值给rbp,对应8086汇编中的mov bp, sp
    0x100000f21 <+1>:  movq   %rsp, %rbp
    
    // 将edi寄存器的值 1 存入栈
    0x100000f24 <+4>:  movl   %edi, -0x4(%rbp)
    // 将esi寄存器的值 2 存入栈
    0x100000f27 <+7>:  movl   %esi, -0x8(%rbp)
    
    // 从栈中将 1 存入 esi
->  0x100000f2a <+10>: movl   -0x4(%rbp), %esi
    // 从栈中将 2 加到 esi:esi = 1 + 2 = 3
    0x100000f2d <+13>: addl   -0x8(%rbp), %esi
    // 将 esi的值 3 存入 eax
    0x100000f30 <+16>: movl   %esi, %eax
    // 恢复 rbp 
    0x100000f32 <+18>: popq   %rbp
    // 返回函数
    0x100000f33 <+19>: retq   
复制代码

上面能够看到,大致上和8086汇编调用函数的原理是同样的,不一样的地方就是关于栈的操做有一些不一样,可是基本流程咱们是看得懂的。

当sum函数调用完成的时候,经过 register read/d 读取当前寄存器中的值:

能够发现rax=3,验证了AT&T也是使用rax寄存器来返回函数的结果的,相似于8086使用ax寄存器返回函数结果。

sum的汇编代码是经过在sum函数中插入断点运行获得的,不是在main函数的中插入断点获得的。

能够发现sum函数中,没有对rsp进行移动操做,这是由于编译器更加智能,它会检查函数中有没有调用其它函数,若是没有调用,就不会存在当前函数的栈空间由于没有移动rsp在调用另外一个函数被覆盖的状况,因此就不须要sub 0x20 %rsp之类的操做。这种没有调用其它函数的函数叫作叶子函数。

Xcode编译器release模式的优化

使用Xcode编译项目都会知道,Xcode分为release和debug两种编译模式,并且都知道一点,就是release模式编译的程序会比debug的程序体积要小、运行速度更快,可是根本的缘由在哪里呢?

下面找到Xcode-Build Settings-Apple Clang-Code Generation-Optimization Level,观察默认的选项:

测试代码:

#import <Foundation/Foundation.h>

int sum(int a, int b) {
    return a + b;
}

int main(int argc, const char * argv[]) {
    int a = 1;
    int b = 2;
    int c = sum(a, b);
    
    printf("%d\n", c);
    return 0;
}

复制代码

在debug模式运行以下代码对应的反汇编指令:

和上面刚刚分析的反汇编基本一致的流程,包括rbp的保存与恢复、函数栈空间的管理、寄存器传递参数、调用sum方法等。

如今使用release模型运行项目,对应的反汇编指令:

第一感受明显汇编指令少了不少,并且找不到寄存器传递参数、已经对sum函数的调用指令了,并且能够发现这句代码:movl &0x3, %esi,这里的3明显就是咱们的加法计算的结果,没有经过函数计算就直接获得了。

编译器在release模式会对代码进行优化,将不少不须要函数调用就可以获得结果的运算所有直接转成计算后的汇编代码,因此运算更快,并且对应汇编指令的减小,程序体积同时也是减小。

Xcode中使用汇编混编

内联汇编

以下代码,在高级语言中插入汇编的方式实现计算 result = sum1 + sum2:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    int num1 = 1;
    int num2 = 6;
    int result;
    
    __asm__(
            // rax = num2 + num1
            "addq %%rbx, %%rax"
            // 将rax寄存器计算的结果赋值给result
            : "=a"(result)
            // 将num1给rbx, 将num2给rax
            : "a"(num1), "b"(num2)
            );
    
    printf("%d\n", result);
    
    return 0;
}
复制代码

外联汇编

在头文件my_math.h中定义两个C语言函数,以下图:

在my_math.s中使用汇编实现这两个函数,以下图:

引用my_math.h使用定义的sum和minus函数:

#import <Foundation/Foundation.h>
#import "my_math.h"

int main(int argc, const char * argv[]) {
    int a = sum(1, 2);
    printf("%d\n", a);
    
    int b = minus(9, 3);
    printf("%d\n", b);
    return 0;
}
复制代码
相关文章
相关标签/搜索