And指令
是逻辑运算指令, 按位进行与运算, 也就是需要转成二进制进行运算, 1代表真, 0代表假
1 | mov al,1111 1111B ;或者写成十六进制FFH的形式也可 |
or指令
按位进行或运算
1 | mov al,1111 1111B |
ascii码
1 | mov al 'a' ;该行代码将字符a所对应的ascii码传入al寄存器中 |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
乱码三千 – 码出一个新世界
是逻辑运算指令, 按位进行与运算, 也就是需要转成二进制进行运算, 1代表真, 0代表假
1 | mov al,1111 1111B ;或者写成十六进制FFH的形式也可 |
按位进行或运算
1 | mov al,1111 1111B |
1 | mov al 'a' ;该行代码将字符a所对应的ascii码传入al寄存器中 |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
咱们知道x86架构cpu用于PC端和工作站较多,ARM架构cpu常见于手机和单片机,那么MIPS架构的cpu主要在哪些设备可以找到它们的身影呢?
在mips中通用寄存器用$开头表示,一共有32个
| 寄存器编号 | 寄存器名 | 寄存器用途 |
|---|---|---|
| $0 | $zero | 永远返回0 |
| $1 | $at | 保留寄存器 |
| $2-$3 | $v0-$v1 | 一般用于存储表达式或者函数的返回值(value的简写) |
| $4-$7 | $a0-$a3 | 参数寄存器(Argument简写) |
| $8-$15 | $t0-$t7 | 一般用于存储临时变量(temp简写) |
| $16-$23 | $s0-$s7 | 存放子函数调用过程需要被保留的数据(saved values) |
| $24-$25 | $t8-$t9 | 属性同$t0-$t7 |
| $26-$27 | $k0-$k1 | 一般存储中断函数返回值 |
| $28 | $gp | GlobalPointer简写 |
| $29 | $sp | 栈指针,指向栈顶(Stack Pointer简写) |
| $30 | $s8/$fp | (Save / Frame Pointer)帧指针 |
| $31 | $ra | 一般用于存储函数返回地址(return address简写) |
寄存器编号和别名一一对应,同一个寄存器可以有两种不同表示方法:$0或者$zero
- program counter (PC) 无法直接修改,通过跳转指令可以改动
- HI 和 LO :这两个寄存器特别用来保存乘法、除法、乘法累加的结果。
1 | .data #数据段 |
lili(load immediate) :用于将立即数传送给寄存器
1 | li $t0,1 ;十六进制数据使用0x前缀表示 |
lala(load address) :用于将地址传送至寄存器中, 多用于通过地址获取数据段中的地址
1 | .data |
move用于将一个寄存器中的数据传送至另一个寄存器当中
1 | move $t0,$t1 # 将寄存器$t1中的数据传送至$t0 |
syscall在C语言中输出文本可以使用printf函数,但是汇编中没有printf这么一说,如果想要输出文本,需要借助syscall指令
如果想要输出一个数字1,那么syscall指令从$a0寄存器中取出需要输出的数据
因此, 你在执行syscall指令之前需要将数据提前放入$a0之中:
1 | li $a0,1 |
同时,还需要指定输出的数据类型,数据类型的指定保存在$v0寄存器中
1 | # $v0=1, syscall--->print_int |
$v0存入1,表示syscall将$a0中的数据当做数字输出
$v0存入4,表示syscall将$a0中的数据当做数据的地址,然后输出对应的数据
| Service | Code in $v0 | Arguments | Result |
|---|---|---|---|
| print integer | 1 | $a0 = integer to print | |
| print float | 2 | $f12 = float to print | |
| print double | 3 | $f12 = double to print | |
| print string | 4 | $a0 = address of null-terminated string to print | |
| read integer | 5 | $v0 contains integer read | |
| read float | 6 | $f0 contains float read | |
| read double | 7 | $f0 contains double read | |
| read string | 8 | $a0 = address of input buffer $a1 = maximum number of characters to read | See note below table |
| sbrk (allocate heap memory) | 9 | $a0 = number of bytes to allocate | $v0 contains address of allocated memory |
| exit (terminate execution) | 10 | ||
| print character | 11 | $a0 = character to print | See note below table |
| read character | 12 | $v0 contains character read | |
| open file | 13 | $a0 = address of null-terminated string containing filename $a1 = flags $a2 = mode | $v0 contains file descriptor (negative if error). See note below table |
| read from file | 14 | $a0 = file descriptor $a1 = address of input buffer $a2 = maximum number of characters to read | $v0 contains number of characters read (0 if end-of-file, negative if error). See note below table |
| write to file | 15 | $a0 = file descriptor $a1 = address of output buffer $a2 = number of characters to write | $v0 contains number of characters written (negative if error). See note below table |
| close file | 16 | $a0 = file descriptor | |
| exit2 (terminate with value) | 17 | $a0 = termination result | See note below table |
| Services 1 through 17 are compatible with the SPIM simulator, other than Open File (13) as described in the Notes below the table. Services 30 and higher are exclusive to MARS. | |||
| time (system time) | 30 | $a0 = low order 32 bits of system time $a1 = high order 32 bits of system time. See note below table | |
| MIDI out | 31 | $a0 = pitch (0-127) $a1 = duration in milliseconds $a2 = instrument (0-127) $a3 = volume (0-127) | Generate tone and return immediately. See note below table |
| sleep | 32 | $a0 = the length of time to sleep in milliseconds. | Causes the MARS Java thread to sleep for (at least) the specified number of milliseconds. This timing will not be precise, as the Java implementation will add some overhead. |
| MIDI out synchronous | 33 | $a0 = pitch (0-127) $a1 = duration in milliseconds $a2 = instrument (0-127) $a3 = volume (0-127) | Generate tone and return upon tone completion. See note below table |
| print integer in hexadecimal | 34 | $a0 = integer to print | Displayed value is 8 hexadecimal digits, left-padding with zeroes if necessary. |
| print integer in binary | 35 | $a0 = integer to print | Displayed value is 32 bits, left-padding with zeroes if necessary. |
| print integer as unsigned | 36 | $a0 = integer to print | Displayed as unsigned decimal value. |
| (not used) | 37-39 | ||
| set seed | 40 | $a0 = i.d. of pseudorandom number generator (any int). $a1 = seed for corresponding pseudorandom number generator. | No values are returned. Sets the seed of the corresponding underlying Java pseudorandom number generator (java.util.Random). See note below table |
| random int | 41 | $a0 = i.d. of pseudorandom number generator (any int). | $a0 contains the next pseudorandom, uniformly distributed int value from this random number generator’s sequence. See note below table |
| random int range | 42 | $a0 = i.d. of pseudorandom number generator (any int). $a1 = upper bound of range of returned values. | $a0 contains pseudorandom, uniformly distributed int value in the range 0 = [int] [upper bound], drawn from this random number generator’s sequence. See note below table |
| random float | 43 | $a0 = i.d. of pseudorandom number generator (any int). | $f0 contains the next pseudorandom, uniformly distributed float value in the range 0.0 = f 1.0 from this random number generator’s sequence. See note below table |
| random double | 44 | $a0 = i.d. of pseudorandom number generator (any int). | $f0 contains the next pseudorandom, uniformly distributed double value in the range 0.0 = f 1.0 from this random number generator’s sequence. See note below table |
| (not used) | 45-49 | ||
| ConfirmDialog | 50 | $a0 = address of null-terminated string that is the message to user | $a0 contains value of user-chosen option 0: Yes 1: No 2: Cancel |
| InputDialogInt | 51 | $a0 = address of null-terminated string that is the message to user | $a0 contains int read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field |
| InputDialogFloat | 52 | $a0 = address of null-terminated string that is the message to user | $f0 contains float read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field |
| InputDialogDouble | 53 | $a0 = address of null-terminated string that is the message to user | $f0 contains double read $a1 contains status value 0: OK status -1: input data cannot be correctly parsed -2: Cancel was chosen -3: OK was chosen but no data had been input into field |
| InputDialogString | 54 | $a0 = address of null-terminated string that is the message to user $a1 = address of input buffer $a2 = maximum number of characters to read | See Service 8 note below table $a1 contains status value 0: OK status. Buffer contains the input string. -2: Cancel was chosen. No change to buffer. -3: OK was chosen but no data had been input into field. No change to buffer. -4: length of the input string exceeded the specified maximum. Buffer contains the maximum allowable input string plus a terminating null. |
| MessageDialog | 55 | $a0 = address of null-terminated string that is the message to user $a1 = the type of message to be displayed: 0: error message, indicated by Error icon 1: information message, indicated by Information icon 2: warning message, indicated by Warning icon 3: question message, indicated by Question icon other: plain message (no icon displayed) | N/A |
| MessageDialogInt | 56 | $a0 = address of null-terminated string that is an information-type message to user $a1 = int value to display in string form after the first string | N/A |
| MessageDialogFloat | 57 | $a0 = address of null-terminated string that is an information-type message to user $f12 = float value to display in string form after the first string | N/A |
| MessageDialogDouble | 58 | $a0 = address of null-terminated string that is an information-type message to user $f12 = double value to display in string form after the first string | N/A |
| MessageDialogString | 59 | $a0 = address of null-terminated string that is an information-type message to user $a1 = address of null-terminated string to display after the first string | N/A |
使用syscall指令输出helloworld示例:
1 | .data |
定义整型数据
1 | # 打印Integer数据 |
定义Float数据
1 | #打印float数据 |
定义Double数据
1 | #打印double数据 |
定义字符串数据
1 | #打印double数据 |
字符串输入
1 | .data |
整型数据输入
1 | # 输入的结果系统会存放在$v0寄存器 |
浮点型数据输入
1 | #以float为例 输入的结果会存放在$f0寄存器 |
单精度数(float型)在32位计算机中存储占用4字节,也就是32位,有效位数为7位,小数点后6位。
双精度数(double型)在32位计算机中存储占用8字节,也就是64位,有效位数为16位,小数点后15位。
在mips中一共有32个浮点寄存器(其中包含16个双精度浮点寄存器),用于单独处理浮点数
函数声明
格式
1 | 函数名: |
示例
1 | .data |
函数调用
格式
1 | jal 函数名 |
示例
1 | .data |
函数传参和返回值
在mips汇编中,函数的参数一般放在$a系列寄存器当中,最多对应4个参数,超过4个部分使用栈储存. 函数的变量和返回值一般放在$v系列寄存器当中
1 | #需求:定义加法函数 并调用获取返回值int sum(int v,int b) |
嵌套函数
由于每执行jal调用一次函数, 就会刷新$ra寄存器中的值,因此,在嵌套函数调用之前,需要临时保存上一次$ra中的值,使用栈空间临时保护即可
栈空间拉伸和平衡
1 | addi $sp,$sp,-4 #栈拉伸 拉伸4个字节空间 |
入栈和出栈
1 | sw $s0 ,0($sp) #入栈 往内存中写入数据 |
嵌套函数使用栈保护$ra代码示例
1 | main: |
从mars中可以查看到内存分布起始物理地址

