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

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


  • 首页

  • 归档

  • 搜索

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汇编语言之函数的声明和调用

发表于 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汇编语言之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汇编语言之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操作

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

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

ida工具的使用

发表于 2020-12-11

这里以ida7.0版本为例 ida理论上支持各种文件的查看包括exe,dll , so等等

主要界面了解

汇编界面转源代码界面

使用F5快捷键进入

右键选择hide casts后界面好看多了

668是什么鬼?继续优化

选中env变量名右键选择se lvar type 或者使用快捷键Y ,然后填入JNIEnv*

这时,代码舒爽多了

快速查看当前文件所包含的字符串

使用shift+F12快捷键

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

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

汇编语言知识总结

发表于 2020-12-11

介绍

汇编是一类编程语言,每种cpu对应一种cpu语言,这些语言语法大同小异,指令集有所不同,

那么这些cpu语言统称为汇编语言,与java,c++,python等高级语言无异, 只不过汇编更加接近硬件,代码执行效率高

二进制>汇编>c>java

所有编程语言都有相应语法,汇编也不例外, 语法是人定的,只是一套公共协议,目的是方便程序员进行程序开发

为什么要学汇编

  • 了解程序的本质, 利于日常开发
  • 从事硬件相关开发工作
  • 进行反编译逆向
  • 装X

常见的cpu架构

  • x86 架构 : PC 端主流 高性能高功耗

  • ARM 架构: 移动端主流 体积小低功耗

  • MIPS 架构: 龙芯3号 国产cpu

不同架构使用的指令集也不一样, X86使用了CISC复杂指令集 ARM采用了RISC精简指令集

RISC可以说是从CISC中取其精华去其糟粕,简化指令功能,让指令的平均执行周期减少,达到提升计算机工作主频的目的,同时引入大量通用寄存器减少不必要的读写过程,提高子程序执行速度,这样一来程序运行时间缩短并且减少了寻址,提高了编译效率,最终达到高性能目的

不同cpu架构所对应的汇编语法大致相同, 只是指令集不同

寄存器

顾名思义, 寄存器可以理解为是寄生在cpu上存放数据的容器, 在计算机当中,用于存放数据的容器有很多,比如内存条,硬盘等等, 那寄存器有什么不一样呢?

  • 寄存器靠近cpu,读写数据速度远大于内存
  • 进行数据的临时存储

当然 cpu内部除了有寄存器之外,还有运算器和控制器, 对于我们程序员来讲,只需要学习寄存器即可

缓存

寄存器和缓存是两个概念, 由于cpu执行速度太快, 而内存读写数据远远跟不上, 这时需要借助缓存进行数据缓冲,相当于是寄存器和内存之间的中间桥梁, 这样cpu在执行指令的时候能够有源源不断的数据供给

了解:寄存器–>一级缓存–>二级缓存–>三级缓存–>内存

拓展: 如果内存条的读写性能过差, 那么cpu再强悍也使不上劲,巧妇难为无米之炊, 因此平常我们再自己组装电脑时,除了内存条的容量之外,还需要考虑到内存条的品质, 否则影响cpu性能,硬盘同理

为什么要了解寄存器

因为程序员如果想要操控cpu或者修改内存, 不能直接操控, 需要借助寄存器, 更改寄存器当中的数据间接地操控cpu和内存

寄存器的数量

在高级语言中如果要对两个变量进行数据交换,我们通常的做法是使用一个temp临时变量,比如:

1
2
3
4
5
6
7
int a=1;
int b=2;
int temp;
//数据交换
temp=a;
a=b;
b=temp;

寄存器是一个存储容器,也可以通俗理解为是一个变量, 那么cpu在进行数据交换时明显一个寄存器是不够的, 在8086cpu中,通用寄存器有好几个,比如ax,bx,cx,dx 这些名称是固定的, 根据cpu的不同名称也各不相同, 咱们只需知道每种cpu都有相应的通用寄存器, 寄存器数量越多,自然运算效率越高

寄存器的分类

  • 通用寄存器 (通用): 用于存放临时数据, 可以简单理解为高级语言中的临时变量
  • 段寄存器 (特有): 内存分段管理 x86架构中分为数据段,代码段和栈段 ARM架构中没有段寄存器
  • 浮点寄存器 (特有): 专用于浮点数的运算
  • 向量寄存器 (特有): 专用于向量运算
  • 标志寄存器
  • 状态寄存器
  • ……….

通用寄存器的命名

在x86架构中 , 一共有四个通用寄存器,以16位x86为例, 分别取名为ax,bx,cx,dx 最大只能装16位的数据

在ARM架构中, 一共有31个通用寄存器,以64位arm为例 从x0到x30

在MIPS架构中,, 一共有32个通用寄存器 ,从$0到$31

在x86架构中,不同精度cpu 通用寄存器名称有所区分:

