乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

x86汇编语言之and和or指令以及ascii码

发表于 2020-12-22

And指令

是逻辑运算指令, 按位进行与运算, 也就是需要转成二进制进行运算, 1代表真, 0代表假

1
2
3
4
mov al,1111 1111B ;或者写成十六进制FFH的形式也可
and al,0000 1111B ;将al中的值和0000 1111B进行与运算, 然后将结果赋值给al

;结果为 0000 1111B

or指令

按位进行或运算

1
2
3
4
mov al,1111 1111B 
or al,0000 1111B

;结果为1111 1111B

ascii码

1
mov al 'a' ;该行代码将字符a所对应的ascii码传入al寄存器中

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

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

汇编语言之MIPS汇编

发表于 2020-12-21

简介

咱们知道x86架构cpu用于PC端和工作站较多,ARM架构cpu常见于手机和单片机,那么MIPS架构的cpu主要在哪些设备可以找到它们的身影呢?

  • 中国龙芯
  • PS游戏机

学习环境搭建

  • 安装JDK, 主要用于运行mips模拟器mars
  • MARS模拟器:https://courses.missouristate.edu/KenVollmar/mars/download.htm

寄存器

在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 :这两个寄存器特别用来保存乘法、除法、乘法累加的结果。

MIPS汇编中的分段处理

1
2
3
.data #数据段

.text #代码段

传送指令

  1. 加载立即数指令 li

li(load immediate) :用于将立即数传送给寄存器

1
li $t0,1  ;十六进制数据使用0x前缀表示
  1. 加载地址指令 la

la(load address) :用于将地址传送至寄存器中, 多用于通过地址获取数据段中的地址

1
2
3
4
5
.data 
msg: .ascii "hello world"

.text
la $a0,msg # 将字符串数据所在的地址赋值给$a0寄存器
  1. 寄存器数据传送指令move

用于将一个寄存器中的数据传送至另一个寄存器当中

1
move $t0,$t1  # 将寄存器$t1中的数据传送至$t0

系统服务指令 syscall

在C语言中输出文本可以使用printf函数,但是汇编中没有printf这么一说,如果想要输出文本,需要借助syscall指令

如果想要输出一个数字1,那么syscall指令从$a0寄存器中取出需要输出的数据

因此, 你在执行syscall指令之前需要将数据提前放入$a0之中:

1
2
li $a0,1
syscall

同时,还需要指定输出的数据类型,数据类型的指定保存在$v0寄存器中

1
2
# $v0=1, syscall--->print_int
# $v0=4, syscall--->print_string

$v0存入1,表示syscall将$a0中的数据当做数字输出

$v0存入4,表示syscall将$a0中的数据当做数据的地址,然后输出对应的数据

syscall指令读写对照表

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
2
3
4
5
6
7
8
.data 

msg: .ascii "hello world\0" #类似于C语言中 char* msg="hello world"

.text
la $a0,msg
li $v0,4
syscall

数据定义

  1. 定义整型数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    # 打印Integer数据
    .data
    age: .word 23 #一个字长数据32位

    .text
    li $v0,1
    lw $a0,age
    syscall


    #========================#
    #加法运算
    .data
    number1: .word 2
    number2: .word 5

    .text
    lw $t0,number1
    lw $t1,number2

    add $t2,$t0,$t1

    li $v0,1
    move $a0 $t2
    syscall

    # 乘法运算 使用mul
  2. 定义Float数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #打印float数据
    .data
    PI: .float 3.14

    .text
    li $v0,2
    lwc1 $f12,PI
    syscall

    #float数据 算数运算
    .data
    f1: .float 3.14
    f2: .float 3.15
    .text
    li $v0,2
    lwc1 $f1,f1
    lwc1 $f2,f2
    add.s $f3,$f2,$f1 #带.s后缀的指令皆为浮点单精度指令
    mov.s $f12,$f3
    syscall
  3. 定义Double数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #打印double数据
    .data
    ayDouble: .double 7.20
    zeroDouble: .double 0.0
    .text

    ldc1 $f2,ayDouble
    ldc1 $f0,zeroDouble
    li $v0,3
    add.d $f12,$f2,$f0 #带.d后缀的指令皆为浮点双精度指令
    syscall
  4. 定义字符串数据

    1
    2
    3
    4
    5
    6
    7
    #打印double数据
    .data
    msg: .ascii "hello world"
    .text
    li $v0,4
    la $a0,msg
    syscall

用户输入

  1. 字符串输入

    1
    2
    3
    4
    5
    6
    7
    .data
    userInput: .space 20 #声明一块空间 默认存放0
    .text
    li $v0,8
    la $a0,userInput #将用户的输入存放至userInput中
    li $a1,20 #限制用户输入, 一旦超过20个默认回车
    syscall
  2. 整型数据输入

    1
    2
    3
    4
    # 输入的结果系统会存放在$v0寄存器
    .text
    li $v0,5
    syscall
  1. 浮点型数据输入

    1
    2
    3
    4
    #以float为例  输入的结果会存放在$f0寄存器
    .text
    li $v0,6
    syscall

单精度和双精度

单精度数(float型)在32位计算机中存储占用4字节,也就是32位,有效位数为7位,小数点后6位。

双精度数(double型)在32位计算机中存储占用8字节,也就是64位,有效位数为16位,小数点后15位。

浮点寄存器

在mips中一共有32个浮点寄存器(其中包含16个双精度浮点寄存器),用于单独处理浮点数

函数声明和调用

  1. 函数声明

    • 格式

      1
      2
      3
      函数名:
      函数体
      jr $ra #$ra寄存器中保存着调用指令下一条代码所在的地址
    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      .data

      .text
      show:
      li $v0,1
      li $a0,3
      syscall

      jr $ra ;jump registers
  1. 函数调用

    • 格式

      1
      jal 函数名
    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      .data

      .text
      jal show #调用函数时将下一条指令的地址存放至$ra寄存器

      #结束程序
      li $v0,10
      syscall

      show:
      li $v0,1
      li $a0,3
      syscall

      jr $ra
  2. 函数传参和返回值

    在mips汇编中,函数的参数一般放在$a系列寄存器当中,最多对应4个参数,超过4个部分使用栈储存. 函数的变量和返回值一般放在$v系列寄存器当中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #需求:定义加法函数 并调用获取返回值int sum(int v,int b)
    main:
    addi $a1,$zero,50
    addi $a2,$zero,100

    jal add

    li $v0,1
    move $a0,$v1
    syscall

    #结束程序
    li $v0,10
    syscall

    add:
    add $v1,$a1,$a2
    jr $ra
  3. 嵌套函数

    由于每执行jal调用一次函数, 就会刷新$ra寄存器中的值,因此,在嵌套函数调用之前,需要临时保存上一次$ra中的值,使用栈空间临时保护即可

