arm汇编基础上(代码片段)

嘻嘻兮 嘻嘻兮     2022-12-09     210

关键词:

ARM是一个精简指令集处理器,其指令集的设计是定长的,也就是其汇编对应的机器码是定长的(2字节或者4字节)。那么对于定长而言,其优点就是更快的被执行,因为这样CPU取指令译码的速度相对x86的CPU会快一些,但是缺点也比较明显,毕竟定长,那么表示其指令有限(指令少),所以一定程度上会软件的复杂度(需要几条指令才能完成一个功能)。

ARM的指令集一开始设计定长都是2字节的,随着时间的发展,会发现指令可能已经不够表达了,所以后面又出现了4字节的指令集。对于2字节的指令集称为thumb指令集,而4字节的称为arm指令集。此时我们可以自由选择,如果需要体积小那么我们可以选择thumb编译,而需要性能更好可以选择arm指令集。

但是对于某些指令而言,比如加法,那么使用2字节和4字节的指令区别其实并不大,所以就出现了thumb2指令集,其指令集算是变长了(要么2字节要么4字节),算是集合了上面两种的指令集的优点,现在的ndk编译器只能编译出thumb2指令集合arm指令集了。

下面来说说ARM处理器的工作状态,也就是说CPU如何按几个字节来取指令译码,毕竟存在多套指令集

1.ARM状态
2.Thumb状态

那么如何给CPU指令集指明状态呢?其实在标志寄存器中有一位来表示哪种状态,具体可以看下面的标志寄存器。

下面就可以说到寄存器了,如下图

上图表示了不同的模式下对于寄存器的访问,那么什么是CPU的工作模式,简单来说也就是x86下的权限级别划分,也就是多少环的意思,在arm里共有7种模式,其实本质上说还是2种,也就是三环和0环,只是把0环拆分的更加细致了,所以多了这么多的模式出来。

对于ARM的寄存器,目前我们只需关心用户模式,只需记住以下两种,共17个寄存器可用

1.通用寄存器 R0-R15
2.标志寄存器 cpsr

对于其他模式的寄存器,又会有其对应的寄存器,也就是上图中颜色比较深的那几个,但是对于寄存器的访问而言是一样的,比如说当进入了管理模式,那么我们使用SPSP寄存器访问的是SPSP_svc寄存器,而到了中止模式,我们还是只需使用SPSP寄存器,但是此时本质上访问的是SPSP_abt寄存器,无需关心真实的寄存器变化和名字。

通过上图也可以发现,不管在哪种模式下,其R0-R7,R15寄存器都是通用的。

下面再来稍微细说一下通用寄存器和程序计数器,在上图的寄存器在汇编中名字可以发现,其后面都有一个括号,表示的是寄存器别名,如下两指令是等价的

mov r15,#0
mov pc,#0

一般而言,对于R13,R14,R15这三个寄存器都使用别名来编码,因为这样子可读性更好(有特殊含义),其余都使用R0-R12表示

PC -  r15 程序寄存器,类似于eip
LR -  r14 链接寄存器,类似于CALL指令,下条指令的地址在r14
SP -  r13 堆栈指针

r13和r15应该比较好理解,对于r14,在x86里面使用call指令会将返回地址压入堆栈,而这里表示返回地址在LR寄存器。但是该寄存器只有一个,表明当函数内部还有函数调用时,我们首先需要先保存LR寄存器,防止寄存器值被覆盖。

对于函数返回而言,此时就比较简单了,只需如下汇编指令就可返回

mov pc,lr

下面就可以来说一下标志寄存器了

N : 当该位为1时表示负数,为0时表示正数
Z : 当该位为1时表示两数相等,为0时表示两数不相等
C : 当该位为1时,若为加法运算表示产生进位,否则该位为0 (减法运算中产生借位则该位置0,否则为1)
V : 使用加法/减法运算时,表示有符号溢出,否则该位为0
I : 当该位为1时,IRQ中断被禁止
F : 当该位为1时,FIQ中断被禁止
T : 当该位为1时,处理器处于Thumb状态下运行;该位为0时,处理器处于ARM状态下运行

