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;
}
複製代碼
相關文章
相關標籤/搜索