栈操作

  1. 栈空间拉伸和平衡

    1
    2
    3
    addi $sp,$sp,-4 #栈拉伸 拉伸4个字节空间

    addi $sp,$sp,4 #栈平衡
  2. 入栈和出栈

    1
    2
    3
    sw $s0 ,0($sp) #入栈   往内存中写入数据

    lw $s0, 0($sp) #出栈 从内存中读取数据
  3. 嵌套函数使用栈保护$ra代码示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    main:
    addi $a1,$zero,50
    addi $a2,$zero,100

    jal add

    li $v0,1
    move $a0,$v1
    syscall

    #结束程序
    li $v0,10
    syscall

    add:
    addi $sp,$sp,-4 #栈拉伸
    sw $ra ,0($sp) #入栈

    jal sub
    add $v1,$a1,$v1

    lw $ra, 0($sp) #出栈
    addi $sp,$sp,4 #栈平衡
    jr $ra

    sub:
    sub $v1,$a1,$a2
    jr $ra

内存空间布局

从mars中可以查看到内存分布起始物理地址

转成图后:

栈的伸缩在mips和x86架构中是由高地址往低地址进行伸缩, 在arm架构中可升序也可降序

内存碎片

在内存动态分配(heap区)过程中容易出现一些小且不连续的空闲内存区域,这些未被使用的内存称作内存碎片

分类:

  • 内部碎片:比如数据在内存中采用4个字节对齐的方式进行存储, 比如我们申请一块3个字节的空间用于存储一个数据,但是系统给我们分配了4个字节空间,这时多出来的一个字节的空间就被称之为内部碎片
  • 外部碎片:在我们进行内存回收和分配的时候容易出现外部碎片,比如我连续申请了三块4个字节的内存空间,当我释放第二块内存空间然后紧接着申请一块8个字节的空间,此时由于之前释放的4个字节空间太小无法使用,这就造成了内存块空闲,这种碎片叫做外部碎片

PC 寄存器

称作 程序计数寄存器(Program Counter Register) :用于存储程序即将要执行的指令所对应在内存中的实际物理地址, 如果改变该值可以让指令跳转到我们想要跳转的地方

如何修改pc寄存器中的值

使用以下转移指令

  1. jr指令

    1
    jr 寄存器  #$ra寄存器实际上就是保存着函数调用前的pc寄存器的值
  2. jal指令

    1
    jal 标号  #跳转的同时给$ra寄存器赋值
  3. j指令

    1
    j 标号   #直接跳转

内存数据的读写

  1. 从指定内存中读取数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    #整型数据
    lw $t0,0x10010000 #读取4个字节数据 赋值给t0寄存器
    ld $t0,0x10010000 #读取8个字节数据 赋值给t0和t1寄存器

    #单精度浮点数据
    lwc1 $f0,0x10010000


    #双精度浮点数据
    ldc1 $f0,0x10010000


    #字符数据
    ### 由于字符数据是以ascii码16进制的形式存放到内存中,因此只能获取到ascii码值,该值属于整型数据,直接使用lw或者ld即可
    lw $t0,0x10010000 #读取4个字节数据 赋值给t0寄存器
    ld $t0,0x10010000 #读取8个字节数据 赋值给t0和t1寄存器

    从内存中读取数据的宽度取决于寄存器的大小,由于32位cpu寄存器最大存储32位数据,因此lw $t0表示一次性读取4个字节的数据到$t0寄存器, 如果想要连续读取八个字节的数据,那么需要使用ld $t0,表示一次性读取8个字节的数据到$t0,$t1连个连续的寄存器,

  2. 往指定内存中写入数据

    1. 第一种 数据定义的同时指定物理地址
    1
    2
    3
    4
    5
    6
    .data 0x10010020 ;将以下定义的数据存放在0x10010020这个物理地址
    .ascii "a"
    .ascii "b"

    .data 0x10010000
    .ascii "c"
    1. 第二种 在代码段中使用指令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #整型数据
    li $s1,4
    sw $s1,0x10010000 ;将$s1寄存器中的数据存入0x10010000这个物理地址

    #单精度浮点数
    .data
    f1: .float 3.14

    .text
    lwc1 $f2,f1
    swc1 $f2,0x10010000


    #双精度浮点数
    .data
    d1: .double 3.14

    .text
    ldc1 $f2,d1
    sdc1 $f2,0x10010000

以上直接使用的是简单粗暴的十六进制表示物理地址,很多时候内存的地址会保存在寄存器中,你可能会看到以下写法:

1
2
3
4
5
6
7
8
9
10
11
lw $s1, $s2
sw $s1, $s2

或者
lw $s1, 20($s2)
sw $s1, 20($s2) ;将地址往高位偏移20个字节 相当于sw $s1, 20+$s2


或者
lw $s1, -20($s2)
sw $s1, -20($s2) ;将地址往低位偏移20个字节

注意: 往指定内存中读取写入数据时,代码段不允许直接写入和读取

一维数组的定义

数组本质上就是多个数据的集合,在内存中按照一定顺序排列,角标即为每个数据的偏移值,在mips中内存数据是按照4个字节进行对齐的,也就是说一个数据最少占用4个字节内存空间,因此数组中数据之间的偏移量固定为n*4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.data
array: .space 20 #别名的另外一种用法 通过array(寄存器)这种格式 寄存器中存放地址偏移地址量

.text
# $t0寄存器存放角标值 $s1中存放需要存入的值
li $s1,1
li $t0,0
sw $s1,array($t0) #相当于 sw $s1,array+$t0

li $s1,2
li $t0,4
sw $s1,array($t0)

li $s1,3
li $t0,8
sw $s1,array($t0)