重点说一下对于C位,加法运算和减法运算其产生的值是不一样的。而对于T位,就是上面有说CPU使用何种工作状态。

最低五位表示CPU处理何种工作模式,也就是用于表示上面说的7种模式(权限),如用户模式则表示 10000。

对于编码而言,一般我们只需关心最高四位和T位即可。

 

下面就可以开始入手写一下ARM汇编代码,首先我们可以先写一份C代码,然后使用ndk编译出一份汇编代码,观察其框架结构

#include<stdio.h>

int main(int argc,char* argv[])

    puts("hello arm");
    return 0;

编译生成汇编的命令如下:

	armv7a-linux-androideabi22-clang -E Hello.c -o Hello.i
	armv7a-linux-androideabi22-clang -S Hello.i -o Hello.s

注意这里需要添加一下ndk的环境变量,这里我的目录如下,大家根据目录对应修改添加即可

D:\\Android-NDK-r21\\toolchains\\llvm\\prebuilt\\windows-x86_64\\bin

生成的.s文件就是对应的汇编文件,由于这里生成的汇编代码,有很多无用的伪指令(.开头的指令),所以我就精简一下,精简后的汇编代码如下:

	.text  @代码段
	.fpu	neon       @浮点协处理器 soft表示软件模拟
	.globl	main       @全局符号为main
	.p2align	2  @对齐值,表示2^n
	.type	main,%function    @指明main的类型,这里是一个函数
	.code	32                @32表示arm指令集,16表示thumb指令集
main:
	push	r11, lr
	mov	r11, sp
	sub	sp, sp, #16
	ldr	r2, .LCPI0_0
.LPC0_0:
	add	r2, pc, r2
	mov	r0, r2
	bl	puts
	movw	r1, #0
	mov	r0, r1
	mov	sp, r11
	pop	r11, pc
	.p2align	2
.LCPI0_0:
	.long	.L.str-(.LPC0_0+8)  @定义数据
.Lfunc_end0:
	.size	main, .Lfunc_end0-main  @函数的大小

	.type	.L.str,%object          @ @.str
	.section	.rodata.str1.1,"aMS",%progbits,1  @定义rodata段
.L.str:
	.asciz	"hello arm"  @字符串
	.size	.L.str, 10

	.section	".note.GNU-stack","",%progbits @表示栈不能执行代码

在ARM里面,使用的是@进行注释,中间部分就是main的函数代码了,没有太多的注释,剩余的伪指令基本上都在后面注释了,下面先编译运行,看看结果是否正确,编译运行命令如下,注意需要先启动一个安卓模拟器或者真机

	//编译链接生成可执行文件
	armv7a-linux-androideabi22-clang -c Hello.s -o Hello.o
	armv7a-linux-androideabi22-clang Hello.o -o Hello

	//运行可执行文件
	adb push Hello /data/local/tmp
	adb shell chmod 777 /data/local/tmp/Hello
	adb shell /data/local/tmp/Hello

下面就解释一下上面的汇编代码,首先我们可以使用.section来定义一个段,在系统中预定了以下一些段、

.text:表示代码段
.data:表示初始化的数据段
.bss:表示未初始化的数据段
.rodata:表示只读数据段

再来看对齐值伪指令,在ARM指令集中不可能有1字节的指令集,所以指令最低是2字节,任何段都是2的n次方,上面的汇编代码表示4字节对齐。

.code伪指令,如果是32,那么表示arm指令集,也就是生成的字节码都是4字节的,而16则表示thumb2指令集。这里可以修改为16然后使用IDA反汇编观察一下字节码即可。

再下面就是函数的汇编代码了,首先看首尾部分,这里和x86中汇编函数框架很类似,都是保存环境,提示堆栈的操作