转成图后:


栈的伸缩在mips和x86架构中是由高地址往低地址进行伸缩, 在arm架构中可升序也可降序
在内存动态分配(heap区)过程中容易出现一些小且不连续的空闲内存区域,这些未被使用的内存称作内存碎片
分类:
称作 程序计数寄存器(Program Counter Register) :用于存储程序即将要执行的指令所对应在内存中的实际物理地址, 如果改变该值可以让指令跳转到我们想要跳转的地方
如何修改pc寄存器中的值
使用以下转移指令
jr指令
1 | jr 寄存器 #$ra寄存器实际上就是保存着函数调用前的pc寄存器的值 |
jal指令
1 | jal 标号 #跳转的同时给$ra寄存器赋值 |
j指令
1 | j 标号 #直接跳转 |
从指定内存中读取数据
1 |
|
从内存中读取数据的宽度取决于寄存器的大小,由于32位cpu寄存器最大存储32位数据,因此lw $t0表示一次性读取4个字节的数据到$t0寄存器, 如果想要连续读取八个字节的数据,那么需要使用ld $t0,表示一次性读取8个字节的数据到$t0,$t1连个连续的寄存器,
往指定内存中写入数据
1 | .data 0x10010020 ;将以下定义的数据存放在0x10010020这个物理地址 |
1 | #整型数据 |
以上直接使用的是简单粗暴的十六进制表示物理地址,很多时候内存的地址会保存在寄存器中,你可能会看到以下写法:
1 | lw $s1, $s2 |
注意: 往指定内存中读取写入数据时,代码段不允许直接写入和读取
数组本质上就是多个数据的集合,在内存中按照一定顺序排列,角标即为每个数据的偏移值,在mips中内存数据是按照4个字节进行对齐的,也就是说一个数据最少占用4个字节内存空间,因此数组中数据之间的偏移量固定为n*4
1 | .data |
数组的打印
1 | .data |
1 | .data |
bgt(branch if greater than):用于大于比较1 | bgt $t0,$t1,sub # 如果$t0中的数据大于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义 |
beq(branch equal):用于等于比较1 | beq $t0,$t1,sub # 如果$t0中的数据等于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义 |
ble(branch if less than):用于小于比较1 | ble $t0,$t1,sub # 如果$t0中的数据小于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义 |
练习1: 将以下c代码转换成mips汇编代码:
1 | scanf("%d",$a); |
汇编代码:
1 | # 用$t0指代a ,$t1指代b |
练习2: 将以下c代码转换成mips汇编代码:
1 | //求累加之和 |
汇编代码:
1 | # 用$t0指代i ,$t1指代s |
小于
1 | .data |
等于
1 | .data |
小于等于
1 | .data |
以上是单精度浮点数据的比较示例,如果是双精度,只需将结尾.s改成.d即可
在文件A中定义函数
1 | fun: |
在文件B中使用关键字.include引用A文件中的函数
1 | .text |
所有文件必须在同一目录下
宏替换
全局替换,使用我们之前学过的.include伪指令进行替换
宏匹配
在汇编中,如果我们要依次打印1,2,3三个整数,那么汇编如下:
1 | print1: |
我们发现使用标签的方式定义函数,当函数体内容存在不确定变量值时,代码非常冗余, 如果使用高级语言进行封装的话,我们一般一个函数就搞定了:
1 | void print(int a){ |
有没有办法使得汇编能像高级语言一样简洁呢?
在MARS中给我们提供了一个扩展伪指令,叫做宏匹配
宏匹配使用的格式如下:
1 | .macro 别名 |
示例:
1 | li $v0,10 |
如果我们要封装一个打印整型数据的函数,那么我们可以:
1 | #封装结果为 |
这样是不是和高级语言没什么区别啦
打印字符串封装示例:
1 | .macro print_str (%str) |
然后结合我们之前学过的多文件开发,完全可以将这个封装好的函数单独放在一个文件中,直接在头部.include就行
宏定义
全局定义,如果我们想给一个数据或者寄存器,甚至是一行代码取个别名,然后在代码中使用别名的方式指代,那么可以使用宏定义指令.eqv 别名的好处就是方便我们进行记忆
1 | .eqv LIMIT 20 #给20这个立即数取个别名为LIMIT |
当我们有以下代码:
1 | .text |
如果我们使用宏定义,我们可以写成如下:
1 | .eqv LIMIT 20 #给20这个立即数取个别名为LIMIT |
注:宏定义和宏匹配必须先定义后使用,也就是说定义的代码需要放在前头
二维数组其实就类似于我们数学中的二维坐标系,我们如果要定位一个点的话可以使用(x,y)来表示,在计算机的世界里,二维中所有的点都按照顺序依次存放在内存当中
假设我们将第一维当做行,第二维当做列,那么排列的方式有以下两种:
第一种是 行不动,列轮动
| 内存地址 | 二维坐标 |
|---|---|
| 0x00000020 | arr[2][2] |
| 0x0000001C | arr[2][1] |
| 0x00000018 | arr[2][0] |
| 0x00000014 | arr[1][2] |
| 0x00000010 | arr[1][1] |
| 0x0000000C | arr[1][0] |
| 0x00000008 | arr[0][2] |
| 0x00000004 | arr[0][1] |
| 0x00000000 | arr[0][0] |
这种方式获取实际地址的公式为:
1 | addr=baseAddr+(rowIndex*colSize+colIndex)*dataSize |
第二种是 列不动,行轮动
| 内存地址 | 二维坐标 |
|---|---|
| 0x00000020 | arr[2][2] |
| 0x0000001C | arr[1][2] |
| 0x00000018 | arr[0][2] |
| 0x00000014 | arr[2][1] |
| 0x00000010 | arr[1][1] |
| 0x0000000C | arr[0][1] |
| 0x00000008 | arr[2][0] |
| 0x00000004 | arr[1][0] |
| 0x00000000 | arr[0][0] |
这种方式获取实际地址的公式为:
1 | addr=baseAddr+(colIndex*rowSize+rowIndex)*dataSize |
1 | #需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}}; |
1 | #需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}}; |
由于数组中数据是在内存中连续进行排列存储的,那么我们可以之间将数据 依次存入内存之中,然后使用算法进行数据获取即可(以下示例皆采用 行不动,列动 的方式)
1 | #需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}}; |
再简化一下:
1 | #需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}}; |
按照正常的编程思维,我们一般使用第一种行不动 列动的存储方式 第一维为行,第二维为列,如果你想改成行动存储方式,有两种方法:要么将数据的存储顺序进行变动,配合第二种算法,要么将第二维当成行,第一维当成列,配合第二种算法进行处理
类别 | 指令名称 | 实例 | 含义 | 注释 | 英文注解 |
算 数 | 加法 | add $s1, $s2, $s3 | $s1 = $s2 + $s3 | 三个寄存器操作数 | addition 加法 |
减法 | sub $s1, $s2, $s3 | $s1 = $s2 - $s3 | 三个寄存器操作数 | subtraction 减法 | |
立即数加法 | addi $s1, $s2, 20 | $s1 = $s2 + 20 | 用于加常数数据 | add immediate 立即加法 | |
数 据 传 输 | 取字 | lw $s1, 20 ($s2) | $s1 = Memory[$s2 + 20] | 将一个字从内存中取到寄存器中 | load word 加载字 |
存字 | sw $s1, 20 ($s2) | Memory[$s2 + 20] = $s1 | 将一个字从寄存器中取到内存中 | store word 存储字 | |
取半字 | lh $s1, 20 ($s2) | $s1 = Memory[$s2 + 20] | 将半个字从内存中取到寄存器中 | load halfword 加载半字 | |
取无符号半字 | lhu $s1, 20 ($s2) | $s1 = Memory[$s2 + 20] | 将半个字从内存中取到寄存器中 | load halfword unsigned | |
存半字 | sh $s1, 20 ($s2) | Memory[$s2 + 20] = $s1 | 将半个字从寄存器中取到内存中 | stroe halfword 存储半字 | |
取字节 | lb $s1, 20 ($s2) | $s1 = Memory[$s2 + 20] | 将一字节从内存中取到寄存器中 | load byte | |
取无符号字节 | lbu $s1, 20 ($s2) | $s1 = Memory[$s2 + 20] | 将一字节从内存中取到寄存器中 | load byte unsigned | |
存字节 | sb $s1, 20 ($s2) | Memory[$s2 + 20] = $s1 | 将一字节从寄存器中取到内存中 | store byte | |
取链接字 | ll $s1, 20 ($s2) | $s1 = Memory[$s2 + 20] | 取字作为原子交换的前半部 | load linked | |
存条件字 | sc $s1, 20 ($s2) | Memory[$s2 + 20] = $s1; $s1 = 0 or 1 | 存字作为原子交换的后半部分 | store conditional | |
取立即数的高位 | lui $s1, 20 | $s1 = 20 * 216 | 取立即数并放到高16位 | load upper immediate | |
逻 辑 | 与 | and $s1, $s2, $s3 | $s1 = $s2 & $s3 | 三个寄存器操作数按位与 | and |
或 | or $s1, $s2, $s3 | $s1 = $s2 | $s3 | 三个寄存器操作数按位或 | or | |
或非 | nor $s1, $s2, $s3 | $s1 = ~ ($s2 | $s3) | 三个寄存器操作数按位或非 | not or | |
立即数与 | andi $s1, $s2, 20 | $s1 = $s2 & 20 | 和常数按位与 | and immediate | |
立即数或 | ori $s1, $s2, 20 | $s1 = $s2 | 20 | 和常数按位或 | or immediate | |
逻辑左移 | sll $s1, $s2, 10 | $s1 = $s2 << 20 | 根据常数左移相应位 | set left logical | |
逻辑右移 | srl $s1, $s2, 10 | $s1 = $s2 >> 20 | 根据常数右移相应位 | set right logical | |
条 件 分 支 | 相等时跳转 | beq $s1, $s2, 25 | if ($s1 == $s2) go to PC + 4 + 25 * 4 | 相等检测: 和PC相关的跳转 | branch on equal |
不相等时跳转 | bne $s1, $s2, 25 | if ($s1 != $s2) go to PC + 4 + 25 * 4 | 不相等检测: 和PC相关的跳转 | branch on not equal | |
小于时跳转 | slt $1, $s2, $3 | if ($s2 < $s3) $s1 = 1; else $s1 = 0 | 比较是否小于 | set less than | |
无符号数比较小时置位 | sltu $1, $s2, $3 | if ($s2 < $s3) $s1 = 1; else $s1 = 0 | 比较是否小于无符号数 | set less than unsigned | |
无符号数小于立即数时置位 | slti $1, $s2, 20 | if ($s2 < 20) $s1 = 1; else $s1 = 0 | 比较是否小于常数 | set less than immediate | |
无符号数比较小于无符号立即数时置位 | sltiu $1, $s2, 20 | if ($s2 < 20) $s1 = 1; else $s1 = 0 | 比较是否小于无符号常数 | set less than immediate unsigned | |
无 条 件 跳 转 | 跳转 | j 2500 | go to 2500 * 4 | 跳转到目标地址 | jump |
跳转至寄存器所指位置 | jr $ra | go to $ra | 用于switch语句,以及过程调用 | jump register | |
跳转并链接 | jal 2500 | $ra = PC + 4; go to 2500 * 4; | 用于过程调用(方法) 正常的执行流程执行完A自然要执行B指令,现在要跳转执行C方法,这时就把B地址存入寄存器中,执行完C后跳转到B | jump and link |

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
arm64-v8a是可以向下兼容的,其下有armeabi-v7a,armeabi
armeabi-v7a向下兼容armeabi
对于一个cpu是arm64-v8a架构的手机,它运行app时,进入jnilibs去读取库文件时,先看有没有arm64-v8a文件夹:
如果没有该文件夹,去找armeabi-v7a文件夹,如果没有,再去找armeabi文件夹,如果连这个文件夹也没有,就抛出异常
如果有arm64-v8a文件夹,那么就去找特定名称的.so文件,注意:如果没有找到,不会再往下(armeabi-v7a文件夹)找了,而是直接抛出异常
由于向下兼容的特性 高版本的设备可以使用低版本armeabi的so库, 但是低版本不支持高版本库, 这也就是为什么很多开发商包括微信只保留了armeabi的so库,从而兼容市面上所有的设备
所有的x86/x86_64/armeabi-v7a/arm64-v8a设备都支持armeabi架构的.so文件,因此似乎移除其他ABIs的.so文件是一个减少APK大小的好技巧。但事实上并不是:这不只影响到函数库的性能和兼容性
64位设备(arm64-v8a, x86_64, mips64)能够运行32位的函数库,但是以32位模式运行,在64位平台上运行32位版本的ART和Android组件,将丢失专为64位优化过的性能(ART,webview,media等等)
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
CPU除了有控制器、运算器还有寄存器。其中寄存器的作用就是进行数据的临时存储。
CPU的运算速度是非常快的,为了性能CPU在内部开辟一小块临时存储区域,并在进行运算时先将数据从内存复制到这一小块临时存储区域中,运算时就在这一小快临时存储区域内进行。我们称这一小块临时存储区域为寄存器。
对于arm64系的CPU来说, 如果寄存器以x开头则表明的是一个64位的寄存器,如果以w开头则表明是一个32位的寄存器,在系统中没有提供16位和8位的寄存器供访问和使用。其中32位的寄存器是64位寄存器的低32位部分并不是独立存在的。
iPhoneX上搭载的ARM处理器A11它的1级缓存的容量是64KB,2级缓存的容量8M.
CPU每执行一条指令前都需要从内存中将指令读取到CPU内并执行。而寄存器的运行速度相比内存读写要快很多,为了性能,CPU还集成了一个高速缓存存储区域.当程序在运行时,先将要执行的指令代码以及数据复制到高速缓存中去(由操作系统完成).CPU直接从高速缓存依次读取指令来执行.
数据地址寄存器通常用来做数据计算的临时存储、做累加、计数、地址保存等功能。定义这些寄存器的作用主要是用于在CPU指令中保存操作数,在CPU中当做一些常规变量来使用。
ARM64中
注意:
有一种特殊的寄存器段寄存器:CS,DS,SS,ES四个寄存器来保存这些段的基地址,这个属于Intel架构CPU中.在ARM中并没有
因为浮点数的存储以及其运算的特殊性,CPU中专门提供浮点数寄存器来处理浮点数
现在的CPU支持向量运算.(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器.
为指令指针寄存器, 它指示了CPU当前要读取指令的地址, 类似于x86汇编种的cs+ip
注意:ARM64开始,取消32位的 LDM,STM,PUSH,POP指令! 取而代之的是ldr\ldp str\stp
ARM64里面 对栈的操作是16字节对齐的!!
注意:读/写 数据是都是往高地址读/写 也就是sp指针是从高地址往低地址移动但是指向的数据是往高地址读写,堆指针是从低往高地址移动,堆和栈各占一头,两个指针相撞则抛出堆栈内存溢出
str(store register)指令
将数据从寄存器中读出来,存到内存中.
ldr(load register)指令
将数据从内存中读出来,存到寄存器中
此ldr 和 str 的变种ldp(pair) 和 stp(pair) 还可以操作2个寄存器.
1 | ;利用栈进行数据交换 |
stur指令: 偏移量为减时使用 . stur w0, [x29, #0x8] 偏移量为负的 将寄存器w0的值存入x29 - 0x8 的内存地址
[sp]: sp保存栈空间的地址值, [sp]表示取值,获取所对应的空间 和8086中的[bx]是类似的
1 | stp x29,x30,[sp,#-0x10]! ;尾部多了一个!号 |
类似于x86汇编中的, call
ARM64平台的特色指令,它面向硬件做了优化处理的
x30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!
注意:在函数嵌套调用之前的时候.需要将x30入栈!
1 | .text ;代码段 |
寄存器是全局容器,所有函数共用,但是栈不一样,一个函数占用独有的栈空间, 在各个函数嵌套调用时,寄存器很容易被覆盖读写,这个时候为了保持寄存器的数据不被改变,通常结合栈临时保存寄存器中的值,然后函数ret之前将数据恢复,这样就能确保上一个函数的数据不被改变,也就是实现了将寄存器当做局部变量使用
ARM64里面 对栈的操作是16字节对齐的, 也就是一次开辟栈空间至少是16字节, 或者是16的倍数, 如果不是这个值会报错
1 | .text ;代码段 |
既然sp一次最少拉伸16个字节, 那么以下函数需要拉伸多少空间:
1 | void sum(int a, int b){ |
由于int类型的数据占用4个字节空间, 这里一共有5个int,那么需要占用5*4=20个字节的空间, 那么sp一次性拉伸0x20也就是32字节的栈空间
1 | 16位寄存器-->最大装2个字节数据-->0xFFFF |
如果函数里面又调用了函数,那么sp拉伸多少呢?
1 | void sum(int a, int b){ |
由于bl调用函数之前会复写x30(lr)寄存器中的值, 所以需要将x29和x30寄存器进行临时保护, 这两个寄存器占用16个字节, 加上sum函数的局部变量和参数所占的16个字节,一共是32个字节
函数体中没有调用其他函数的函数称之为叶子函数,又称为末尾函数
这种函数在编写汇编代码时可以省略使用栈空间, 栈空间是为了临时保护数据不被下一个函数污染, 叶子函数不存在这种风险,所以不需要进行保护处理,直接使用寄存器即可
ARM64下,函数的参数通常情况下是存放在X0到X7(W0到W7)这8个寄存器里面的.如果超过8个参数,就会入栈.(一是跟参数个数有关,另外还更数据结构有关,指针占用8个字节刚好一个64位寄存器, 如果仓鼠类型超出8个字节,即存放到其他地方,比如栈空间)
函数的返回值通常情况下是放在X0 寄存器里面的.
称为或指令, 进行或运算, https://blog.csdn.net/qq_39416311/article/details/102762635
1 | orr w8,wzr,#0x1 ;将立即数0x1和0进行或运算, 然后复制给w8 |
假如有两个函数A和B,它们的调用链为:A–>B–>A
在高级语言中,A函数进行了复用,但是在汇编当中并没有复用的概念,每调用一个函数便开辟一次栈空间, 因此哪怕是调用同一个函数,如果递归嵌套次数过多,就会造成内存溢出
cpsr(current program status registers)寄存器
CPSR和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义.而CPSR寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息.
要想在算数运算是影响标记寄存器的值,必须在指令后面加上s,比如:
1 | add--->adds |
注:CPSR寄存器是32位的

CPSR的第31位是 N,符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0.
注意,在ARM64的指令集中,有的指令的执行时影响状态寄存器的,比如add\sub\or等,他们大都是运算指令(进行逻辑或算数运算);
CPSR的第30位是Z,0标志位。它记录相关指令执行后,其结果是否为0.如果结果为0.那么Z = 1.如果结果不为0,那么Z = 0.
对于Z的值,我们可以这样来看,Z标记相关指令的计算结果是否为0,如果为0,则N要记录下”是0”这样的肯定信息.在计算机中1表示逻辑真,表示肯定.所以当结果为0的时候Z = 1,表示”结果是0”.如果结果不为0,则Z要记录下”不是0”这样的否定信息.在计算机中0表示逻辑假,表示否定,所以当结果不为0的时候Z = 0,表示”结果不为0”。
CPSR的第29位是C,进位标志位。一般情况下,进行无符号数的运算。
加法运算:当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。
减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。
对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N - 1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。如下图所示:

我们知道,当两个数据相加的时候,有可能产生从最高有效位想更高位的进位。比如两个32位数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在32位中无法保存,我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM下就用C位来记录这个进位值。比如,下面的指令
1 | mov w0,#0xaaaaaaaa;0xa 的二进制是 1010 |
当两个数据做减法的时候,有可能向更高位借位。再比如,两个32位数据:0x00000000 - 0x000000ff,将产生借位,借位后,相当于计算0x100000000 - 0x000000ff。得到0xffffff01 这个值。由于借了一位,所以C位 用来标记借位。C = 0.比如下面指令:
1 | mov w0,#0x0 |
CPSR的第28位是V,溢出标志位。在进行有符号数运算的时候,如果超过了机器所能标识的范围,称为溢出。
adrp(address page):地址页,用于计算指定数据所在物理地址和当前pc地址之间的偏移量, 也就是说通过该指令计算出常量的物理地址
1 | adrp x0,1 |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
需要掌握一定的计算机基础知识 懂任意一门编程语言最佳
涵盖x86 ARM MIPS三大主流架构汇编语言入门知识
68课时 帮你快速入门汇编语言技能
内容全面 涵盖市面上主流x86 ARM MIPS三大架构汇编语言
讲解通俗易懂 适合零基础的同学快速入门学习
实战结合 将语法知识拆分成若干个小细节 分别进行相应的实战操作 帮助大家快速理解并上手
第一部分: 三大汇编语言基础精讲
第二部分: 8086汇编语言讲解
第三部分: MIPS汇编语言讲解
第四部分: ARM汇编语言讲解
移动端讲师、作家、开发者、独立音乐人
《smali语言从门到精通》《安卓进阶之逆向安卓反编译从0到1》《硬件进阶之三大架构汇编语言入门》系列视频作者 九年移动开发经验 旨在将复杂的事情简单化
欢迎大家来学习 一块进步~

本文为作者原创 转载时请注明出处 谢谢

1 | assume cs:code,ds:data,ss:stack |
使用数据别名优化代码
第一种:
1 | assume cs:code,ds:data,ss:stack |
第二种:
1 | assume cs:code,ds:data,ss:stack |
知识点:
1 | 第一种 方案使用str: |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
1 | assume cs:code,ds:data,ss:stack |
使用标号即可:
1 | assume cs:code,ds:data,ss:stack |
由于print函数默认执行,为了保证在call时才执行, 咱们将print函数代码移至中断后:
1 | assume cs:code,ds:data,ss:stack |
该代码存在一个问题,就是调用print函数后程序无法终止, 此时加上ret优化 ,最终代码为:
1 | assume cs:code,ds:data,ss:stack |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
## 场景
当我们需要在内存中申请一块空间,可以使用伪指令db和dw
1 | db-->define byte 定义字节 |
如果按照以下写法:
1 | assume cs:code |
以上代码存在一个问题, 由于数据是在代码段中定义, cpu默认将数据识别为代码, 将导致数据不可用,那么解决办法为,增加入口标记:
1 | assume cs:code |
标记是为了告诉编译器代码段入口位置, 这样就能保证db数据不被识别为指令
1 | assume cs:code |
1 | assume cs:code |
1 | assume cs:code,ds:data,ss:stack |
事实上我们使用的段其实是一个逻辑概念,即是我们自己定义的,
再说白了,我定义一个段,我说它是数据段那它就是数据段,我说它是代码段那么它就是代码段,
它们其实都是一块连续的内存而已,至于为什么要区分为数据段和代码段,
很明显,是用来给我们编程提供方便的,即我们在自己的思想上或者说是编码习惯上规定,
数据放数据段中,代码放代码段中 。而我们在使用数据段的时候,为了方便或者说是代码的编写方便起见,
我们一般把数据段的段地址放在 DS 寄存器中,当然,如果你硬要觉得 DS 不顺眼,那你可以换个 ES 也是一样的,但是换成CS则不行,因为CS指向的数据都被当成指令进行处理,如果换成SS呢,可行,但是读取数据需要使用pop,修改数据需要使用push,如果是用DS或者ES ,可以直接使用DS:[0]这种形式进行内存数据的读写
被DS和ES指向的内存空间的数据被cpu当作数据处理,被SS指向的内存空间的数据被cpu当作是栈空间,被CS指向的内存空间的数据被cpu当作指令进行执行
看注释说明:
1 | assume cs:code,ds:data,ss:stack |
本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站
通过加壳的形式或者log插桩获取
直接抽取so文件 然后构建一个新app
使用ida工具静态分析so库
使用xposed工具动态获取
原料:
一台root设备, 推荐使用模拟器
xposed是一个第三方的app, 用于hook代码, 可以简单理解为代码拦截,
该app存在的作用:
插件其实就是一个独立的app, 只不过该app内部自定义了一些标识能够被xposed框架app识别, xposed框架将我们编写好的带有标识的app当成插件进行管理, 插件利用xposed框架中集成好的hook环境实现hook操作

本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站