数组的打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
.data
array: .space 20

.text
#初始化数组中的数据
li $s1,1
li $t0,0
sw $s1,array($t0)

li $s1,2
li $t0,4
sw $s1,array($t0)

li $s1,3
li $t0,8
sw $s1,array($t0)


#查找角标为2的数值
getData:
la $s1 ,array
li $a0,2
mul $a0,$a0,4
add $s1,$s1,$a0
lw $a0,0($s1)
li $v0,1
syscall

#将角标临时置为0 方便下面循环操作
li $t0,0
while:
beq $t0,12,exit
lw $t2,array($t0)

addi $t0,$t0,4

li $v0,1
move $a0,$t2
syscall
j while

exit:
li $v0,10
syscall
快速初始化数组数据的方法
1
2
.data
array: .word 20 :3 #批量定义3个整型数据20

分支跳转指令

  1. 整型数据分支比较跳转
  • bgt(branch if greater than):用于大于比较
1
2
3
4
bgt $t0,$t1,sub # 如果$t0中的数据大于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义


sub:
  • beq(branch equal):用于等于比较
1
2
3
4
beq $t0,$t1,sub # 如果$t0中的数据等于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义


sub:
  • ble(branch if less than):用于小于比较
1
2
3
4
ble $t0,$t1,sub # 如果$t0中的数据小于$t1,则跳转到sub分支,执行sub中的代码,否则,按照顺序执行bgt下面的代码, sub是一个代号,可以自定义


sub:

练习1: 将以下c代码转换成mips汇编代码:

1
2
3
4
5
6
7
8
scanf("%d",$a);
scanf("%d",$b);

if(a>b){
printf("YES");
}else{
printf("NO");
}

汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 用$t0指代a ,$t1指代b

.data
msg_yes: .ascii "YES\0" # \0表示字符串结尾
msg_no: .ascii "NO\0"


.text
li $v0,5 #控制syscall为读取integer状态
syscall # 此时io控制台显示光标,可输入数字,回车后将输入的数字保存在$v0中
move $t0,$v0 #由于接下来还需要使用$v0 ,为避免数据被覆盖掉 将输入的数据转移到$t0中进行临时保存

li $v0,5
syscall
move $t1,$v0


bgt $t0,$t1,sub
li $v0,4
la $a0,msg_no
syscall

#结束程序
li $v0,10
syscall
sub:
li $v0,4
la $a0,msg_yes
syscall

练习2: 将以下c代码转换成mips汇编代码:

1
2
3
4
5
6
7
8
9
10
11
//求累加之和
//1+2+3+.....+100

int i=1;
int s=0;

while(i<=100){
s=s+i;
i=i+1;
}
printf("%d",s);

汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 用$t0指代i ,$t1指代s

.text
li $t0 ,1
li $t1 ,0


loop:
# s=s+i;
add $t1,$t1,$t0
add $t0,$t0,1

ble $t0,100,loop


move $a0,$t1
li $v0,1
syscall
  1. 浮点型数据分支比较
  • 小于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    .data
    num1: .float 3.14
    num2: .float 3.16

    .text

    lwc1 $f0,num1
    lwc1 $f1,num2

    c.lt.s $f0,$f1 #关键代码
    bc1t sub #bc1t表示条件满足 bc1f表示不满足条件


    sub:
  • 等于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    .data
    num1: .float 3.14
    num2: .float 3.16

    .text

    lwc1 $f0,num1
    lwc1 $f1,num2

    c.eq.s $f0,$f1 #关键代码 c表示coproc协处理寄存器 s表示single单精度
    bc1t sub #bc1t表示条件满足 bc1f表示不满足条件


    sub:
  • 小于等于

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    .data
    num1: .float 3.14
    num2: .float 3.16

    .text

    lwc1 $f0,num1
    lwc1 $f1,num2

    c.le.s $f0,$f1 #关键代码
    bc1t sub #bc1t表示条件满足 bc1f表示不满足条件


    sub:

    以上是单精度浮点数据的比较示例,如果是双精度,只需将结尾.s改成.d即可

mips多文件开发

在文件A中定义函数

1
2
3
4
5
fun:
li $v0,1
li $a0,1
syscall
jr $ra

在文件B中使用关键字.include引用A文件中的函数

1
2
3
4
.text
jal fun

.include "A.asm"

所有文件必须在同一目录下

宏

  1. 宏替换

    全局替换,使用我们之前学过的.include伪指令进行替换

  2. 宏匹配

在汇编中,如果我们要依次打印1,2,3三个整数,那么汇编如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print1:
li $v0,1
li $a0,1
syscall
jr $ra

print2:
li $v0,1
li $a0,2
syscall
jr $ra

print2:
li $v0,1
li $a0,3
syscall
jr $ra

我们发现使用标签的方式定义函数,当函数体内容存在不确定变量值时,代码非常冗余, 如果使用高级语言进行封装的话,我们一般一个函数就搞定了:

1
2
3
void print(int a){
print(a);
}

有没有办法使得汇编能像高级语言一样简洁呢?

在MARS中给我们提供了一个扩展伪指令,叫做宏匹配

宏匹配使用的格式如下:

1
2
3
.macro 别名
#汇编指令...
.end_macro

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
li $v0,10
syscall

#比如我们要对以上两行指令使用宏匹配进行封装

#封装结果为
.macro exit
li $v0,10
syscall
.end_macro


#在代码中引用
.text
exit #直接使用别名调用

如果我们要封装一个打印整型数据的函数,那么我们可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
#封装结果为
.macro print_int(%param)
li $v0,1
li $a0,%param
syscall
.end_macro


#在代码中引用
.text
print_int(1) #直接使用别名调用
print_int(2)
print_int(3)

这样是不是和高级语言没什么区别啦

打印字符串封装示例:

1
2
3
4
5
6
7
8
9
10
11
.macro print_str (%str)
.data
myLabel: .asciiz %str
.text
li $v0, 4
la $a0, myLabel
syscall
.end_macro

print_str ("test1")
print_str ("test1")