push r11, lr @保存r11和lr寄存器,因为内部有函数调用,lr表示函数的返回地址,所以需要保存
mov	r11, sp    @保存sp
sub	sp, sp, #16  @提升堆栈,立即数前面需要加#

@....


mov	sp, r11   @还原堆栈
pop	r11, pc @还原环境,这里原先lr保存的值给pc,表示返回

需要额外注意的是原先保存的lr值最后是给了pc相当于上面说的返回

mov pc,lr @lr中保存着函数返回地址,给了pc相当于返回

再来看中间的这串汇编代码,也就是调用puts函数

	ldr	r2, .LCPI0_0
.LPC0_0:
	add	r2, pc, r2
	mov	r0, r2
	bl	puts  @bl指令表示函数调用

对于puts函数而言,需要传递的应该是一个地址值,也就是字符串的首地址,对于ARM汇编中,mov是没有办法操作内存的,只有str/ldr指令用于存储和读取内存。由于一个地址值可能就需要占用4个字节,所以可能无法直接表示,那么我们就需要自己来计算该字符串的地址

pc + offset

我们可以使用当前的指令的地址值加上对于的偏移,这里子就能计算出字符串的首地址了,所以LCPI0_0中其实存放的是偏移,然后使用ldr指令来加载偏移值,下一条add指令加上pc就是结果了。

.LCPI0_0:
	.long	.L.str-(.LPC0_0+8)  @定义数据  .L.str-.LPC0_0-8

.long表示定义4字节的数据,我们还可以使用.byte和.short来分别来定义单字节和双字节数据。

只是奇怪的是,对于偏移值而言,为什么最后还需要减8呢,这里因为在ARMCPU中,pc指向的不是当前的指令,而是下下条指令,也就是三级流水线。

地址
100: mov r0,pc  @r0=108
104: mov r1,r2
108: mov r2,r3

假设如上代码,其r0执行完的结果并不是100,而是108,具体的原因可以看下图

所以因为PC指向的是下下条指令,在arm指令中,每条指令占用4字节,所以减去8之后其偏移值+PC就正常了。那么如果对于.code伪指令的值改为16的话,那么这个值就需要修改为4了,这样子才能保证偏移值的正确。

OK,最后就可以说说函数的调用约定了,这里的约定是比较简单的,前四个寄存器使用R0~R3进行传递参数,剩余不够的使用堆栈传递参数,所以你会发现该字符串的地址值最后赋值给了r0。

好了,下面我们自己来添加一个函数,并尝试调用

	.text
	.fpu	neon
	.globl	main                    @ -- Begin function main
	.p2align	2
	.type	main,%function
	.code	32                      @ @main
main:
	push	r11, lr
	mov	r11, sp
	sub	sp, sp, #16
	mov r0,#5  @传递参数一
	mov r1,#8  @传递参数二
	bl MyAdd   @调用函数
	ldr	r2, .LCPI0_0
.LPC0_0:
	add	r2, pc, r2
	movw	r3, #0
	mov	r0, r2  @参数一  参数二在MyAdd的返回值中,正好是r1
	bl	printf  @修改调用printf函数
	movw	r1, #0
	str	r0, [sp]                @ 4-byte Spill
	mov	r0, r1
	mov	sp, r11
	pop	r11, pc
	.p2align	2
.LCPI0_0:
	.long	.L.str-(.LPC0_0+8)
.Lfunc_end0:
	.size	main, .Lfunc_end0-main
	
	.type MyAdd,%function
	.code 32
MyAdd:  @添加的add函数
	add r1,r0,r1  @这里我的返回值放在r1
	mov pc,lr  @返回
.LMyAdd_end:
	.size MyAdd,.LMyAdd_end-MyAdd
	
	.type	.L.str,%object          @ @.str
	.section	.rodata.str1.1,"aMS",%progbits,1
