飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 2328|回复: 2

[Android] 【转载】ARM汇编基础教程:2.数据类型和寄存器

[复制链接]
  • TA的每日心情
    奋斗
    2020-11-23 19:47
  • 签到天数: 81 天

    [LV.6]常住居民II

    发表于 2018-9-14 14:42:21 | 显示全部楼层 |阅读模式
    看雪原文地址:https://bbs.pediy.com/thread-220535.htm
    原文链接:https://azeria-labs.com/arm-data-types-and-registers-part-2/
    翻译:ljcnaix

    数据类型
        这是ARM汇编基础教程的第二篇,包含了数据类型和寄存器的相关知识。

             data-types-1.png
        和高级语言一样,ARM汇编语言支持对不同数据类型的操作。我们可以load(或store)的数据类型包括signed/unsigned words,halfwords或者bytes。我们用“-h”或“-sh”后缀表示half words,用“-b”或“-sb”表示bytes,无后缀默认表示words。有符号和无符号数据类型之间的区别有:
        §  有符号数可以表示正直和负值,所以范围较小;
        §  无符号数只能表示正值,所以范围更大。
    下面是使用load和store指令操作不同类型数据的示例:

    ldr = Load Word
    ldrh = Load unsigned Half Word
    ldrsh = Load signed Half Word
    ldrb = Load unsigned Byte
    ldrsb = Load signed Bytes
      
    str = Store Word
    strh = Store unsigned Half Word
    strsh = Store signed Half Word
    strb = Store unsigned Byte
    strsb = Store signed Byte

    字节序
        在内存中有两种存储多字节数据的方式,大端序和小端序。这两种方式的差异是数据存储时的字节顺序不同。在以小端序存储数据的设备中(如x86),位权低(个位的位权比十位低)的字节存储在低地址(地址值小的地址)。在以大端序存储数据的设备上,位权高的字节存储在低地址。上一篇我们提到过,ARM架构在ARMv3之前是小端序的,在那之后,ARM处理器可以通过硬件配置在大小端之间切换。以ARMv6为例,指令是固定的以小端序存储的,而内存数据的读取方式可以通过控制程序状态寄存器CPSR的第9位实现在大端和小端之间切换(http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0301h/Cdfbbchb.html)。
    data-types-1.png

    ARM寄存器
        ARM处理器的寄存器个数与ARM指令集版本有关。根据ARM手册,除了基于ARMv6-M和ARMv7-M的处理器,其它的ARM处理器都有30个32 bit的通用寄存器(http://infocenter.arm.com/help/topic/com.arm.doc.dui0473c/Babdfiih.html)。前17个(原文是16个,我觉的可能是作者犯的off-by-one错误,如果理解有误,请高手指正)寄存器是在用户模式下可访问的,其它的寄存器只有在特定的运行模式下才可以访问(ARMv6-M和ARMv7-M除外,它们的架构有一些差异,有兴趣的话可以单独去学习)。在这篇教程中,我们将关注那些可以在任何运行模式下被访问的寄存器:r0~r15还有CPSR。这16个寄存器可以被分为两组:通用寄存器和专用寄存器。
             QQ截图20180914143049.png

        下面这张表将ARM的寄存器和x86寄存器做了一个简单类比:
             QQ截图20180914143106.png

        R0~R12(R12的使用要慎重):R0~R12是通用寄存器(R12已经不完全是了),它们可以在常规操作中使用,来存储临时变量或地址。习惯上,R0常在算数运算中作为累加器,或者存储函数的返回地址。R7常用于存储系统调用号。R11常作为栈帧指针来标记函数栈帧的边界。此外,ARM的函数调用约定规定,函数的前四个参数存储在寄存器r0~r3中。
        R13:R13是堆栈指针(SP,Stack Point)。它指向堆栈的顶部。堆栈是用来存储函数局部存储的一段内存,在函数返回时回收。堆栈指针通过减去我们要分配的空间大小,来分配堆栈上的空间。比如,我们要分配一个32 bit的空间,那么就令R13减4。
        R14:R14是链接寄存器(LR,Link Register)。当进行函数调用时,链接寄存器被更新为调用函数指令的下一条指令的地址。这样做可以使程序在执行完子函数之后得以返回父函数。
        R15:R15是程序计数器(PC,Program Counter)。在执行指令时,PC总是自动的增加,增加的大小等于正在执行指令的长度。这个长度在ARM架构下是固定的,ARM模式是4字节,Thumb模式是2字节。当执行分支指令时,PC被更新为目的地址。需要注意的是,由于RISC CPU流水线优化的原因,在执行期间,ARM模式下PC等于当前指令地址加8,Thumb模式下等于当前指令地址加4,也就是后移两条指令。这不同于x86的EIP寄存器,总是指向当前指令的下一条指令。
        下面我们通过调试器来看看PC的行为。我们使用下面的程序,先将PC保存在寄存器r0中,然后随意执行两条指令。让我们看看会发生什么:(vvizz:这里原文改动了,暂时以翻译为准)
    [Asm] 纯文本查看 复制代码
    .section .text
    .global _start
    .global _main
    
    _start:
        b _main
    _main:
        mov r0, pc
        mov r1, #2
        add r2, r1, r1
        bkpt

    我们使用gdb远程调试(作者在这里使用了gef增强脚本,可以在https://github.com/hugsy/gef找到,关于gdb远程调试环境搭建以及gef的配置使用,后面会单独写文章介绍)在_main标号处设置断点,然后执行:
    [AppleScript] 纯文本查看 复制代码
    gef➤ b _main
    Breakpoint 1 at 0x10058: file src/0x00pc/pc.s, line 9.
    gef➤ c
    Continuing.

    你应该会看到类似下面的输出:
    [AppleScript] 纯文本查看 复制代码
    $r0  : 0x00000000
    $r1  : 0x00000000
    $r2  : 0x00000000
    $r3  : 0x00000000
    $r4  : 0x00000000
    $r5  : 0x00000000
    $r6  : 0x00000000
    $r7  : 0x00000000
    $r8  : 0x00000000
    $r9  : 0x00000000
    $r10 : 0x00000000
    $r11 : 0x00000000
    $r12 : 0x00000000
    $sp  : 0xbefff740  →  0x00000001
    $lr  : 0x00000000
    $pc  : 0x00010058  →  <_main+0> mov r0,  pc
    $cpsr: [thumb fast interrupt overflow carry zero negative]
    ──────────────[ source:src/0x00pc/pc.s+9 ]────────────
          5     _start:
          6         b _main
          7    
          8     _main:
    ->    9         mov r0, pc
          10        mov r1, #2
          11        add r2, r1, r1
          12        bkpt0x8070 andeq r0, r0, r11

    我们可以看到,PC中存储的地址为(0x10058),也就是下一条即将执行的指令地址。我们使用si命令单步执行,下一条指令中PC将被存储到寄存器r0中,届时寄存器r0的值将会是0x10058,是这样吗?
    [AppleScript] 纯文本查看 复制代码
    $r0  : 0x00010060  →  <_main+8> add r2,  r1,  r1
    $r1  : 0x00000000
    $r2  : 0x00000000
    $r3  : 0x00000000
    $r4  : 0x00000000
    $r5  : 0x00000000
    $r6  : 0x00000000
    $r7  : 0x00000000
    $r8  : 0x00000000
    $r9  : 0x00000000
    $r10: 0x00000000
    $r11: 0x00000000
    $r12: 0x00000000
    $sp  : 0xbefff740  →  0x00000001
    $lr  : 0x00000000
    $pc  : 0x0001005c  →  <_main+4> mov r1,  #2
    $cpsr: [thumb fast interrupt overflow carry zero negative]
    ─────────────[ source:src/0x00pc/pc.s+10 ]────────────
          6     b _main
          7    
          8     _main:
          9         mov r0, pc
    ->    10        mov r1, #2
          11        add r2, r1, r1
          12        bkpt

    然而,事实并非如此。我们来看寄存器r0,我们期待的结果是调试器显示的PC的值0x10058,然而指令执行的结果表明,在指令执行时PC指向的是0x10060,相当于向后偏移两条指令的位置。产生这种差异的原因其实很简单,调试器显示的PC寄存器的值是经过处理的。下面我简单解释一下,当0x10058处的指令被执行时,PC寄存器已经指向了0x10058+0x8处的指令,这是由于CPU的流水线机制导致的。CPU取指令,解码指令和执行指令时使用的是不同的硬件部件,因此,这几个操作(实际的CPU可能更复杂,有更多的操作步骤)是可以并行执行的。因为RISC CPU的指令长度一定,所以CPU可以在解码指令之前就知道下一条指令的长度,从而在解码指令时取下一条指令,在执行指令时,对下一条指令进行解码,并取下下一条指令,这称为三级流水线。所以ARM当我们在执行0x10058处的指令时,PC已经指向0x10060处进行取指令操作了。这是硬件中真实发生的情况,而调试器为了令展示更有逻辑性,所以PC寄存器显示了当前执行指令的地址,当我们真实调试时不要受此影响。
    当前程序状态寄存器
    当你调试一个ARM二进制文件时,你会关心Flags。
    cpsr.png
    如果你查看gef显示的寄存器信息,会发现一个特殊的寄存器$cpsr(Current Program Status Register,当前程序状态寄存器)。你可以看到其中存储了thumb、fast、interrupt、overflow、carry、zero和negative这些Flags标志位。
    [AppleScript] 纯文本查看 复制代码
    $sp  : 0xbefff740  →  0x00000001
    $lr  : 0x00000000
    $pc  : 0x0001005c  →  <_main+4> mov r1,  #2
    $cpsr: [thumb fast interrupt overflow carry zero negative]
    thumb、fast、interrupt、overflow、carry、zero、negative这些标志位,由寄存器$cpsr中特定的比特位表示。当$cpsr寄存器的某个比特位被置位时,gef中的标志位会显示为粗体。N,Z,C和V位与x86上EFLAG寄存器中的SF,ZF,CF和OF位相同。这些标志位被用于实现汇编语言层变的条件分支和循环,我们将在第六篇:条件分支中对它们进行详细介绍。
    746171_gqpspejn4u16yne.png
    上图显示了32 bit寄存器$cpsr的布局,左边是高位,右边是低位。每个单元格(除了GE、MM和空白单元格)都是1 bit。这些1 bit的标志位定义了程序当前状态的各种属性。
    QQ截图20180914144003.png
    假设我们用cmp指令来比较1和2,结果将为负,Negative标志位被置1。因为cmp指令执行一次隐式的减法操作,1-2=-1。然而,如果我们比较2和1(和刚才相反),减法操作不借位,Carry标志位被置1。如果我们比较两个相同的数,比如2和2,那么2-2=0,在Carry标志位置1的同时,Zero标志位也被置1。可以使用如下示例程序检验上述分析。
    [AppleScript] 纯文本查看 复制代码
    .section .text
    .global _start
    .global _main
    _start:
        b _main
    
    _main:
        mov r1, #1
        mov r2, #2
        cmp r1, r2
        cmp r2, r1
        cmp r2, r2
        bkpt

    ;=============================================================

    vvizz:这一篇和原文有不小的出入,所以我只能当一个搬运工~~~哈哈,各位表哥勘误吧,有问题留言评论。

    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2024-3-22 23:57
  • 签到天数: 174 天

    [LV.7]常住居民III

    发表于 2019-2-28 16:55:35 | 显示全部楼层
    感谢楼主分享!学习啦!
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

  • TA的每日心情
    开心
    2019-6-4 08:49
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 2019-6-8 10:18:57 | 显示全部楼层
    感谢楼主分享!学习啦!
    PYG19周年生日快乐!
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表