然后结合我们之前学过的多文件开发,完全可以将这个封装好的函数单独放在一个文件中,直接在头部.include就行

  1. 宏定义

    全局定义,如果我们想给一个数据或者寄存器,甚至是一行代码取个别名,然后在代码中使用别名的方式指代,那么可以使用宏定义指令.eqv 别名的好处就是方便我们进行记忆

    1
    2
    3
    .eqv  LIMIT      20 #给20这个立即数取个别名为LIMIT
    .eqv CTR $t2
    .eqv CLEAR_CTR add CTR, $zero, 0

    当我们有以下代码:

    1
    2
    3
    4
    .text
    li $v0,1
    add $t2, $zero, 0
    li $t0,20

    如果我们使用宏定义,我们可以写成如下:

    1
    2
    3
    4
    5
    6
    7
    8
    .eqv  LIMIT      20 #给20这个立即数取个别名为LIMIT
    .eqv CTR $t2
    .eqv CLEAR_CTR add CTR, $zero, 0

    .text
    li $v0,1
    CLEAR_CTR
    li $t0,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
2
3
4
5
6
addr=baseAddr+(rowIndex*colSize+colIndex)*dataSize

实际地址=首地址+(第几行*总列数+第几列)*数据占用的宽度

比如:我要计算arr[2][1]的实际物理地址, 那么
实际地址=0x00000000+(2*3+1)*4=0x00000000+0x0000001C=0x0000001C

第二种是 列不动,行轮动

内存地址 二维坐标
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
2
3
4
5
6
addr=baseAddr+(colIndex*rowSize+rowIndex)*dataSize

实际地址=首地址+(第几列*行数+第几行)*数据占用的宽度

比如:我要计算arr[2][1]的实际物理地址, 那么
实际地址=0x00000000+(1*3+2)*4=0x00000000+0x00000014=0x00000014

使用mips汇编实现二维数组定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}};


#以下是以 行不动 列轮动的方式实现
.data
da: .word 1,2,3,5,6,7,10,11,12
array: .space 36

.text
# void initArry(*arr,row,col,index)
initArry:
#首地址
la $a0,array
#第几行
li $a1,0
#第几列
li $a2,0
#存第几个数
li $a3,0

while:
# arr[x][y]
bgt $a1,2,exit
# 避免寄存器中的参数被子函数覆盖 将数据放置在栈中临时保存
add $sp,$sp,-12
sw $a1,8($sp)
sw $a2,4($sp)
sw $a3,0($sp)

jal saveDataToMemory
#从栈中恢复局部变量值
lw $a1,8($sp)
lw $a2,4($sp)
lw $a3,0($sp)
add $sp,$sp,12

#累加计数
add $a3,$a3,4
#列轮动
addi $a2,$a2,1
bgt $a2,2,sub

j while

sub:
li $a2,0
add $a1,$a1,1
j while

saveDataToMemory:
# 避免寄存器中的参数被子函数覆盖 将数据放置在栈中临时保存
add $sp,$sp,-4
sw $ra,0($sp)

#计算数据存放的物理地址
jal getAddr

#获取需要存放的数据
lw $t0,da($a3)
#将数据存入指定位置中
sw $t0,0($v0)

lw $ra,0($sp)
add $sp,$sp,4
jr $ra

#算法部分
getAddr:
#实际地址=首地址+(第几行*总列数+第几列)*数据占用的宽度
mul $a1,$a1,3
add $a2,$a2,$a1
mul $a2,$a2,4
add $v0,$a2,$a0

jr $ra


exit:
#程序结束之前测试数据能否正常取出
jal getDataFromArry
li $v0,10
syscall




#获取指定坐标位置的数据arr[2][1] 输出值为11
getDataFromArry:
#第几行
li $a1,2
#第几列
li $a2,1
#计算物理地址
jal getAddr
#获取数据
lw $t0,0($v0)

#打印数据
move $a0,$t0
li $v0,1
syscall

列不不动 行轮动方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}};


#以下是以 列不动,行轮动的方式实现
.data
da: .word 1,2,3,5,6,7,10,11,12
array: .space 36

.text
# void initArry(*arr,row,col,index)
initArry:
#首地址
la $a0,array
#第几行
li $a1,0
#第几列
li $a2,0
#存第几个数
li $a3,0

while:
# arr[x][y]
bgt $a2,2,exit
# 避免寄存器中的参数被子函数覆盖 将数据放置在栈中临时保存
add $sp,$sp,-12
sw $a1,8($sp)
sw $a2,4($sp)
sw $a3,0($sp)

jal saveDataToMemory
#从栈中恢复局部变量值
lw $a1,8($sp)
lw $a2,4($sp)
lw $a3,0($sp)
add $sp,$sp,12

#累加计数
add $a3,$a3,4
#行轮动
addi $a1,$a1,1
bgt $a1,2,sub

j while

sub:
li $a1,0
add $a2,$a2,1
j while

saveDataToMemory:
# 避免寄存器中的参数被子函数覆盖 将数据放置在栈中临时保存
add $sp,$sp,-4
sw $ra,0($sp)

#计算数据存放的物理地址
jal getAddr

#获取需要存放的数据
lw $t0,da($a3)
#将数据存入指定位置中
sw $t0,0($v0)

lw $ra,0($sp)
add $sp,$sp,4
jr $ra

#算法部分
getAddr:
#实际地址=首地址+(第几列*行数+第几行)*数据占用的宽度
mul $a2,$a2,3
add $a1,$a1,$a2
mul $a1,$a1,4
add $v0,$a1,$a0

jr $ra


exit:
#程序结束之前测试数据能否正常取出
jal getDataFromArry
li $v0,10
syscall




#获取指定坐标位置的数据arr[2][1] 输出7
getDataFromArry:
#第几行
li $a1,2
#第几列
li $a2,1
#计算物理地址
jal getAddr
#获取数据
lw $t0,0($v0)

#打印数据
move $a0,$t0
li $v0,1
syscall

更为简便的方法实现二维数组的搭建

由于数组中数据是在内存中连续进行排列存储的,那么我们可以之间将数据 依次存入内存之中,然后使用算法进行数据获取即可(以下示例皆采用 行不动,列动 的方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}};


#由于数组中的数据是存放在堆内存中,需要在程序执行时动态分配


.text
#堆地址从0x10040000开始
la $a0,0x10040000
#将数据按照顺序存放至内存中
jal initData

#获取数据
jal getDataFromArry
j exit