.L.str:
	.asciz	"hello arm:%d"  @修改字符串
	.size	.L.str, 13

	.section	".note.GNU-stack","",%progbits

上面的代码中,对于Add函数,我就是偷懒了,把返回值放在r1寄存器中,因为在返回后,也没有对r1进行修改,这样子再调用printf函数时会自动取r1作为第二个参数。

执行结果如下:

adb push Hello /data/local/tmp
Hello: 1 file pushed. 0.7 MB/s (6560 bytes in 0.009s)
adb shell chmod 777 /data/local/tmp/Hello
adb shell /data/local/tmp/Hello
hello arm:13

最后显示的就是打印的结果了,13是正确的,说明两个函数都被正确调用了。

arm汇编基础详解(代码片段)

目录前言1.GNU汇编语法2.Cortex-A7常用汇编指令2.1处理器内部数据传输指令2.2存储器访问指令2.3压栈和出栈指令(了解)2.4跳转指令2.5算术运算指令2.6逻辑运算指令前言我们在进行嵌入式Linux开发的时候是绝对要掌握基本的AR... 查看详情

ios汇编入门教程arm64汇编基础(代码片段)

__attribute__anti_debug__arm64____asm____volatile__testabresabresmainrestesthelloBBspspstrw0strw1ldrw0ldrw1addw0w0w1strw0ldrw0addspspretBBspspstpx29x30addx29sporrw0wzrorrw1wzrsturwzrbl_testmovw1strw0m 查看详情

linux驱动开发:arm汇编基础(代码片段)

文章目录Linux驱动开发:ARM汇编基础一、GUN汇编语法1.1语句结构:1.2伪操作1.3GUN函数二、ARM的寻址方式2.1立即寻址2.2寄存器寻址2.3寄存器间接寻址2.4基址变址寻址2.5多寄存器寻址2.6相对寻址2.7堆栈寻址三、Cortex-A常用汇编... 查看详情

arm汇编基础上(代码片段)

...集处理器,其指令集的设计是定长的,也就是其汇编对应的机器码是定长的(2字节或者4字节)。那么对于定长而言,其优点就是更快的被执行,因为这样CPU取指令译码的速度相对x86的CPU会快一些,但... 查看详情

android逆向基础之arm汇编语言知识总结(代码片段)

文章目录前言ARM32汇编1.1寄存器1.2寻址方式1.3汇编指令1.4代码识别1.5IDA示例ARM64汇编2.1寄存器2.2变址寻址方式2.3常用汇编指令2.4实例程序分析总结前言X86是英特尔Intel首先开发制造的一种微处理器体系结构的泛称,包括Intel8086... 查看详情

arm汇编基础基于keil创建stm32汇编程序的编写(代码片段)

文章目录一、新建项目(1)工具介绍(2)创建项目:二、配置环境(1)配置芯片(2)配置运行环境三、调试代码(这里选择使用的是仿真调试)(1)源代码(2)仿真设... 查看详情

android逆向基础之arm汇编语言知识总结(代码片段)

文章目录前言ARM32汇编1.1寄存器1.2寻址方式1.3汇编指令1.4代码识别1.5IDA示例ARM64汇编2.1寄存器2.2变址寻址方式2.3常用汇编指令2.4实例程序分析总结前言X86是英特尔Intel首先开发制造的一种微处理器体系结构的泛称,包括Intel8086... 查看详情

arm开发入门与汇编基础(代码片段)

2019-12-12关键字:汇编指令基础 首先ARM是一家公司,它成立于1990年。ARM公司主要是设计ARM系列的RISC处理器内核,并将这些内核授权给合作伙伴进行生产与销售。ARM公司是一家只负责设计内核而不生产芯片的公司。 ARM芯片... 查看详情

arm汇编基础--ads1.2安装与第一个arm汇编程序

