概述
Android是基于Linux内核的操作系统,大多是是基于ARM架构的,于是要熟悉一下ARM相关内容。
Android开发环境的安装
安装JDK、NDK、SDK,,并配置好环境变量。
模拟器的选择
可以使用SDK自带的模拟器(最推荐,虽然慢但是出问题的概率最小),也可使用其他模拟器,如雷电、逍遥等。由于是x86架构,所以在进行调试时要使用android_x86_server
。
adb的常见命令
adb devices
adb shell
adb -s 设备名 shell
adb install
adb push
adb pull
Apk文件
apk 文件其实是一个zip文件,只是后缀改变了而已。网上找了个结构图如下:
AndroidManifest.xml 文件:Android工程的基础配置属性文件,定义了整个应用的运行信息,包括:包名、权限、API 版本、组件等等信息
classes.dex 文件:Java代码编译得到的Dalvik VM能直接执行的文件,包含了应用的整个代码逻辑
lib 目录:so库存放位置,一般由NDK编译得到,常见于使用游戏弓|擎或JNI native调用的工程中,包含了应用需要的原生库文件,其主要是从
c/c++
代码编译而来- armeabi/armeabi-v7a 目录:so库文件分为不同的CPU架构
META-INF 目录:存放工程一些属性文件,例如Manifest.MF,如存储了应用的证书和签名信息文件
res 目录:资源目录,包含不能打包进
resources.arsc
的一些资源文件,如图片、布局信息等- drawable 目录:图片和对应的xml资源
- layout 目录:定义布局的xml资源
resources.arsc 文件:对res目录下的资源的一个索引文件,保存了原工程中strings.xmI等文件内容
asset(此网图例没有):也是资源目录,但与res有所区别(具体相同、不同如下)
相同:两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制
不同:
- res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类
- res/raw不可以有目录结构,而assets则可以有目录结构,也就是assets目录下可以再建立文件夹
- assets文件夹是存放不进行编译加工的原生文件,即该文件夹里面的文件不会像xml,java文件被预编译,可以存放一些图片,html,js,css等文件
Dalvik
Dalvik是google专门为Android操作系统设计的一个虚拟机
虽然Android上的程序是使用java来开发的,但是Dalvik和标准的java虚拟机(JVM)还是两回事
- Dalvik VM是基于寄存器的,而JVM是基于栈的
- Dalvik有专属的文件执行格式dex(dalvik executable),而JVM则执行的是java字节码
- Dalvik VM比JVM速度更快,占用空间更少
通过Dalvik的字节码我们不能直接看到原来的逻辑代码,这时需要借助如Apktool或jadx等工具来帮助查看。但是,注意的是最终我们修改APK需要操作的文件是.smali文件,而不是将导出来的Java文件重新编译。
ART
ART虚拟机是Android4.4发布的,用来替换Dalvik虚拟机的,在Android 5.0时,默认采用ART虚拟机。
在ART中,系统在安装应用时会进行一次预编译,将字节码预先编译成机器码并存储在本地,而不是像Dalvik每次运行都需要通过即时编译器生成字节码,从而提高了运行的效率。但也因此,ART虚拟机因为需要存储字节码从而占用空间会比较大。
Smali
Smali是用于Dalvik的反汇编程序实现,汇编工具(将Smali代码汇编为dex文件)为smali.jar,与之对应的baksmali.jar则是反汇编程序。其语法是Jasmin/dedexer语法
Smali支持注解、调试信息、线路信息、行数信息等基本Java的基本特性
在Smali中,如果需要存储变量,必须先声明足够数量的寄存器,1个寄存器可以存储32位长度的类型,64位类型(Long/Double)用2个寄存器表示。本地寄存器用v开头数字结尾的符号来表示,参数寄存器则使用p开头数字结尾的符号来表示。特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this”,p1表示函数的第一个参数,p2代表函数中的第二个参数;而在static函数中p0才对应第一个 参数,因为Java的static方法中没有this方法。
基本类型:
- V :void,只能用于返回类型
- Z :boolean
- B :byte
- S :short
- C :char
- I :int
- F :float
- J :long (64 bits)
- D :double (64 bits)
- [xxx(I/D…) : array
- Lxxx/yyy(package/objectName); : object
对于最后一个,例如String对象在smali中为:Ljava/lang/String;
,其中java/lang对应java.lang包,String就是定义在该包中的一个对象
Android启动流程
网上总结的很全,见这儿
Dalvik指令集
- 基本:
指令 | 说明 |
---|---|
.field private isFlag:z | 定义变量 |
.method | 方法 |
.parameter | 方法参数 |
.prologue | 方法开始 |
.line 123 | 此方法位于第123行 |
invoke-super | 调用父函数 |
const/high16 v0,0x7fo3 | 把0x7fo3赋值给v0 |
invoke-direct | 调用函数 |
return-void | 函数返回void |
.end method | 函数结束 |
new-instance | 创建实例 |
iput-object | 对象赋值 |
iget-object | 调用对象 |
invoke-static | 调用静态函数 |
- 移位操作:
指令 | 说明 |
---|---|
move v1,v2 | 将v2中的值移入到v1寄存器中(4位,支持int型) |
move/from16 v1,v2 | 将16位的v2寄存器中的值移入到8位的v1寄存器中 |
move/16 v1,v2 | 将16位的v2寄存器中的值移入到16位的v1寄存器中 |
move-wide v1,v2 | 将寄存器对(一组,用于支持双字型)v2中的值移入到v1寄存器对中(4位,猜测支持float、double型) |
move-wide/from16 | v1,v2 将16位的v2寄存器对(一组)中的值移入到8位的v1寄存器中 |
move-wide/16 v1,v2 | 将16位的v2寄存器对(一组)中的值移入到16位的v1寄存器中 |
move-object v1,v2 | 将v2中的对象指针移入到v1寄存器中 |
move-object/from16 v1,v2 | 将16位的v2寄存器中的对象指针移入到v1(8位)寄存器中 |
move-object/16 v1,v2 | 将16位的v2寄存器中的对象指针移入到v1(16位)寄存器中 |
move-result v1 | 将这个指令的上一条指令计算结果,移入到v1寄存器中(需要配合invoke-static、invoke-virtual等指令使用) |
move-result-object v1 | 将上条计算结果的对象指针移入v1寄存器 |
move-result-wide v1 | 将上条计算结果(双字)的对象指针移入v1寄存器 |
move-exception v1 | 将异常移入v1寄存器,用于捕获try-catch语句中的异常 |
- 返回操作:
指令 | 说明 |
---|---|
return-void | 返回void,即直接返回 |
return v1 | 返回v1寄存器中的值 |
return-object v1 | 返回v1寄存器中的对象指针 |
return-wide v1 | 返回双字型结果给v1寄存器 |
- 常量操作:
指令 | 说明 |
---|---|
const(/4、/16、/hight16) v1 xxx | 将常量xxx赋值给v1寄存器,/后的类型,需要根据xxx的长度选择 |
const-wide(/16、/32、/hight16) v1 xxx | 将双字型常量xxx赋值给v1寄存器,/后的类型,需要根据xxx的长度选择 |
const-string(/jumbo) v1 “aaa” | 将字符串常量”aaa”赋给v1寄存器,过长时需要加上jumbo |
const-class v1 La/b/TargetClass | 将Class常量a.b.TargetClass赋值给v1,等价于a.b.TargetClass.class |
- 调用操作:
指令 | 说明 |
---|---|
invoke-virtual | 用于调用一般的,非private、非static、非final、非构造函数的方法,它的第一个参数往往会传p0,也就是this指针 |
invoke-super | 用于调用父类中的方法,其他和invoke-virtual保持一致 |
invoke-direct | 用于调用private修饰的方法,或者构造方法 |
invoke-static | 用于调用静态方法,比如一些工具类 |
invoke-interface | 用于调用interface中的方法 |
- 判断操作:
指令 | 说明 |
---|---|
if-eq v1,v2 | 判断两个寄存器中的值是否相等 |
if-ne v1,v2 | 判断两个寄存器中的值是否不相等 |
if-lt v1,v2 | 判断v1寄存器中的值是否小于v2寄存器中的值(lt == less than) |
if-ge v1,v2 | 判断v1寄存器中的值是否大于或等于v2寄存器中的值(ge == great than or equals) |
if-gt v1,v2 | 判断v1寄存器中的值是否大于v2寄存器中的值(gt == great than) |
if-le v1,v2 | 判断v1寄存器中的值是否小于或等于v2寄存器中的值(le == less than or equals) |
- 属性操作:
指令 | 说明 |
---|---|
iget | 取值,用于操作int这种的值类型 |
iget-wide | 取值,用于操作wide型字段 |
iget-object | 取值,用于操作对象引用 |
iget-boolean | 取值,用于操作布尔类型 |
iget-byte | 取值,用于操作字节类型 |
iget-char | 取值,用于操作字符类型 |
iget-short | 取值,用于操作short类型 |
iput | 赋值,用于操作int这种的值类型 |
iput-wide | 赋值,用于操作wide型字段 |
iput-object | 赋值,用于操作对象引用 |
iput-boolean | 赋值,用于操作布尔类型 |
iput-byte | 赋值,用于操作字节类型 |
iput-char | 赋值,用于操作字符类型 |
iput-short | 赋值,用于操作short类型 |
- 其他指令:
指令 | 说明 |
---|---|
add-int/lit8 v1, v2, 0x1 | 给v2寄存器+1,并存入v1寄存器(注意:lit8是对要加的常量的长度限制,如果不写,则为4位,还可选择lit16,即16位) |
add-int/2addr v1, v2 | 将v1、v2寄存器中的值相加,并赋值给v1寄存器 |
float-to-int v1, v2 | 将v2寄存器中的float类型值转换为int类型,并赋值给v1寄存器 |
问题汇总
adb连接雷电模拟器时
offline
问题:用Android SDK
中的adb.exe
替换掉雷电模拟器自带的adb.exe
即可解决Android远程调试:与Linux远程调试类似,但需要进行用
adb forward tcp:23946 tcp:23946
端口转发运行
adb devices
时出现未知设备offline:运行adb kill-server
adb start-server
adb remount