initData:
li $s1,1
sw $s1,0($a0)

li $s1,2
sw $s1,4($a0)

li $s1,3
sw $s1,8($a0)

li $s1,5
sw $s1,12($a0)

li $s1,6
sw $s1,16($a0)

li $s1,7
sw $s1,20($a0)

li $s1,10
sw $s1,24($a0)

li $s1,11
sw $s1,28($a0)

li $s1,12
sw $s1,32($a0)


jr $ra


#算法部分
getAddr:
#实际地址=首地址+(第几行*总列数+第几列)*数据占用的宽度
mul $a1,$a1,3
add $a2,$a2,$a1
mul $a2,$a2,4
add $v0,$a2,$a0

jr $ra


exit:
#程序退出
li $v0,10
syscall




#获取指定坐标位置的数据arr[2][1] 输出值为11
getDataFromArry:
#第几行
li $a1,2
#第几列
li $a2,1
#计算物理地址
jal getAddr
#获取数据
lw $t0,0($v0)

#打印数据
move $a0,$t0
li $v0,1
syscall

再简化一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#需求:实现int a[3][3] = {{1, 2, 3}, {5, 6, 7}, { 10, 11, 12}};


.data
da: .word 1,2,3,5,6,7,10,11,12
# 假如以上数据是动态写入的 当做数组中的数据来用

.text
#那么只需要提供首地址然后配合算法就能获取到指定坐标的数据
la $a0,da
jal getDataFromArry
j exit


#算法部分
getAddr:
#实际地址=首地址+(第几行*总列数+第几列)*数据占用的宽度
mul $a1,$a1,3
add $a2,$a2,$a1
mul $a2,$a2,4
add $v0,$a2,$a0

jr $ra


exit:
#程序退出
li $v0,10
syscall




#获取指定坐标位置的数据arr[2][1] 输出值为11
getDataFromArry:
#第几行
li $a1,2
#第几列
li $a2,1
#计算物理地址
jal getAddr
#获取数据
lw $t0,0($v0)

#打印数据
move $a0,$t0
li $v0,1
syscall

按照正常的编程思维,我们一般使用第一种行不动 列动的存储方式 第一维为行,第二维为列,如果你想改成行动存储方式,有两种方法:要么将数据的存储顺序进行变动,配合第二种算法,要么将第二维当成行,第一维当成列,配合第二种算法进行处理

Mips汇编指令汇总表

类别

指令名称

实例

含义

注释

英文注解

算

数

加法

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

Mips内存结构图:

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

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

Android系统目前支持CPU架构都有哪些

发表于 2020-12-14

7种CPU架构

  • armeabi (ARM v5):32位cpu 属于 第5代、第6代早期的ARM处理器
  • armeabi-v7a (ARM v7):32位cpu 属于 第7代的 ARM 处理器 从2010年起
  • arm64-v8a (ARM v8): 第8代、64位ARM处理器
  • x86 : 32位处理器 从2011年起
  • x86_64 : 64位处理器 从2014年起
  • MIPS : 32位处理器 从2012年起
  • MIPS64 : 64位处理器 从2014年起

兼容和文件读取顺序

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库,从而兼容市面上所有的设备

只保留armeabi存在的问题

所有的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等等)

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

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

汇编语言之ARM64汇编

发表于 2020-12-14

寄存器

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直接从高速缓存依次读取指令来执行.

通用寄存器

  • ARM64拥有有31个64位的通用寄存器 x0 到 x30,这些寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)
    • 那么w0 到 w28 这些是32位的. 因为64位CPU可以兼容32位.所以可以只使用64位寄存器的低32位.
    • 比如 w0 就是 x0的低32位!

数据地址寄存器

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

  • 64位: X0-X30, XZR(零寄存器 ,里面存放数据0)
  • 32位: W0-W30, WZR(零寄存器)

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

浮点和向量寄存器

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

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

现在的CPU支持向量运算.(向量运算在图形处理相关的领域用得非常的多)为了支持向量计算系统了也提供了众多的向量寄存器.

  • 向量寄存器 128位:V0-V31

PC寄存器(program counter)

为指令指针寄存器, 它指示了CPU当前要读取指令的地址, 类似于x86汇编种的cs+ip

SP和FP寄存器

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

