ARM汇编(第二部分)ARM数据类型和寄存器 不念不忘少年蓝@ 2023-01-17 09:18 126阅读 0赞 ### 文章目录 ### * 数据类型 * * 字节序 * ARM寄存器 * * 当前程序状态寄存器 https://azeria-labs.com/arm-data-types-and-registers-part-2/ # 数据类型 # 这是《 ARM Assembly Basics》教程系列的第二部分,介绍了数据类型和寄存器。 与高级语言类似,ARM支持对不同数据类型的操作。我们可以加载(或存储)的数据类型可以是有符号的和无符号的字,半字或字节。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center] 这些数据类型的扩展名是:-h或-sh表示半字,-b或-sb表示字节,而字无扩展名。有符号和无符号数据类型之间的区别是: * 带符号的数据类型可以同时包含正值和负值,因此范围较小。 * 无符号数据类型可以容纳较大的正值(包括“零”),但不能容纳负值,因此范围更广。 以下是如何将这些数据类型与“加载和存储”指令一起使用的一些示例: 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 <table> <thead> <tr> <th align="left">指令</th> <th align="left">全称</th> <th align="left">描述</th> </tr> </thead> <tbody> <tr> <td align="left">ldr</td> <td align="left">load word</td> <td align="left">加载一个全字</td> </tr> <tr> <td align="left">ldrh</td> <td align="left">load unsigned half word</td> <td align="left">加载无符号半字</td> </tr> <tr> <td align="left">ldrsh</td> <td align="left">load signed half word</td> <td align="left">加载有符号半字</td> </tr> <tr> <td align="left">ldrb</td> <td align="left">load unsigned byte</td> <td align="left">加载无符号字节</td> </tr> <tr> <td align="left">ldrsb</td> <td align="left">load signed byte</td> <td align="left">加载有符号字节</td> </tr> <tr> <td align="left">str</td> <td align="left">store word</td> <td align="left">存储字</td> </tr> <tr> <td align="left">strh</td> <td align="left">store word</td> <td align="left">存储无符号半字</td> </tr> <tr> <td align="left">strsh</td> <td align="left">store word</td> <td align="left">存储有符号半字</td> </tr> <tr> <td align="left">strb</td> <td align="left">store unsigned byte</td> <td align="left">存储无符号字节</td> </tr> <tr> <td align="left">strsb</td> <td align="left">store signed byte</td> <td align="left">存储有符号字节</td> </tr> </tbody> </table> ## 字节序 ## 有两种查看内存中字节的基本方法:小字节序(LE)或大字节序(BE)。区别在于对象的每个字节存储在内存中的字节顺序。在像Intel x86这样的little-endian机器上,最低有效字节存储在最低地址(最接近零的地址)上。在big-endian计算机上,最高有效字节存储在最低地址。ARM体系结构在版本3之前是低字节序的,因为那时它是双字节序的,这意味着它具有允许可切换字节序的设置。例如,在ARMv6上,指令是固定的小端形式,并且数据访问可以是小端形式,也可以是大端形式,这由程序状态寄存器(CPSR)的位9(E位)控制。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 1] # ARM寄存器 # 寄存器的数量取决于ARM版本。根据《 ARM参考手册》,除了基于ARMv6-M和基于ARMv7-M的处理器外,还有30个通用32位寄存器。前16个寄存器可在用户级模式下访问,其他寄存器可在特权软件执行中使用(ARMv6-M和ARMv7-M除外)。在本教程系列中,我们将使用可在任何特权模式下访问的寄存器:r0-15。这16个寄存器可以分为两组:通用寄存器和专用寄存器。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 2] 下表仅仅是一个快速窥探到ARM寄存器怎么可能 涉及到那些在英特尔处理器。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 3] * R0-R12:可以在常规操作期间用于存储临时值,指针(到存储器的位置)等。R0例如在算术操作期间可以称为累加器,或者用于存储先前调用的函数的结果。R7在处理系统调用时非常有用,因为它存储系统调用号,而R11帮助我们跟踪用作帧指针的堆栈边界(稍后将介绍)。此外,ARM上的函数调用约定指定函数的前四个参数存储在寄存器r0-r3中。 * R13:SP(堆栈指针)。堆栈指针指向堆栈的顶部。堆栈是用于特定于函数存储的内存区域,当函数返回时将对其进行回收。因此,通过从堆栈指针中减去我们要分配的值(以字节为单位),堆栈指针可用于在堆栈上分配空间。换句话说,如果我们要分配一个32位值,则从堆栈指针中减去4。 * R14:LR(链接寄存器)。进行功能调用时,链接寄存器将使用一个内存地址进行更新,该内存地址引用了从其开始该功能的下一条指令。这样做可以使程序返回到“父”函数,该子函数在“子”函数完成后启动“子”函数调用。 * R15:PC(程序计数器)。程序计数器自动增加执行指令的大小。在ARM状态下,此大小始终为4个字节,在THUMB模式下,此大小始终为2个字节。当执行分支指令时,PC保留目标地址。在执行期间,PC在ARM状态下存储当前指令的地址加8(两个ARM指令),在Thumb(v1)状态下存储当前指令的地址加4(两个Thumb指令)。这与x86不同,x86中PC始终指向要执行的下一条指令。 让我们看一下PC在调试器中的行为。我们使用以下程序将pc的地址存储到r0中,并包括两个随机指令。让我们看看发生了什么。 .section .text .global _start _start: mov r0,pc mov r1,#2 加r2,r1,r1 bkpt 在GDB中,我们在\_start处设置一个断点并运行它: gef> br _start Breakpoint 1 at 0x8054 gef> run 这是我们首先看到的输出的屏幕截图: $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 0xbefff7e0 $lr 0x00000000 $pc 0x00008054 $cpsr 0x00000010 0x8054 <_start> mov r0, pc <- $pc 0x8058 <_start+4> mov r0, #2 0x805c <_start+8> add r1, r0, r0 0x8060 <_start+12> bkpt 0x0000 0x8064 andeq r1, r0, r1, asr #10 0x8068 cmnvs r5, r0, lsl #2 0x806c tsteq r0, r2, ror #18 0x8070 andeq r0, r0, r11 0x8074 tsteq r8, r6, lsl #6 我们可以看到PC保留了将要执行的下一条指令(mov r0,pc)的地址(0x8054)。现在,让我们执行下一条指令,之后R0应该保持PC的地址(0x8054),对吗? $r0 0x0000805c $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 0xbefff7e0 $lr 0x00000000 $pc 0x00008058 $cpsr 0x00000010 0x8058 <_start+4> mov r0, #2 <- $pc 0x805c <_start+8> add r1, r0, r0 0x8060 <_start+12> bkpt 0x0000 0x8064 andeq r1, r0, r1, asr #10 0x8068 cmnvs r5, r0, lsl #2 0x806c tsteq r0, r2, ror #18 0x8070 andeq r0, r0, r11 0x8074 tsteq r8, r6, lsl #6 0x8078 adfcssp f0, f0, #4.0 …正确的?错误的。查看R0中的地址。尽管我们期望R0包含先前读取的PC值(0x8054),但它却保留了比我们先前读取的PC(0x805c)提前两个指令的值。从这个例子中您可以看到,当我们直接阅读PC时,它遵循PC指向下一条指令的定义。但是在调试时,PC会在当前PC值(0x8054 + 8 = 0x805C)之前指向两个指令。这是因为较旧的ARM处理器始终会在当前执行的指令之前获取两条指令。ARM保留此定义的原因是为了确保与早期处理器兼容。 ## 当前程序状态寄存器 ## 使用gdb调试ARM二进制文件时,您会看到称为Flags的信息: ![在这里插入图片描述][20210428164042584.png_pic_center] 寄存器$ cpsr显示当前程序状态寄存器(CPSR)的值,在该状态下,您可以看到标志标志,快速,中断,溢出,进位,零和负。这些标志代表CPSR寄存器中的某些位,并根据CPSR的值进行设置,并在激活时变为粗体。N,Z,C和V位与x86上EFLAG寄存器中的SF,ZF,CF和OF位相同。这些位用于支持汇编级别的条件和循环中的条件执行。我们将介绍在第6部分:条件执行和分支中使用的条件代码。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 4] 上图显示了32位寄存器(CPSR)的布局,其中左侧(<-)保留了最高有效位,而右侧(->)保留了最低有效位。每个单个单元(GE和M部分以及空白单元除外)的大小为一位。这些一位定义了程序当前状态的各种属性。 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 5]假设我们将使用CMP指令比较数字1和2。结果将为“负”,因为1-2 = -1。当我们比较两个相等的数字(例如2与2)时,会设置Z(零)标志,因为2 – 2 =0。请记住,不会修改与CMP指令一起使用的寄存器,只会修改CPSR基于将这些寄存器相互比较的结果。 这是在GDB(已安装GEF)中的样子:在此示例中,我们比较了寄存器r1和r0,其中r1 = 4和r0 =2。这是执行cmp r1,r0操作后的标志外观: ![在这里插入图片描述][20210428164327279.png_pic_center] 设置进位标志是因为我们使用cmp r1,r0将4与2(4 – 2)进行比较。相反,如果我们使用cmp r0,r1将较小的数字(2)与较大的数字(4)进行比较,则会设置负标志(N )。 这是ARM信息中心的摘录: APSR包含以下ALU状态标志: N –当运算结果为负时设置。 Z –运算结果为零时设置。 C –当操作导致进位时置位。 V –在操作引起oVerflow时设置。 进位发生: 如果相加的结果大于或等于2 32 如果减法的结果是正数或零 作为移动或逻辑指令中的直列式桶形移位器操作的结果。 如果加,减或比较的结果大于或等于2 31或小于Δ2 2 31,则会发生溢出。 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center]: /images/20221021/8bab6de351f0463fa88b2822afc268c3.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 1]: /images/20221021/0ed3f44339f5404580b2312cefa7505a.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 2]: /images/20221021/5b0fa706b26d4bfc9c78d8e3a7930546.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 3]: /images/20221021/69f76bb2bd6444b8a56f654773685a20.png [20210428164042584.png_pic_center]: /images/20221021/f4a6bad4e1154cd4aad8d199be90ea76.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 4]: /images/20221021/64314618440142feb61967df9b59d35d.png [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI4ODc3MTI1_size_16_color_FFFFFF_t_70_pic_center 5]: /images/20221021/8195305dcd2c4a24ad664b8baa2875da.png [20210428164327279.png_pic_center]: /images/20221021/80b2474eb45a4282b65831bd7a046b2a.png
还没有评论,来说两句吧...