前言最近的工作是与逆向相关,用到ARM汇编,以前在大学时学过x86汇编和ARM开发,自我感觉当时学的还挺好,很久没用也生疏了.正好趁着这个机会也复习一下ARM汇编.关于ARM的介绍和理论知识,此处就不再赘述.进入正题:安装ADS1.2安装过... 查看详情

arm汇编简单介绍(代码片段)

1.汇编文件说明:  汇编文件以【.s】结尾的文件格式  注释:多行注释/**/  ;单行注释@ 2. 符号说明:  1)汇编指令,一条指令对应一个机器码,完成一定的功能  2)伪指令,一条指令对应多条机器码,... 查看详情

android逆向arm汇编(使用ida解析arm架构的动态库文件|分析malloc函数的arm汇编语言)(代码片段)

文章目录一、分析malloc函数的arm汇编语言一、分析malloc函数的arm汇编语言在上一篇博客【Android逆向】arm汇编(使用IDA解析arm架构的动态库文件|使用IDA打开arm动态库文件|切换IDA中汇编代码显示样式)打开并配置了选项;分析libc.so的... 查看详情

android逆向arm汇编(使用ida解析arm架构的动态库文件|分析malloc函数的arm汇编语言)(代码片段)

文章目录一、分析malloc函数的arm汇编语言一、分析malloc函数的arm汇编语言在上一篇博客【Android逆向】arm汇编(使用IDA解析arm架构的动态库文件|使用IDA打开arm动态库文件|切换IDA中汇编代码显示样式)打开并配置了选项;分析libc.so的... 查看详情

arm汇编(代码片段)

汇编指令和伪指令的区别汇编指令是cpu机器指令的助记符,代表这个cpu的功能。伪指令本质不是指令,只是和汇编指令写在同一个代码中,它由编译器提供,用于指导编译过程,经过编译后伪指令最终不会生... 查看详情

android逆向arm汇编(使用ida解析arm架构的动态库文件|使用ida打开arm动态库文件|切换ida中汇编代码显示样式)(代码片段)

文章目录一、使用IDA打开arm动态库文件二、切换IDA中汇编代码显示样式一、使用IDA打开arm动态库文件分析AndroidSDK中的arm架构的动态库,动态库位置:D:\\001_Develop\\001_SDK\\Sdk\\build-tools\\26.0.3\\renderscript\\lib\\intermediates\\armeabi-v7a\\libc.so... 查看详情

android逆向arm汇编(使用ida解析arm架构的动态库文件|使用ida打开arm动态库文件|切换ida中汇编代码显示样式)(代码片段)

文章目录一、使用IDA打开arm动态库文件二、切换IDA中汇编代码显示样式一、使用IDA打开arm动态库文件分析AndroidSDK中的arm架构的动态库,动态库位置:D:\\001_Develop\\001_SDK\\Sdk\\build-tools\\26.0.3\\renderscript\\lib\\intermediates\\armeabi-v7a\\libc.so... 查看详情

mips简单入门(代码片段)

What‘stheMIPS?汇编语言汇编语言是一类语言的总称,因处理器不同,而对应的不同机器的指令集也不同,产生了很多种汇编语言。目前最流行的是ARM,MIPS,x86。ARM用于大量的移动手机和嵌入式系统。MIPS流行于IBMCPUs。x86用在InterPCs。... 查看详情

《嵌入式-嵌入式大杂烩》arm汇编入门(代码片段)

我们在学习ARM的时候,一般都不用看汇编启动代码,直接使用芯片厂商提供的汇编启动代码,但是要想深入了解ARM内部原理,就必须掌握一定的汇编知识。我们在前面总结了处理器架构与指令集,那么汇编和... 查看详情

arm伪指令(代码片段)

...令集中的指令,只是为了编程方便人们定义了伪指令。在汇编时这些指令将会被等效的ARM指令或arm指令的组合代替。编程时可以像其他ARM指令一样使用伪指令,区别是伪指令不像指令一样有对应的机器编码。我今天主要给大家介... 查看详情