注意: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
2
3
4
5
6
7
8
9
;利用栈进行数据交换
.text
.global _A
_A:
sub sp,sp,#0x20 ;sp=sp-0x20 开辟一个32字节的占空间
stp x0,x1,[sp,#0x10] ;然后将x0和x1中的数据存入sp+0x10所指向的栈空间 []相当于是获取指定地址的空间,不会改变sp原来的值,如果想改变sp的值只需在末尾加上! 也就是[sp,#0x10]! 即可
ldp x1,x0,[sp,#0x10] ;读取sp+0x10这块栈空间中的数据存放至x1,x0寄存器中
add sp,sp,#0x20 ;栈平衡, 释放内存空间
ret ;返回至调用指令下一行

stur指令: 偏移量为减时使用 . stur w0, [x29, #0x8] 偏移量为负的 将寄存器w0的值存入x29 - 0x8 的内存地址

[sp]: sp保存栈空间的地址值, [sp]表示取值,获取所对应的空间 和8086中的[bx]是类似的

另外 汇编简写

1
2
3
4
5
6
7
8
9
10
11
12
stp x29,x30,[sp,#-0x10]! ;尾部多了一个!号

;相当于一下两行代码
sub sp,sp,#0x10
stp x29,x30,[sp]
;或者直接理解为加了!号的sp值会发生改变


ldp x29,x30 ,[sp],#x010
;相当于以下两行代码
ldp x29,x30,[sp]
add sp,#0x10

bl指令

  • CPU从何处执行指令是由pc中的内容决定的,我们可以通过改变pc的内容来控制CPU执行目标指令
  • ARM64提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值,比如
    • mov x0,#10、mov x1,#20
  • 但是,mov指令不能用于设置pc的值,ARM64没有提供这样的功能
  • ARM64提供了另外的指令来修改PC的值,这些指令统称为转移指令,最简单的是bl指令

类似于x86汇编中的, call

bl标号

  • 将下一条指令的地址放入lr(x30)寄存器
  • 转到标号处执行指令

ret

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

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

x30寄存器

x30寄存器存放的是函数的返回地址.当ret指令执行时刻,会寻找x30寄存器保存的地址值!

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

arm代码示例

1
2
3
4
5
6
7
8
9
10
11
12
.text ;代码段
.global _A,_B ;定义两个全局函数 A和B

_A:
mov x0 ,#0xa0 ;arm汇编中数据用#开头
mov x1 ,#0x00
add x1 ,x0,#0x14 ;x1=x0+0x14
ret ;返回到bl指令所对应的下一条指令

_B:
add x0, x0,#0x10
ret

寄存器和栈

寄存器是全局容器,所有函数共用,但是栈不一样,一个函数占用独有的栈空间, 在各个函数嵌套调用时,寄存器很容易被覆盖读写,这个时候为了保持寄存器的数据不被改变,通常结合栈临时保存寄存器中的值,然后函数ret之前将数据恢复,这样就能确保上一个函数的数据不被改变,也就是实现了将寄存器当做局部变量使用

栈的对齐

ARM64里面 对栈的操作是16字节对齐的, 也就是一次开辟栈空间至少是16字节, 或者是16的倍数, 如果不是这个值会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.text ;代码段
.global _A,_B ;定义两个全局函数 A和B

_A:
mov x0 ,#0xaaaa ;arm汇编中数据用#开头
str x30,[sp,#-0x10]! ;在调用下一个函数之前临时保存lr寄存器中的地址, lr保存bl _A的下一条指令地址
bl _B
mov x0,#0xcccc
ldr x30,[sp],#0x10 ;lr恢复
ret ;返回到bl指令所对应的下一条指令

_B:
add x0, x0,#0x10
ret

既然sp一次最少拉伸16个字节, 那么以下函数需要拉伸多少空间:

1
2
3
4
5
6
void sum(int a, int b){
int c=3;
int c=4;
int c=5;

}

由于int类型的数据占用4个字节空间, 这里一共有5个int,那么需要占用5*4=20个字节的空间, 那么sp一次性拉伸0x20也就是32字节的栈空间

1
2
3
16位寄存器-->最大装2个字节数据-->0xFFFF
32位寄存器-->最大装4个字节数据-->0xFFFFFFFF
64位寄存器-->最大装8个字节数据-->0XFFFFFFFFFFFFFFFF

如果函数里面又调用了函数,那么sp拉伸多少呢?

1
2
3
4
5
6
void sum(int a, int b){
int c=3;
int c=4;
int c=sumb(a,b);

}

由于bl调用函数之前会复写x30(lr)寄存器中的值, 所以需要将x29和x30寄存器进行临时保护, 这两个寄存器占用16个字节, 加上sum函数的局部变量和参数所占的16个字节,一共是32个字节

叶子函数

函数体中没有调用其他函数的函数称之为叶子函数,又称为末尾函数

这种函数在编写汇编代码时可以省略使用栈空间, 栈空间是为了临时保护数据不被下一个函数污染, 叶子函数不存在这种风险,所以不需要进行保护处理,直接使用寄存器即可

ARM64方法返回值

ARM64下,函数的参数通常情况下是存放在X0到X7(W0到W7)这8个寄存器里面的.如果超过8个参数,就会入栈.(一是跟参数个数有关,另外还更数据结构有关,指针占用8个字节刚好一个64位寄存器, 如果仓鼠类型超出8个字节,即存放到其他地方,比如栈空间)
函数的返回值通常情况下是放在X0 寄存器里面的.

orr指令

称为或指令, 进行或运算, 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
2
add--->adds
sub--->subs

注:CPSR寄存器是32位的

  • CPSR的低8位(包括I、F、T和M[4:0])称为控制位,程序无法修改,除非CPU运行于特权模式下,程序才能修改控制位!
  • N、Z、C、V均为条件码标志位。它们的内容可被算术或逻辑运算的结果所改变,并且可以决定某条指令是否被执行!意义重大!

N(Negative)标志

CPSR的第31位是 N,符号标志位。它记录相关指令执行后,其结果是否为负.如果为负 N = 1,如果是非负数 N = 0.

   注意,在ARM64的指令集中,有的指令的执行时影响状态寄存器的,比如add\sub\or等,他们大都是运算指令(进行逻辑或算数运算);

Z(Zero)标志

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”。

C(Carry)标志

CPSR的第29位是C,进位标志位。一般情况下,进行无符号数的运算。
加法运算:当运算结果产生了进位时(无符号数溢出),C=1,否则C=0。
减法运算(包括CMP):当运算时产生了借位时(无符号数溢出),C=0,否则C=1。

   对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N - 1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位。如下图所示:

进位

   我们知道,当两个数据相加的时候,有可能产生从最高有效位想更高位的进位。比如两个32位数据:0xaaaaaaaa + 0xaaaaaaaa,将产生进位。由于这个进位值在32位中无法保存,我们就只是简单的说这个进位值丢失了。其实CPU在运算的时候,并不丢弃这个进位制,而是记录在一个特殊的寄存器的某一位上。ARM下就用C位来记录这个进位值。比如,下面的指令

1
2
3
4
5
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

借位

   当两个数据做减法的时候,有可能向更高位借位。再比如,两个32位数据:0x00000000 - 0x000000ff,将产生借位,借位后,相当于计算0x100000000 - 0x000000ff。得到0xffffff01 这个值。由于借了一位,所以C位 用来标记借位。C = 0.比如下面指令:

1
2
3
4
mov w0,#0x0
subs w0,w0,#0xff ;
subs w0,w0,#0xff
subs w0,w0,#0xff

V(Overflow)溢出标志

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

  • 正数 + 正数 为负数 溢出
  • 负数 + 负数 为正数 溢出
  • 正数 + 负数 不可能溢出

adrp指令

adrp(address page):地址页,用于计算指定数据所在物理地址和当前pc地址之间的偏移量, 也就是说通过该指令计算出常量的物理地址

1
2
3
4
5
6
7
adrp x0,1
;1.将1的值左移12位, 1 0000 0000 0000 ==0x1000
;2.将pc寄存器的低12位清零
;3.清零之后的值加上0x1000 然后将最后结果赋值给x0寄存器


;adrp指令后边的数值1为十六进制

内存分区

  • 代码区:可读可写可执行
  • 栈区: 可读可写
  • 堆区:动态申请, 可读可写
  • 全局变量区:可读可写
  • 常量区:只读

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

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

程序员进阶之三大架构汇编语言入门视频教程-胖薯出品

发表于 2020-12-14

适用人群

  • 计算机爱好者
  • 程序员
  • 硬件开发者

课程要求

需要掌握一定的计算机基础知识 懂任意一门编程语言最佳

课程概述

涵盖x86 ARM MIPS三大主流架构汇编语言入门知识

课程特色

68课时 帮你快速入门汇编语言技能

课程亮点

  • 内容全面 涵盖市面上主流x86 ARM MIPS三大架构汇编语言

  • 讲解通俗易懂 适合零基础的同学快速入门学习

  • 实战结合 将语法知识拆分成若干个小细节 分别进行相应的实战操作 帮助大家快速理解并上手

课程大纲

  • 第一部分: 三大汇编语言基础精讲

  • 第二部分: 8086汇编语言讲解

  • 第三部分: MIPS汇编语言讲解

  • 第四部分: ARM汇编语言讲解

课程观看

  • 网易云课堂
  • 51CTO课堂
  • 面包多付费观看
  • Udemy课堂
  • B站免费观看
  • YouTube观看
  • 淘宝/拼多多赞助观看

作者介绍

移动端讲师、作家、开发者、独立音乐人

《smali语言从门到精通》《安卓进阶之逆向安卓反编译从0到1》《硬件进阶之三大架构汇编语言入门》系列视频作者 九年移动开发经验 旨在将复杂的事情简单化

欢迎大家来学习 一块进步~

455b581f5038499b9ad03f6dcbb7c3e6

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

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

8086汇编语言之dos打印hello world

发表于 2020-12-13

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
assume cs:code,ds:data,ss:stack

;数据段 代码段可直接获取数据段中数据, 相当于高级语言中的局部变量
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;数据段 代码段可直接获取数据段中数据, 相当于高级语言中的全局变量
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
db 'hello world!$' ;$ 表示结尾标记

data ends

code segment


start:
;设置ds和ss
mov ax,data
mov ds,ax
mov ax stack
mov ss,ax

;业务逻辑代码
mov dx,14h
mov ah 9h ;9h表示调用显存 从dx总读取偏移地址对应的数据
int 21h

;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义

使用数据别名优化代码

第一种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
assume cs:code,ds:data,ss:stack

;数据段 代码段可直接获取数据段中数据, 相当于高级语言中的局部变量
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;数据段 代码段可直接获取数据段中数据, 相当于高级语言中的全局变量
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
str: db 'hello world!$' ;$ 表示结尾标记 str:表示指向该数据的地址

data ends

code segment


start:
;设置ds和ss
mov ax,data
mov ds,ax
mov ax stack
mov ss,ax

;业务逻辑代码
mov dx,str
mov ah,9h ;9h表示调用显存 从dx总读取偏移地址对应的数据
int 21h

;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义

第二种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
assume cs:code,ds:data,ss:stack

;数据段 代码段可直接获取数据段中数据, 相当于高级语言中的局部变量
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;数据段 代码段可直接获取数据段中数据, 相当于高级语言中的全局变量
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
str db 'hello world!$' ;$ 表示结尾标记

data ends

code segment


start:
;设置ds和ss
mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax

;业务逻辑代码
mov dx,offset str;获取别名对应数据的偏移地址
mov ah, 9h ;9h表示调用显存 从dx总读取偏移地址对应的数据
int 21h

;退出程序
mov ah, 4ch
int 21h
code ends
end start ;标记名称可自定义

知识点:

1
2
3
4
5
6
7
8
9
10
第一种 方案使用str:
str: db 'hello world!$' ; str:表示指向该数据的地址
;业务逻辑代码
mov dx, str


第二种 方案使用offset
str db 'hello world!$' ;$ 表示结尾标记 str表示指向该数据的内容
;业务逻辑代码
mov dx,offset str;获取别名对应数据的偏移地址 如果直接使用str表示将hello world!中的h赋值给dx

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

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

8086汇编语言之函数的声明和调用

发表于 2020-12-13

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
assume cs:code,ds:data,ss:stack

;数据段
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;数据段
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
str db 'hello world!$' ;$ 表示结尾标记

data ends

code segment


start:
;设置ds和ss
mov ax,data
mov ds,ax
mov ax stack
mov ss,ax

;业务逻辑代码
mov dx,offset str;获取别名对应数据的偏移地址
mov ah 9ch ;9h表示调用显存 从dx总读取偏移地址对应的数据
int 21h

;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义

需求: 将打印Hello world!封装成一个函数

使用标号即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
assume cs:code,ds:data,ss:stack

;数据段
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;数据段
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
str db 'hello world!$' ;$ 表示结尾标记

data ends

code segment


start:
;设置ds和ss
mov ax,data
mov ds,ax
mov ax stack
mov ss,ax

print: ;函数名
;业务逻辑代码
mov dx,offset str;获取别名对应数据的偏移地址
mov ah 9ch ;9h表示调用显存 从dx总读取偏移地址对应的数据
int 21h

;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义

由于print函数默认执行,为了保证在call时才执行, 咱们将print函数代码移至中断后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
assume cs:code,ds:data,ss:stack

;数据段
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;数据段
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
str db 'hello world!$' ;$ 表示结尾标记

data ends

code segment


start:
;设置ds和ss
mov ax,data
mov ds,ax
mov ax stack
mov ss,ax

;业务逻辑代码
call print ;调用print函数

;退出程序
mov ah 4ch
int 21h

print: ;函数名
mov dx,offset str;获取别名对应数据的偏移地址
mov ah 9ch ;9h表示调用显存 从dx总读取偏移地址对应的数据
int 21h
code ends
end start ;标记名称可自定义

该代码存在一个问题,就是调用print函数后程序无法终止, 此时加上ret优化 ,最终代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
assume cs:code,ds:data,ss:stack

;数据段
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;数据段
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
str db 'hello world!$' ;$ 表示结尾标记

data ends

code segment


start:
;设置ds和ss
mov ax,data
mov ds,ax
mov ax stack
mov ss,ax

;业务逻辑代码
call print ;调用print函数

;退出程序
mov ah 4ch
int 21h

print: ;函数名
mov dx,offset str;获取别名对应数据的偏移地址
mov ah 9ch ;9h表示调用显存 从dx总读取偏移地址对应的数据
int 21h
ret
code ends
end start ;标记名称可自定义

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

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

8086汇编语言之Call和ret指令

发表于 2020-12-13

call

可以理解为高级语言中函数的调用

使用格式为:

1
call  标号/地址

执行流程:

* 先将下一条指令的偏移地址入栈
* 然后跳转到定位的标号地址执行指令

ret

可以理解为高级语言中的return

官方解释:

ret指令就是讲栈顶的值Pop给IP

栈平衡

编译器为了保证ret之后执行调用栈中预先通过call入栈的下一条指令, 需要将临时push进栈中的局部变量 和参数等进行pop, 从而保持栈sp值恢复原样, 也就是所谓的栈平衡

特点: ip突然上升,而后ip突然恢复

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

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

8086汇编语言之代码分段

发表于 2020-12-12

## 场景

当我们需要在内存中申请一块空间,可以使用伪指令db和dw

1
2
db-->define byte  定义字节
dw-->define word 定义字

如果按照以下写法:

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code
code segment
db 1,2,3,4,5
db 'hello'
db "pangshu"

mov al ,cs:[0] ;取出预先定义好的数据 ip默认从0开始
;退出程序
mov ah 4ch
int 21h
code ends
end

以上代码存在一个问题, 由于数据是在代码段中定义, cpu默认将数据识别为代码, 将导致数据不可用,那么解决办法为,增加入口标记:

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code
code segment
db 1,2,3,4,5
db 'hello'
db "pangshu"

start: mov al ,cs:[0] ;取出预先定义好的数据 ip默认从0开始
;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义

标记是为了告诉编译器代码段入口位置, 这样就能保证db数据不被识别为指令

知识点

  1. 如果我想定义20个0数据,有一种快捷的语法:
1
2
3
4
5
6
7
8
9
10
assume cs:code
code segment
db 20 dup(0) ;申请20个字节的空间 然后存放0

start: mov al ,cs:[0] ;取出预先定义好的数据 ip默认从0开始
;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义
  1. 数据段和栈段的定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code
code segment
db 20 dup(0) ;可存数据也可当作栈
db 20 dup(0) ;可存数据也可当作栈
start: ;将数据所在的物理基地址交由ds段寄存器进行存放管理
mov dx,cs
mov ds,dx
mov ax,1122h
mov [0],ax

;定义栈段 将栈空间所在的物理基地址交由ss栈段进行保存管理
mov ss,ds
mov sp,40 ;从高字节往低字节存放
push ax

;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义
  1. 分段定义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
assume cs:code,ds:data,ss:stack

;数据段 代码段可直接获取数据段中数据, 相当于高级语言中的局部变量
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;数据段 代码段可直接获取数据段中数据, 相当于高级语言中的全局变量
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
age dw 20h ;给数据取个别名为age

data ends

code segment


start:
mov ax,1122h
mov age,ax ; 相当于[14h],ax

;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义

额外思考

事实上我们使用的段其实是一个逻辑概念,即是我们自己定义的,

再说白了,我定义一个段,我说它是数据段那它就是数据段,我说它是代码段那么它就是代码段,

它们其实都是一块连续的内存而已,至于为什么要区分为数据段和代码段,

很明显,是用来给我们编程提供方便的,即我们在自己的思想上或者说是编码习惯上规定,

数据放数据段中,代码放代码段中 。而我们在使用数据段的时候,为了方便或者说是代码的编写方便起见,

我们一般把数据段的段地址放在 DS 寄存器中,当然,如果你硬要觉得 DS 不顺眼,那你可以换个 ES 也是一样的,但是换成CS则不行,因为CS指向的数据都被当成指令进行处理,如果换成SS呢,可行,但是读取数据需要使用pop,修改数据需要使用push,如果是用DS或者ES ,可以直接使用DS:[0]这种形式进行内存数据的读写

被DS和ES指向的内存空间的数据被cpu当作数据处理,被SS指向的内存空间的数据被cpu当作是栈空间,被CS指向的内存空间的数据被cpu当作指令进行执行

看注释说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
assume cs:code,ds:data,ss:stack

;开辟了一块栈空间 同时生成了与之对应的物理地址
stack segment
db 20 dup(0) ;定义数据相当于是定义了段地址
stack ends

;开辟了一块数据空间 同时生成了与之对应的物理地址
data segment
db 20 dup(0) ;定义数据相当于是定义了段地址
age dw 20h ;给数据取个别名为age

data ends

;开辟了一块代码空间 如果没有入口标记 那么里面无论存放什么, cpu都把它当成指令
code segment
start:
;程序开始

;获取栈空间基地址 存放在ss寄存器中
mov ax ,stack
mov ss ,ax
;获取数据空间基地址 存放在ds寄存器中
mov ax,data
mov ds ,ax

;如果有别名 可以直接使用别名访问数据,而不需要另外借助段地址
mov age,ax ; 相当于[14h],ax

;退出程序
mov ah 4ch
int 21h
code ends
end start ;标记名称可自定义

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

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

android反编译之jni接口数据的获取的几种方法

发表于 2020-12-11

如果没有签名校验

  1. 通过加壳的形式或者log插桩获取

  2. 直接抽取so文件 然后构建一个新app

如果app或者so库有签名校验

  1. 使用ida工具静态分析so库

  2. 使用xposed工具动态获取

    原料:

    • 一台root设备, 推荐使用模拟器

      • xposed框架安装包
      • 编写hook插件 (关键)

xposed工具简单介绍

xposed是一个第三方的app, 用于hook代码, 可以简单理解为代码拦截,

该app存在的作用:

  • 内部集成了相应的hook环境包
  • 方便管理多个插件

插件hook的原理

插件其实就是一个独立的app, 只不过该app内部自定义了一些标识能够被xposed框架app识别, xposed框架将我们编写好的带有标识的app当成插件进行管理, 插件利用xposed框架中集成好的hook环境实现hook操作

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

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

1…293031…50

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

493 日志
143 标签
RSS
© 2025 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%