1
2
3
4
5
6
7
;在x86架构中,不同精度cpu 通用寄存器名称有所区分:
0x1122334455667788
================ rax (64 bits) ;字母r开头表示64位寄存器 r是registers的意思
======== eax (32 bits) ;e开头表示32位寄存器 e是扩展的意思 extend
==== ax (16 bits) ;默认ax表示16位寄存器
== ah (8 bits)
== al (8 bits)

e是扩展的意思,在386以前,CPU的寄存器的16位的,用AX,BX等表示,
386及以后的CPU,它们的寄存器的32位的,所以就用多一个E来表示

在ARM架构中,不同精度cpu 通用寄存器名称同样有所区分:

1
2
3
4
5
0x1122334455667788
================ xN (64 bits) ;x开头表示64位寄存器 从x0到x30
======== WN (64 bits) ;WN表示64位寄存器中的低32位 64位cpu中才有 32位中没有W这种命名

======== RN (32 bits) ;字母R开头表示32位寄存器 不一定是大写也有小写

在64位cpu中WN是XN的低32位, 属于xN的一部分, WN数据改了,xN也会跟着一块改, 也就是说WN无法独立存在, 同时ARM中并没有提供16位和8位寄存器的访问和使用

汇编代码初探

工具: https://godbolt.org/

进制转换基础

  1. 计算机只识别二进制, 汇编中一般使用十六进制表示数据, 使用十六进制是为了方便程序员阅读和开发
  2. 二进制和十六进制转换
1
2
3
0101 1100 1001 0010  //二进制

5 C 9 2 //每四位二进制数对应一位十六进制数

在x86汇编代码中,十六进制写法为如下:

1
mov ax,2000H  ;h结尾表示十六进制, h不区分大小写

在ARM汇编代码中,十六进制写法为如下:

1
mov R0,#0x2020202A ;使用0x开头表示  #是固定写法,暂不用理会

数据单位

  • 位 :一个二进制位
  • 字节 :8个二进制位表示一个字节

由于八个二进制位转换成十六进制后是两位十六进制数, 所以两个十六进制数占用一个字节:

1
2
3
0x20   ;占用一个字节
或者
20H ;占用一个字节

在计算机中最小的数据单位是位,但在内存中,最小的数据单位是字节,一个内存单元占用一个字节, 内存单元就是一个存放数据的容器,可以比喻为一栋公寓里面的小单间, 每个单间住着一对夫妻

内存单元和地址

内存单元相当于一栋公寓里的小单间, 每个单间里面住着一个字节(一对夫妻) , 一对夫妻是两个人, 形容两个十六进制数

地址就相当于这个房间的门牌号, 通常使用十六进制表示(也叫物理地址)

cpu想要读写内存中的数据, 需要通过地址来需要对应的内存单元,也叫寻址

那么问题来了, 一栋公寓里一共有多少个单间呢, 或者说单间的数量跟什么有关呢?

  • 内存条容量: 既然每个内存单元占的空间是固定的, 那么内存容量越大,房间自然就越多

给房间贴门牌号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0x1
0x2
0x3
...
0x10
0x20
...
0x100
0x200
...
0x1000
0x2000
...

0xFFFF 65535
0xFFFFA

现实中门牌编号最大值和什么有关?

  • 装修师傅的计算能力

  • 门牌金属板的宽度(字体大小不变的前提)

在计算机的世界中

  • 装修师傅的计算能力—–>cpu运算能力

  • 门牌金属板的宽度——->地址总线宽度(地址总线数量)

如果对应的cpu是16位的, 同时地址总线也是16位,那么最大只能运算表示16位数也就是0xFFFF ,

如果对应的cpu是32位的, 同时地址总线也是32位,那么最大只能表示32位数也就是0xFFFFFFFF

如果对应的cpu是64位的, 同时地址总线也是64位,那么最大只能表示64位数也就是0xFFFFFFFFFFFFFFFF

在8086cpu中 cpu是16位 但是地址总线却是20位, 本来最大只能表示16位地址值, cpu设计者为了让其能表示20位地址,使用了段地址*16+偏移地址的形式来表示20位地址

物理地址=段地址*16+偏移地址

如果要表示一个20位物理地址0xFFFFA,可以有一下四种写法:

1
2
3
4
5
6
7
0xFFFFA=0xFFFF*16+0x000A //FFFF0   10*10=100   

0xFFFFA=0xFFF0*16+0x00FA

0xFFFFA=0xFF00*16+0x0FFA

0xFFFFA=0xF000*16+0xFFFA

因此计算机的寻址能力不单单和cpu有关还和地址总线有关, 32位操作系统对应32位地址总线, 这也就是为什么即便你用的是64位cpu,如果只装了32位操作系统,无法完全发挥cpu和内存的性能

栈和队列

栈和队列都是数据存储结构,数据结构大致包含以下几种存储结构:

  1. 线性表,还可细分为顺序表,链表、栈和队列;
  • 树结构,包括普通树,二叉树,线索二叉树;
  • 图存储结构
  • 队列结构 :先进先出, 和排队一样
  • 栈存储结构 : 先进后出, 类似于往往杯子里放饼干, 第一个放的最后一个取出

