ARM汇编初识

作者: 烟影很美 | 来源:发表于2018-04-27 15:47 被阅读185次

    iOS架构及设备

    架构 设备 宽度
    armv6 iPhone, iPhone2, iPhone3G, 第一代、第二代iPod Touch 32
    armv7 iPhone3GS, iPhone4, iPhone4S, iPad, iPad2, iPad3(The New iPad), iPad mini, iPod Touch 3G, iPod Touch4 32
    armv7s iPhone5, iPhone5C, iPad4(iPad With Retina Display) 32
    arm64 iPhone5s以及之后版本 64

    ARM64寄存器

    寄存器 描述
    x0-x30 通用寄存器, 如果有需要可以当做32bit(w0-w30)使用
    FP(x29) 保存栈帧地址(栈底指针)
    LR(30) 通常称x30为程序链接寄存器, 保存子程序结束后需要执行的下一条指定的地址
    SP 保存栈指针, 使用SP/WSP来进行对SP寄存器的访问
    PC 程序计数器, 俗称PC指针, 总是指向即将执行的下一条指令, 在arm64中, 软件是不能改写PC寄存器的
    SPSR 状态寄存器
    • x0-x7: 用于传递函数参数, 超出的参数将入栈. 假如在函数funcA中调用函数funcB, 传给funcB的参数超出8个的将保存在函数funcA函数栈的栈顶.
    • x0 - x30 是31个通用整形寄存器。每个寄存器可以存取一个64位大小的数。 当使用 r0 - r30/x0 - x30 访问时,它就是一个64位的数。当使用 w0 - w30 访问时,访问的是这些寄存器的低32位.

    寄存器概念补充

    数据地址寄存器

    数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。

    ARM64中
    • 64位: X0-X30, XZR(零寄存器)
    • 32位: W0-W30, WZR(零寄存器)

    注意:
    之前讲解8086汇编中有一种特殊的寄存器段寄存器:CS,DS,SS,ES四个寄存器来保存这些段的基地址,这个属于Intel架构CPU中.在ARM中并没有

    浮点和向量寄存器

    因为浮点数的存储以及其运算的特殊性,CPU中专门提供浮点数寄存器来处理浮点数

    • 浮点寄存器 64位: D0 - D31 32位: S0 - S31

    SP和FP寄存器

    • sp寄存器在任意时刻都会保存我们栈顶的地址
    • fp寄存器也称为x29寄存器属于通用寄存器, 但是在某些时刻我们利用它保存栈底地址

    注意: ARM64开始, 取消32位的LDM,STM,PUSH,PHP指令, 取而代之的是ldr/ldp,str/stp
    ARM64里面对栈的操作是16字节对齐的

    关于内存读写指令

    注意: 读/写数据都是往高地址读/写

    str(store register)

    将数据从寄存器存到栈中, 同时操作两个寄存器时, 使用stp

    str x0,[sp,#0x10]
    

    ldr(load register)

    将数据从栈读到寄存器中, 同时操作两个寄存器时, 使用ldp

    ldr x0,[sp,#0x10]
    

    堆栈操作练习

    将32个字节空间作为这段程序的栈空间,然后利用栈x0,x1的值进行交换

    sub sp,sp,#0x20                  ;拉伸栈空间
    stp x0,x1,[sp,#0x10]             ;将x0,x1存放在sp+0x10为地址的栈中
    ldp x1,x0,[sp,#0x10]             ;从sp+0x10为地址的栈中读取数据到x1,x0
    add sp,sp,#0x20                  ;平栈
    

    bl和ret指令

    bl 标号

    • 将下一条指令的地址放入lr(x30)寄存器 (l)
    • 跳转执行标号为地址所指向的指令 (b)

    ret

    • 默认使用lr(x30)寄存器的值, 通过底层指令提示CPU此处作为下一条指令的地址!

    ARM64平台的特色指令, 它面向硬件做了优化处理

    x30寄存器(lr)

    • x30寄存器存放的是函数的返回地址. 当ret指令执行时, 会寻找x30寄存器保存的地址所指向的内存保存的值为下一条指令

    注意: 在函数嵌套调用的时候. 需要将x30入栈!

    ldp    x29, x30, [sp, #0x10]
    add    sp, sp, #0x20             ; =0x20 
    ret
    

    函数的参数和返回值

    • ARM64下, 函数的参数是存放在x0-x7(w0-w7)这8个寄存器里面的. 如果超过8个参数, 就会入栈.
    • 函数的返回值放在x0寄存器里面

    NZCV状态寄存器

    • CPU内部的寄存器中, 有一种特殊的寄存器(对于不同处理器, 个数和结构可能不同). 这种寄存器在RAM中, 被称为CPSR(Current program status register)寄存器
    • CPSR和其他寄存器不同, 其他寄存器是用来存放数据的, 都是整个寄存器具有一个含义. 而CPSR寄存器是按位起作用的, 它的每一位都有特定含义.
    • CPSR的低八位(包括I、F、T、M[4:0]) 称为控制位, 程序无法修改, 除非CPU运行于特权模式下.
    • N、Z、C、V均为条件标志位. 他们的内容可被算数或逻辑运算改变, 并且可以决定某条指令是否被执行(if的汇编实现..)

    注: CPSR是32位寄存器. add/sub等指令不能影响CPSR寄存器, 如需要保存计算状态, 使用adds/subs等.

    N(Negative)

    CPSR的第31位(从零开始数...)是N, 符号标志位. 它记录相关指令执行后, 其结果是否为负. 如果结果为负, N=1. 反之N=0.

    Z(Zero)

    CPSR的第30位(...你们懂得)是Z, 0标志位,. 它记录相关指令执行后, 其结果是否为0, 如果是0, Z=1. 反之Z=0.

    C(Carry)

    CPSR的第29位(...)时C, 进位标志位. 一般情况下, 进行无符号数运算.
    加法运算: 当运算产生进位时, C=1, 反之C=0.
    减法运算(包括CMP): 当运算产生借位时, C=0, 反之C=1.

    • 可理解为, 进位将进位的1保存到了C. 借位将C保存的1借走. 而实际上, 借位之前C为保存的不一定是1. 进位也类似(未仔细求证).
    进位: 
    mov w0,#0xaaaaaaaa;0xa 的二进制是 1010
    adds w0,w0,w0; 执行后 相当于 1010 << 1 进位1(无符号溢出) 所以C标记 为 1
    adds w0,w0,w0; 执行后 相当于 0101 << 1 进位0(无符号没溢出) 所以C标记 为 0
    adds w0,w0,w0; 重复上面操作
    adds w0,w0,w0
    
    借位:
    mov w0,#0x0
    subs w0,w0,#0xff ;
    subs w0,w0,#0xff
    subs w0,w0,#0xff
    

    V(Overflow) 溢出标志

    CPSR的第28位时V, 溢出标志位. 在进行有符号运算时, 如果超过了机器所能标识的范围, 视为溢出.

    实际是无符号运算影响了符号位或者产生了进位或借位

    以下情况都会造成溢出
    • 正数 + 正数 = 负数
    • 负数 + 负数 = 正数

    正数 + 负数 不可能溢出.

    指令条件码(逻辑对照)

    竟然跟NZCV对应的值不匹配, 虽然理解, 但还是好难受.
    比如不相等, 就不能确定N位的值. 猜测

    编码 助记符 描述 标记
    0000 EQ (equal) 相等 Z=1
    0001 NE(Not Equal) 不相等 Z=0
    0010 CS/(Carry Set/High or Same) 无符号数大于/等于 C = 1
    0011 CC/LO(Carry Clear/LOwer) 无符号数小于 C = 0
    0100 MI(MInus) 负数 N=1
    0101 PL(PLus) 非负数 N=0
    0110 VS(oVerflow set) 上溢出 V=1
    0111 VC(oVerflow clear) 没有上溢出 V=0
    1000 HI(HIgh) 无符号数大于 C=1 且 Z=0
    1001 LS(Lower or Same) 无符号数小于/等于 C=0 且 Z=1
    1010 GE(Greater or Equal) 有符号数大于或等于 N=V
    1011 LT(Less Than) 有符号数小于 N!=V
    1100 GT(Greater Than) 有符号数大于 Z=0,N=V
    1101 LE(Less or Equal) 有符号数小于等于 Z=1,N!=V
    1110 AL 无条件执行 任何
    1111 NV 从不执行 任何

    全局变量/静态变量/常量

    • 全局变量、静态变量保存在静态区
    • 常量的话, 如果是字符串, 通常保存在常量区. 较大(二进制表示需要较大宽度)的数字类型保存在常量区. 如果是较小数字, 可以通过立即数的形式来保存.
    • 较大的数字(ARM64中二进制表示大于0xffff, 即长度超过2字节), 因为ARM汇编命令是等长的(四字节), 比较大的数不能包含在命令中, ARM通过偏移的方式, 使用adrp指令读取静态区.
    • 同理字符串也不能保存在代码区, 通过偏移的方式, 同样使用adrp指令读取.

    下面代码是读取一个常量/静态变量到x8寄存器

    adrp x0,1        ; 将当前PC寄存器指向的地址低12位清零, 加上 (`1`左移12位), 将结果保存到`x0`寄存器
    add x0,#0xf28    ; `x0`保存的值加上偏移0xf28再次保存到`x0`寄存器.
    ldr x8, [x0]     ; 将x0存储的值作为地址, 读取地址指向的数据, 保存到x8寄存器
    

    上面代码中, 最后x8寄存器保存的就是一个静态变量或者字符串常量的指针.

    待续

    // 知识有限, 随时修改..

    // ## #### ######

    相关文章

      网友评论

        本文标题:ARM汇编初识

        本文链接:https://www.haomeiwen.com/subject/azuzkftx.html