yuanzhixiang's blog

yuanzhixiang

ARM64 main 汇编分析

34
2023-12-13

名词解释

名词

解释

sp

Stack Pointer(栈指针)的缩写。它通常指向程序的堆栈顶部,用于追踪函数调用中的栈帧的顶部位置。

fp

Frame Pointer(帧指针)的缩写。它用于指向当前函数栈帧的开始位置,这在遍历栈帧或进行函数调用时特别有用。

fp 是 x29 寄存器

lr

Link Register(链接寄存器)的缩写。在某些架构(如ARM)中,当一个函数被调用时,返回地址被存储在这个寄存器中。这样,一旦函数执行完毕,控制可以返回到调用它的代码位置。

lr 是 x30 寄存器

原始 Rust 代码

fn main() {
    let x = test_invoke_method(222, 666);
    println!("{}", x);
}

fn test_invoke_method(x: i64, y: i64) -> i64 {
    return x + y;
}

原始汇编

example`example::main:
    0x10075506c <+0>:   sub    sp, sp, #0x80
    0x100755070 <+4>:   stp    x29, x30, [sp, #0x70]
    0x100755074 <+8>:   add    x29, sp, #0x70
    0x100755078 <+12>:  mov    w8, #0xde
    0x10075507c <+16>:  mov    x0, x8
    0x100755080 <+20>:  mov    w8, #0x29a
    0x100755084 <+24>:  mov    x1, x8
    0x100755088 <+28>:  bl     0x100755100               ; example::test_invoke_method at main.rs:13
->  0x10075508c <+32>:  add    x9, sp, #0x8
    0x100755090 <+36>:  str    x0, [sp, #0x8]
    0x100755094 <+40>:  mov    x8, x9
    0x100755098 <+44>:  stur   x8, [x29, #-0x10]
    0x10075509c <+48>:  adrp   x8, 49
    0x1007550a0 <+52>:  add    x8, x8, #0xadc            ; core::fmt::num::imp::<impl core::fmt::Display for i64>::fmt at num.rs:283

example`example::test_invoke_method:
    0x100755100 <+0>:  sub    sp, sp, #0x30
    0x100755104 <+4>:  stp    x29, x30, [sp, #0x20]
    0x100755108 <+8>:  add    x29, sp, #0x20
    0x10075510c <+12>: str    x0, [sp, #0x10]
    0x100755110 <+16>: stur   x1, [x29, #-0x8]
->  0x100755114 <+20>: adds   x8, x0, x1
    0x100755118 <+24>: str    x8, [sp, #0x8]
    0x10075511c <+28>: cset   w8, vs
    0x100755120 <+32>: tbnz   w8, #0x0, 0x100755138     ; <+56> at main.rs:14:12
    0x100755124 <+36>: b      0x100755128               ; <+40> at main.rs
    0x100755128 <+40>: ldr    x0, [sp, #0x8]
    0x10075512c <+44>: ldp    x29, x30, [sp, #0x20]
    0x100755130 <+48>: add    sp, sp, #0x30
    0x100755134 <+52>: ret    
    0x100755138 <+56>: adrp   x0, 53
    0x10075513c <+60>: add    x0, x0, #0x100            ; str.0
    0x100755140 <+64>: mov    w8, #0x1c
    0x100755144 <+68>: mov    x1, x8
    0x100755148 <+72>: adrp   x2, 67
    0x10075514c <+76>: add    x2, x2, #0x238
    0x100755150 <+80>: bl     0x1007899e8               ; core::panicking::panic at panicking.rs:120

执行过程

  • 假设在执行 sub sp, sp, #0x80 之前的栈内存结构如下

        sp   = 2128            
        x29  = x29_value1      
        x30  = x30_value1      
                               
        2000                   
        2001                   
        2002                   
        2003                   
        ...                    
        2126                   
        2127                   
  sp -> 2128             
  • 执行 sub sp, sp, #0x80 后,栈内存变成下面的结构

        sp   = 2128            
        x29  = x29_value1      
        x30  = x30_value1      
                               
  sp -> 2000             
        2001                   
        2002                   
        2003                   
        ...                    
        2126                   
        2127                   
        2128                   
  • 执行 stp x29, x30, [sp, #0x70] 后,栈内存变成下面的结构

        sp   = 2000            
        x29  = x29_value1      
        x30  = x30_value1 

        sp -> 2000                     
        2001                           
        2002                           
        2003                           
        2004                           
        2005                           
        2006                           
        ...                            
        2111                           
        2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 执行 mov w8, #0xde 后,栈内存变成下面的结构,w 表示使用低 32 位

        sp   = 2000   
        x8   = 0x00000000_000000de         
        x29  = 2112      
        x30  = x30_value1 

  sp -> 2000                     
        2001                           
        2002                           
        2003                           
        2004                           
        2005                           
        2006                           
        ...                            
        2111                           
 x29 -> 2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 执行 mov x0, x8 后,栈内存变成下面的结构

        sp   = 2000  
        x0   = 0x00000000_000000de
        x8   = 0x00000000_000000de         
        x29  = 2112      
        x30  = x30_value1 

  sp -> 2000                     
        2001                           
        2002                           
        2003                           
        2004                           
        2005                           
        2006                           
        ...                            
        2111                           
 x29 -> 2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 执行 mov w8, #0x29a 后,栈内存变成下面的结构

        sp   = 2000  
        x0   = 0x00000000_000000de
        x8   = 0x00000000_0000029a         
        x29  = 2112      
        x30  = x30_value1 

  sp -> 2000                     
        2001                           
        2002                           
        2003                           
        2004                           
        2005                           
        2006                           
        ...                            
        2111                           
 x29 -> 2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 执行 mov x1, x8 后,栈内存变成下面的结构

        sp   = 2000  
        x0   = 0x00000000_000000de
        x1   = 0x00000000_0000029a
        x8   = 0x00000000_0000029a         
        x29  = 2112      
        x30  = x30_value1 

  sp -> 2000                     
        2001                           
        2002                           
        2003                           
        2004                           
        2005                           
        2006                           
        ...                            
        2111                           
 x29 -> 2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 执行 bl 0x100755100,会将这条指令的下一条指令的地址存入 x30,然后将 pc 改为 0x100755100,执行后的内存结构如下

        pc   = 0x100755100
        sp   = 2000  
        x0   = 0x00000000_000000de
        x1   = 0x00000000_0000029a
        x8   = 0x00000000_0000029a         
        x29  = 2112      
        x30  = 0x10075508c 

  sp -> 2000                     
        2001                           
        2002                           
        2003                           
        2004                           
        2005                           
        2006                           
        ...                            
        2111                           
 x29 -> 2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           

下面开始进入 test_invoke_method 方法

  • 执行 sub sp, sp, #0x30 = 48 后的栈内存变化

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_000000de = 222
        x1   = 0x00000000_0000029a = 666
        x8   = 0x00000000_0000029a = 666         
        x29  = 2112      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        2000                     
        2001                           
        2002                                                    
        ...                            
        2111                           
 x29 -> 2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 执行 stp x29, x30, [sp, #0x20 = 32] 后的栈变化

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_000000de
        x1   = 0x00000000_0000029a
        x8   = 0x00000000_0000029a         
        x29  = 2112      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        1983
        1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x100031100
        2000                     
        2001                           
        2002                                                    
        ...                            
        2111                           
 x29 -> 2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 执行 add x29, sp, #0x20 = 32 后的栈变化

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_000000de
        x1   = 0x00000000_0000029a
        x8   = 0x00000000_0000029a         
        x29  = 1984      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        1983
 x29 -> 1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x100031100
        2000                     
        2001                           
        2002                                                    
        ...                            
        2111                           
        2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 当执行 str x0, [sp, #0x10 = 16] 后的变化

    • str x0, [sp, #0x10]

      • str 是“store register”的缩写,用于将一个寄存器的内容存储到内存中。

      • 这条指令将寄存器 x0 的内容存储到栈指针 sp 指向的地址加上偏移量 0x10(16字节)的位置。

      • 简单来说,这是将寄存器 x0 的值保存到栈上一个特定的位置。

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_000000de = 222
        x1   = 0x00000000_0000029a = 666
        x8   = 0x00000000_0000029a = 666         
        x29  = 1984      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        1967
        1968 |
        1969 |
        1970 |
        1971 |
        1972 |
        1973 |
        1974 |
        1975 | = 0x00000000_000000de = 222
        1976
        ...
        1983
 x29 -> 1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x100031100
        2000                     
        2001                           
        2002                                                    
        ...                            
        2111                           
        2112 |                         
        2113 |                         
        2114 |                         
        2115 |                         
        2116 |                         
        2117 |                         
        2118 |                         
        2119 | = x29_value1            
                                       
        2120 |                         
        2121 |                         
        2122 |                         
        2123 |                         
        2124 |                         
        2125 |                         
        2126 |                         
        2127 | = x30_value1            
                                       
        2128                           
  • 当执行 stur x1, [x29, #-0x8 = 8] 后的变化

    • stur x1, [x29, #-0x8]

      • stur 也是一种存储指令,但通常用于更复杂的地址计算。这里使用的是帧指针(x29)。

      • 这条指令将寄存器 x1 的内容存储到帧指针 x29 指向的地址减去偏移量 0x8(8字节)的位置。

      • 这通常用于在函数调用的栈帧中保存局部变量或参数。

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_000000de = 222
        x1   = 0x00000000_0000029a = 666
        x8   = 0x00000000_0000029a = 666         
        x29  = 1984      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        1967
        1968 |
        1969 |
        1970 |
        1971 |
        1972 |
        1973 |
        1974 |
        1975 | = 0x00000000_000000de = 222

        1976 |
        1977 |
        1978 |
        1979 |
        1980 |
        1981 |
        1982 |
        1983 | = 0x00000000_0000029a = 666

 x29 -> 1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x10075508c
        2000                     
  • 执行 adds x8, x0, x1 的后的变化

    • adds x8, x0, x1

      • adds 是“add and set flags”的缩写,用于执行加法操作,并更新处理器的状态标志(比如零标志、负标志等)。

      • 这条指令将寄存器 x0x1 的内容相加,结果存储在寄存器 x8 中。

      • 状态标志的更新允许随后的指令根据加法操作的结果(例如是否产生溢出、结果是否为零等)进行决策。

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_000000de = 222
        x1   = 0x00000000_0000029a = 666
        x8   = 0x00000000_00000378 = 888         
        x29  = 1984      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        1967
        1968 |
        1969 |
        1970 |
        1971 |
        1972 |
        1973 |
        1974 |
        1975 | = 0x00000000_000000de = 222

        1976 |
        1977 |
        1978 |
        1979 |
        1980 |
        1981 |
        1982 |
        1983 | = 0x00000000_0000029a = 666

 x29 -> 1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x10075508c
        2000                     
  • 当执行 str x8, [sp, #0x8 = 8] 后的内存变化

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_000000de = 222
        x1   = 0x00000000_0000029a = 666
        x8   = 0x00000000_00000378 = 888         
        x29  = 1984      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        1958
        1959
        1960 |
        1961 |
        1962 |
        1963 |
        1964 |
        1965 | 
        1966 |
        1967 | = 0x00000000_00000378 = 888

        1968 |
        1969 |
        1970 |
        1971 |
        1972 |
        1973 |
        1974 |
        1975 | = 0x00000000_000000de = 222

        1976 |
        1977 |
        1978 |
        1979 |
        1980 |
        1981 |
        1982 |
        1983 | = 0x00000000_0000029a = 666

 x29 -> 1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x10075508c
        2000                     

  • 接下来的三个指令这里给出解释,简单的说就是如果加溢出了会跳到 panic,没溢出则到 0x100755128,我们接着看 0x100755128 的指令

    • cset w8, vs

      • cset 是“conditional set”的缩写,用于基于条件标志设置寄存器的值。

      • w8 是目标寄存器。

      • vs 是“overflow set”(溢出设置)的条件代码,指示如果溢出标志被设置,则将 w8 设置为1,否则设置为0。

    • tbnz w8, #0x0, 0x100755138

      • tbnz 是“test bit and branch if non-zero”的缩写,用于测试寄存器中的特定位,并在该位非零时执行分支操作。

      • 这条指令测试 w8 寄存器的第0位。

      • 如果测试的位非零,跳转到地址 0x100755138 执行,这是一个条件跳转。

    • b 0x100755128

      • b 是“branch”的缩写,用于无条件跳转到指定的地址。

      • 这条指令将程序的执行流跳转到地址 0x100755128

  • 接下来执行 ldr x0, [sp, #0x8]

    • ldr x0, [sp, #0x8]

      • ldr 是“load register”的缩写,用于从内存加载数据到寄存器。

      • 这条指令从栈指针 sp 加上偏移量 0x8 的地址加载数据到寄存器 x0。这通常用于恢复函数调用前保存的某个值或准备返回值。

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_00000378 = 888
        x1   = 0x00000000_0000029a = 666
        x8   = 0x00000000_00000378 = 888         
        x29  = 1984      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        1958
        1959
        1960 |
        1961 |
        1962 |
        1963 |
        1964 |
        1965 | 
        1966 |
        1967 | = 0x00000000_00000378 = 888

        1968 |
        1969 |
        1970 |
        1971 |
        1972 |
        1973 |
        1974 |
        1975 | = 0x00000000_000000de = 222

        1976 |
        1977 |
        1978 |
        1979 |
        1980 |
        1981 |
        1982 |
        1983 | = 0x00000000_0000029a = 666

 x29 -> 1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x10075508c
        2000                     
  • 接下来执行 ldp x29, x30, [sp, #0x20 = 32],恢复 x29 栈底指针和 x30 返回地址

    • ldp x29, x30, [sp, #0x20]

      • ldp 是“load pair”的缩写,用于同时从内存加载两个寄存器的值。

      • 这条指令从栈中恢复 x29(帧指针)和 x30(链接寄存器)的原始值。[sp, #0x20] 表示从栈指针 sp 加上偏移量 0x20 的地址开始加载数据。

        pc   = 0x100755100
        sp   = 1952  
        x0   = 0x00000000_00000378 = 888
        x1   = 0x00000000_0000029a = 666
        x8   = 0x00000000_00000378 = 888         
        x29  = 2112      
        x30  = 0x10075508c 

        1950
        1951
  sp -> 1952
        ...
        1958
        1959
        1960 |
        1961 |
        1962 |
        1963 |
        1964 |
        1965 | 
        1966 |
        1967 | = 0x00000000_00000378 = 888

        1968 |
        1969 |
        1970 |
        1971 |
        1972 |
        1973 |
        1974 |
        1975 | = 0x00000000_000000de = 222

        1976 |
        1977 |
        1978 |
        1979 |
        1980 |
        1981 |
        1982 |
        1983 | = 0x00000000_0000029a = 666

        1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x10075508c
        2000            
        ...
 x29 -> 2112        
  • 接下来执行 add sp, sp, #0x30 = 48

    • add 是“addition”的缩写,用于执行加法操作。

    • 这条指令把栈指针 sp 增加 0x30,实际上是收缩栈空间,清理当前函数的栈帧。

        pc   = 0x100755100
        sp   = 2000  
        x0   = 0x00000000_00000378 = 888
        x1   = 0x00000000_0000029a = 666
        x8   = 0x00000000_00000378 = 888         
        x29  = 2112      
        x30  = 0x10075508c 

        1950
        1951
        1952
        ...
        1958
        1959
        1960 |
        1961 |
        1962 |
        1963 |
        1964 |
        1965 | 
        1966 |
        1967 | = 0x00000000_00000378 = 888

        1968 |
        1969 |
        1970 |
        1971 |
        1972 |
        1973 |
        1974 |
        1975 | = 0x00000000_000000de = 222

        1976 |
        1977 |
        1978 |
        1979 |
        1980 |
        1981 |
        1982 |
        1983 | = 0x00000000_0000029a = 666

        1984 |
        1985 |
        1986 |
        1987 |
        1988 |
        1989 |
        1990 |
        1991 | = 2112
        
        1992 |
        1993 |
        1994 |
        1995 |
        1996 |
        1997 |
        1998 |
        1999 | = 0x10075508c
  sp -> 2000               
        ...
 x29 -> 2112                     
  • 接下来执行 ret

    • ret 是“return”的缩写,标志着函数的结束。

    • 这条指令使得程序返回到 x30 寄存器中存储的返回地址,即调用此函数的下一条指令。

  • 后续这里的返回值存储在了 x0 的寄存器里,返回后使用 x0 就能拿到返回值了

结论

  1. sp 指向栈顶,当需要访问内部的变量的时候可能会通过 sp + offset 的方式去访问

  2. x29 指向栈底,当需要访问内部的变量的时候可能会通过 x29 - offset 的方式去访问

  3. 传递函数变量的时候会放在 x0, x1 这样的寄存器里面

  4. 返回值最终也放回了 x0 这样的寄存器