栈作用 :

  • 用于存储临时数据,
  • 对数据进行暂时性保护,不被复写
  • 寄存器不够用时,使用栈临时代替中转

寄存器和栈同样用于存放临时数据, 那么它们两者有什么区别呢?

寄存器类似于全局变量,是个公共容器,可以被所有函数读写,寄存器中的数据容易被覆盖, 常用于短周期使用

栈空间是累加型结构: 如果想要复写第一个放入的数据,必须先将后面存放的数据丢弃, 类似于递归, 适合嵌套数据,这也是为什么函数和函数中的局部变量都存放在栈中的原因

总线

存在的意义, 内存中的数据不能直接运算,必须将其读取到寄存器中进行处理, cpu运算完毕后,将其保存至内存中, 那么这一系列过程中,涉及到数据传输, 那么这三条线就是干这个用的

x86汇编语法

  1. 注释
1
;我是注释

了解: arm汇编注释同为; 而mips汇编注释为#

  1. 变量取值和赋值(传送指令)
1
2
3
4
5
6
;赋值
mov ax,2000H ;将十六进制2000赋值给十六位寄存器ax 相当于ax=2000H


;取值
mov bx,ax ;将ax中的值取出赋值给bx 相当于bx=ax

无论是x86还是arm传送指令都是mov

存放的数据大小根据使用的寄存器而定, 比如ax是16位寄存器,最大只能存放16位数据,也就是4位十六进制数据

  1. 函数声明

结构如下:

1
2
3
函数名:
函数体
ret ;结尾标记

示例:

1
2
3
4
5
6
print:	;函数名
mov dx,offset str
mov ah 9ch
int 21h

ret ;函数结尾标记
  1. 函数调用

x86架构中使用关键指令call, ARM架构中使用关键指令bl

x86架构汇编示例:

1
2
3
4
5
6
7
8
9
10
11
		call print ;调用print函数

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

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

ARM架构汇编代码示例:

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

_A:
mov x0 ,#0xa0 ;arm汇编中数据用#开头
bl _B ;调用函数B
mov x1 ,#0x00
add x1 ,x0,#0x14 ;x1=x0+0x14
ret

_B:
add x0, x0,#0x10
ret ;返回到bl指令所对应的下一条指令

C语言内嵌汇编代码(GCC内联汇编)

格式

1
2
3
4
5
6
7
8
9
10
asm volatile(   ;asm也可写成 __asm__ 或者__asm
"汇编指令"
:"=限制符"(输出参数) ,"=限制符"(输出参数)
:"限制符"(输入参数)
:保留列表
)

;volatile是可选关键字,表示禁止编译器对汇编代码进行优化
;汇编指令之间使用\n进行分隔
;限制符用于和c语言交互,属于可选,多个参数使用逗号进行分隔

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//将input的值赋值给result 
int main()
{
int result = 0;
int input = 1;
asm volatile (
"movl %1, %0\n" // 通过占位符指定交互的变量 %1赋值给%0
: "=r"(result) // 输出变量,与汇编交互
: "r"(input) // 输入变量,与汇编交互
// 这里的r指示编译器自动将通用寄存器关联到变量
);
printf("result = %d\n", result);
printf("input = %d\n", input);


}

我们看到,movl指令的操作数(operand)中,出现了%1、%0,这往往让新手摸不着头脑。其实只要知道下面的规则就不会产生疑惑了:

在内联汇编中,操作数通常用数字来引用,具体的编号规则为:若命令共涉及n个操作数,则第1个输出操作数(the first output operand)被编号为0,第2个output operand编号为1,依次类推,最后1个输入操作数(the last input operand)则被编号为n-1。

具体到上面的示例代码中,根据上下文,涉及到2个操作数变量a、b,这段汇编代码的作用是将a的值赋给b,可见,a是input operand,而b是output operand,那么根据操作数的引用规则,不难推出,a应该用%1来引用,b应该用%0来引用。

常用限制符参照:

限制符 说明
r 通用寄存器
a eax,ax,al
b ebx,bx,bl
c ecx,cx,cl
d edx,dx,dl
S esi,si
D edi,di
q 寄存器a,b,c,d
m 使用合法的内存代表参数
g 任意寄存器,内存,立即数

为什么有些汇编语法不一致

C语言外链汇编

新建一个汇编原文件, linux平台.s结尾 ,windows平台.asm结尾

1
2
3
4
5
6
7
8
9
10

;外链汇编
;以下使用的是AT&T的汇编语法

.text ;声明为代码段
.global _sum ;定义为全局函数, 否则无法被外界访问
_sum: ;函数名称必须_开头
movq %rdi,%rax ;方法参数存放在di和di寄存器中
addq %rsi,%rax
retq

然后在C中进行相应调用即可

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

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

1…293031…50

乱码三千

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

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