首页
社区
课程
招聘
[原创]安卓逆向基础知识之Java与smali基础
发表于: 2023-8-24 13:31 14433

[原创]安卓逆向基础知识之Java与smali基础

2023-8-24 13:31
14433

Java作为一种面向对象语言。最为重要的两个概念那就是类和对象:

:类是一个模板,它描述一类对象的行为和状态。

对象:对象是类的一个实例,有状态和行为。

类和对象之间的关系就像人类和某个人之间的关系,人类拥有一些行为和状态,这些行为和状态作为了人类的特征;用面向对象的思维来说,每个人可以说是人类这个类实例化的对象,在拥有人类的行为和状态下还拥有各自的特点。

数据类型分为四大类八种,四大类是:整形、浮点型、字符型、布尔型;整形可以细分为byte、short、int、long;浮点可以细分为float、double。

1、整数类型默认为int类型,占4字节,当使用字节大小是大于它的long类型时,除了需要定义变量为long类型,还需要在值后面加L或者l才能将该变量表示为long类型。如:long l = 123456789l;

2、浮点类型默认为double类型,占8字节,在值后面加上F/f就是float类型了。如:float f = 3.14f;

Java提供了两类修饰符。分别是访问修饰符和非访问修饰符。

访问修饰符是一种用来限制类、接口、类成员(字段、方法、构造函数等)访问权限的关键字,主要包括以下四种:

为了实现一些其他的功能,Java 提供了很多非访问修饰符,如:

switch case 语句判断一个变量与一系列值中某个值是否相等,每个值称为一个分支。

switch case 执行时,一定会先进行匹配,匹配成功就开始执行case 语句之后的语句,再根据是否有 break,判断是否继续输出,或是跳出判断。

面向对象是Java的重中之重,之间简单介绍了对象和类的关系,现在正式来讲解一下面向对象相关知识。

每个类都有构造器。如果没有主动的为类定义构造器,Java 编译器将会为该类提供一个默认的无参构造器。构造器的主要作用是初始化对象的数据成员,在创建一个对象时,构造器会被自动调用来为对象的各个数据成员赋初值。

面向对象基础实例:

ClassMethod.java文件

Zoo.java文件

封装指的是将数据和操作数据的方法封装在一个单元内部,并通过访问权限控制来限制外部对该单元的访问。

例如,下面是一个简单的Java类,它封装了一个学生的姓名和年龄:

可以看到在这个类中,姓名和年龄都是私有成员变量,只能通过公开的getter和setter方法来访问和修改。这样,我们就可以在外部控制和限制对这些成员变量的访问。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

Java中的继承是单继承的,也就是说每个子类只能继承一个父类。Java中通过使用关键字"extends"来实现继承。

继承的语法格式如下所示:

其中,SubClass是子类,SuperClass是父类。子类可以继承父类的属性和方法,同时可以添加自己的属性和方法。

Java中的所有类都继承自Object类。因此,如果没有指定父类,Java中的类默认继承自Object类。

Java中的继承关系可以形成继承层次结构,也就是说一个子类可以成为另一个子类的父类。Java中的继承层次结构可以使用继承关系图来表示。

在子类继承父类后,构造器会有以下特点:

子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。这是因为子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。

如果父类中没有无参的构造器,子类必须使用super关键字显式调用父类的构造器,并传入相应的参数。例如:

子类通过this(...)去调用本类的其他构造器,本类其他构造器会通过super去手动调用父类的构造器,最终还是会调用父类构造器的。例如:

注意:this(...)和super(...)都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。

继承的优点在于它可以提高代码的重用性和可维护性,同时可以使代码更加灵活和可扩展。通过继承,子类可以继承父类的属性和方法,从而减少了代码的编写量。此外,继承也可以使代码更加灵活,能够更好地适应需求的变化。

继承的缺点在于它可能会引入过多的复杂性,使程序难以维护和扩展。如果继承关系设计不当,会导致代码的耦合性过高,增加了代码的复杂度和维护成本。因此,在设计继承关系时,需要仔细考虑继承的层次,避免出现过多的继承关系。

在 Java 中,多态是一种基于继承、多态和重载的特性。它允许同一种类型的对象在不同的场景下表现出不同的行为。

Java 实现多态的方式主要有两种:继承和接口。

子类可以继承父类的方法,并且可以重写父类的方法,从而实现多态。当子类重写了父类的方法后,当通过父类对象调用该方法时,实际上会调用子类重写后的方法。

示例代码:

输出结果:

接口是一种规范,它定义了一组方法,但并不提供方法的具体实现。不同的类可以实现同一个接口,并且每个类都可以根据自己的实际情况来实现接口中的方法,从而实现多态。

示例代码:

输出结果:

无论是通过继承还是接口实现多态,都可以使代码更加灵活、可扩展性更强。

虚函数的存在是为了多态,虚方法是实现多态的重要手段,可以使不同的子类对象调用同一个方法时产生不同的行为。虽然Java 中没有虚方法这个概念,但是Java中每个方法都是虚方法,除了被 final 关键字修饰的方法。

在Java中,虚方法的实现依赖于方法表和虚方法表。方法表是每个类的一部分,它包含了该类所有的方法的信息,包括方法名、参数类型、返回值类型等。虚方法表是每个类的一个隐藏的数据结构,它包含了该类所有的虚方法的信息,包括方法的地址、偏移量等。每个对象在内存中都有一个指向其所属类的虚方法表的指针,称为vtable指针。

当调用一个虚方法时,Java虚拟机首先根据对象的实际类型找到其对应的虚方法表,然后根据方法的偏移量找到要调用的方法的地址,并执行该方法。如果子类重写了父类的虚方法,则子类的虚方法表中会覆盖父类相应的方法地址,从而实现了多态。

以下是一个示例代码:

在上面的代码中,Animal、Cat和Dog都有一个eat方法,但是它们的行为不同。在main方法中,分别创建了一个Animal、一个Cat和一个Dog对象,并调用它们的eat方法。由于eat方法是虚方法,因此在运行时会根据对象的实际类型来确定要调用哪个方法,从而实现了多态。

方法重写指的是在子类中定义一个与父类方法名、返回类型、参数列表都相同的方法,但是方法体不同的过程。

当子类对象调用被重写的方法时,将优先调用子类中的方法,而不是父类中的方法。

方法重写的条件为:

下面是一个方法重写的例子:

在上面的例子中,Animal类中定义了一个move()方法,输出“动物在移动”。在Dog类中重写了这个方法,并输出“狗在奔跑”。当我们调用animal.move()时,输出的是父类Animal中的move()方法的内容;当我们调用dog.move()时,输出的是子类Dog中重写的move()方法的内容。

static是静态的意思,可以修饰成员变量和成员方法;static修饰的成员变量和成员方法在内存中只用存储一份,因为可以被类和对象共享访问、修改。

static注意事项:

1、静态方法只能访问静态成员(静态变量或者静态方法),不能直接访问实例成员,因为静态成员是属于类的,而不是直接属于对象的,静态成员会同类一同创建。

2、实例方法可以访问静态成员,也可以访问实例成员,因为静态成员是可以被共享访问的。

3、静态方法中不可以出现this关键字,因为this只能代表当前对象,而静态方法不一定使用对象调用。

4、静态方法中也不能使用super关键字,因为super关键字代表父类对象,而静态方法没有对象的概念。

5、静态方法也不能被子类重写,因为静态方法是属于类的,而不是属于对象的。

抽象类是不能被实例化的类,如果你是新手,那你是不是很疑惑,一个类不能被实例化那还有什么卵用?

别急嘛!抽象类的存在主要是为了被子类继承和实现。抽象类中可以包含抽象方法,抽象方法是一种没有实现的方法,只有方法的声明,没有方法体。

抽象类的特点如下:

下面是一个抽象类的例子:

在上面的例子中,Animal类是一个抽象类,它包含了一个抽象方法move()、一个非抽象方法eat()和一个非抽象方法getName()。在Dog类中继承了Animal类,并实现了move()方法。在Test类中,我们创建了一个Animal类型的对象,实际上是一个Dog对象,然后调用了它的move()、eat()和getName()方法。由于Dog类重写了move()方法,因此输出的是“小狗在奔跑”,而eat()方法是从父类继承而来,输出的是“小狗在吃东西”,getName()方法也是从父类继承而来,用来获取动物的名称。

接口是一种特殊的抽象类,它只包含了抽象方法和常量,没有任何实现。接口中的所有方法都是公共的,不能包含实例域或构造器,因此不能被实例化。

一个类可以实现多个接口,但只能继承一个类。

接口的定义格式如下:

接口中的方法默认为public abstract类型,可以省略这两个关键字。接口中的常量必须是public static final类型的(这三个修饰符表示该变量是公共的、静态的、不可改变的,也叫该变量为常量),可以省略这三个关键字。接口中的方法不能包含方法体,必须由实现类去实现。

下面是一个接口的例子:

在上面的例子中,Animal是一个接口,包含了一个常量LEGS和一个抽象方法move()。Dog类实现了Animal接口,并实现了move()方法。在Test类中,我们创建了一个Animal类型的对象,实际上是一个Dog对象,然后调用了它的move()方法。由于Dog类实现了Animal接口,因此可以使用Animal类型来引用Dog对象,从而实现了多态性。输出的结果为“狗在奔跑,狗有4条腿”。

内部类其实就是在一个类里面再定义一个类,内部类可以访问其外部类的所有成员,包括私有成员。内部类可以分为成员内部类、静态内部类、局部内部类和匿名内部类。

成员内部类就是在一个类的内部定义的另一个类,它可以访问外部类的所有成员,包括私有成员,并且可以使用外部类的引用来访问外部类的成员。成员内部类的定义格式如下:

在上面的例子中,Inner是Outer的成员内部类,它可以访问Outer的私有成员x。在外部类中创建Inner对象的方法如下:

​ 2.静态内部类

静态内部类是在一个类的内部定义的静态类,它和普通的类一样,不依赖于外部类的实例,因此可以直接通过类名来访问。静态内部类不能访问外部类的非静态成员,只能访问外部类的静态成员。静态内部类的定义格式如下:

在上面的例子中,Inner是Outer的静态内部类,它可以访问Outer的静态成员x。在外部类中创建Inner对象的方法如下:

局部内部类是定义在方法内部的类,它只能在该方法内部使用,对外部不可见。局部内部类可以访问外部类的所有成员,包括私有成员。局部内部类的定义格式如下:

在上面的例子中,Inner是Outer方法内部的局部内部类,它可以访问Outer的私有成员x。在方法内部创建Inner对象的方法如下:

匿名内部类是没有名字的内部类,它通常用于创建一个只需要使用一次的类。匿名内部类可以继承一个类或者实现一个接口,它没有构造方法,但可以使用构造代码块进行初始化。匿名内部类的定义格式如下:

在上面的例子中,父类构造器可以是有参数的构造器,也可以是无参数的构造器;接口可以是有多个方法的接口,也可以是只有一个方法的接口。下面是一个使用匿名内部类实现接口的例子:

在上面的例子中,我们创建了一个实现Animal接口的匿名内部类,并实现了move()方法。在main方法中,我们创建了一个Animal类型的对象,实际上是一个匿名内部类的对象,然后调用了它的move()方法。输出的结果为“动物在移动”。

Java中的异常指程序在运行过程中出现的错误或异常情况,例如类型错误、数组下标越界等。Java提供了异常处理机制来处理这些异常情况。

Java能提供的异常类是有限的,不可能为全世界出现的问题提供异常类来代表出现的问题。如果某个企业出现自己的问题,想要通过异常类来表示,那就需要自己来定义异常类了。接下来我们自己自定义异常类,假设我们需要收集每个用户合理的年龄,如果检测到某个用户输入的年龄不合理就抛出我们自定义的异常。

自定义运行时异常类:

自定义编译时异常类:

创建一个Java类,用于实现收集每个用户合理的年龄,如果检测到某个用户输入的年龄不合理就抛出我们自定义的异常:

当输入的年龄合理时:

当输入的年龄不合理时:

虽说当将异常对象抛给main方法或者main方法出现异常都可以将异常对象抛给JVM虚拟机,然后JVM虚拟机会进行处理后将异常信息抛给用户,这对于用户来说是极其影响体验的,所以接下来讲讲开发中一般会怎么处理异常。一般情况下处理异常会用以下几种方式:

第一种、捕获异常,然后将合适的信息响应给用户。

第二种、捕获异常,然后尝试修复异常。

在前面我们简单处理异常的时候,如果用户输入非int型的数据,那肯定是会抛出异常的。所以我们还是围绕收集每个用户合理的年龄这个主题,使用这两种异常处理的方式将之前的代码完善。

捕获异常,然后将合适的信息响应给用户:

我们进行输入测试结果:

捕获异常,然后尝试修复异常:

我们进行输入测试结果:

问:Java中的反射是什么呢?

答:在Java中,反射是指在程序运行时能够动态地获取类的信息并操作类的成员(属性、方法、构造方法等)的机制。

问:那Java中的反射需要学习些什么呢?

答:

反射学习第一步:将类的字节码文件加载到内存中,然后获取类的class对象。因为Java提供了一个class对象来代表字节码,所以获取类的字节码其实就是获取类的class对象,class对象会封装类的各种信息。

反射学习第二步:获取类的构造器,而构造器也是一个对象,去获取类中的构造器,那就会返回一个constructor对象,该对象就会封装构造器的各种信息。

反射学习第三步:获取类的成员变量和成员方法等,而成员变量和成员方法等都是一种对象。比如去获取类中的成员变量,会返回一个Field对象,就可以获取该对象的各种信息或者操作该对象;去获取类中的成员方法,会返回一个Method对象,就可以获取该对象的各种信息或者操作该对象。

总结起来反射就是学习动态地获取类的信息并学习如何操作它们。接下来我们一步一步讲。

反射学习第一步:将类的字节码文件加载到内存中,然后获取类的class对象:

获取class对象的几种方式:

接下来通过运行结果来验证结果是否正确:

反射学习第二步:获取类的构造器:

Class对象既然代表类,那么Class肯定会提供相应的方法去获取类的各种信息。所以获取类的构造器有以下几种方法:

首先我们需要写一个简单的类来学习:

第一种方式:获取全部构造器(只能获取public修饰的):

得到类的Class对象后,可以通过Class对象调用类的getConstructors方法得到类的全部构造器。但是这个方法会把类的每一个构造器都封装成构造器对象,然后把所有构造器对象放到一个构造器数组里面去。所以要使用以下方式获取类的全部构造器:

我们获取类的全部构造器后,可以进行验证:遍历构造器数组中的每一个构造器对象,并打印构造器名称和参数个数。

打印结果:

可以看到只打印出了一个有参数构造器,而无参数构造器并没有打印出来,因为getConstructors方法虽然可以得到类的全部构造器,但是只能获取public修饰的。

第二种方式:获取全部构造器(只要存在就能拿到):

得到类的Class对象后,想要通过Class对象得到类的全部构造器且只要存在就能拿到,那就需要使用getDeclaredConstructors方法。但是这个方法也会把类的每一个构造器都封装成构造器对象,然后把所有构造器对象放到一个构造器数组里面去。所以要使用以下方式获取类的全部构造器:

验证:遍历构造器数组中的每一个构造器对象,并打印构造器名称和参数个数。

打印结果:

这次可以看到将所有的构造器全部都拿到了,没有了只能获取public修饰的构造器这种限制。

剩下的两种方式与前两种方式类似,我就简单讲讲。

第三种方式:获取某一个构造器(只能获取public修饰的):

得到类的Class对象后,想要通过Class对象得到类的某一个构造器,可以通过getConstructor方法获取到,但是问题也是只能获取public修饰的。那怎么精准获取到想要的类的构造器呢?那就需要通过参数的差别:

获取某一个构造器的格式:Constructor 变量 = 类的class.getConstructor(Class<?>... parameterTypes);

获取有参数构造器:

获取无参数构造器:

打印结果:

可以看到打印出来了有参数构造器对象的名称和参数个数,但是获取无参数构造器却报错了,因为这个方式能获取public修饰的,而无参构造器却是private修饰的,所以获取不到报错了!

第四种方式:获取某一个构造器(只要存在就能拿到):

得到类的Class对象后,使用getConstructor方法去获取类的某一个构造器最大的问题就是只能获取public修饰的,而使用getDeclaredConstructor方法去获取类的某一个构造器就没有这个问题,因为getDeclaredConstructor方法也是只要存在就能拿到。

获取某一个构造器的格式:Constructor 变量 = 类的class.getDeclaredConstructor(Class<?>... parameterTypes);

获取有参数构造器:

获取无参数构造器:

打印结果:

这次可以看到不管是public修饰的构造器还是private修饰的构造器都打印出来了,没有了只能获取public修饰的构造器这种限制。

这四种获取类的构造器的方式全部讲完了,以下是完整代码:

我们获取到了构造器自然要使用,而反射获取构造器的作用主要是初始化对象并返回。要怎么使用呢?我简单的讲解一下,我先把Zoon类的有参数构造器从public修饰变为private修饰,然后如下使用反射获取的构造器:

现在反射第二步也算是完成了。

反射学习第三步:获取类的成员变量和成员方法:

获取类的成员变量和成员方法与获取类的构造器类似,我就简单讲讲。

我先把Zoon类的成员变量age和成员方法setName从public修饰的变为了private修饰的。

获取并使用类的成员变量:

打印结果:

获取类的成员方法并执行:

打印结果:

到这我们就将反射全部讲解完毕,我们已经充分认识了什么是反射,以及反射的核心作用是用来获取类的各个组成部分并执行他们。

1、先来了解一下dalvik虚拟机:

dalvik虚拟机是Android 5.0以前用于运行安卓应用的虚拟机,从 Android 4.4 开始,Google 开始引入了全新的虚拟机 ART(Android Runtime),直到Android5.0开始ART虚拟机就替代了dalvik虚拟机。既然dalvik虚拟机被ART虚拟机替代了,那我们还有学的必要吗?ART 是向下兼容的,ART虚拟机对DEX字节码的运行是兼容的,因此针对 Dalvik 开发的应用也能在 ART 环境中运作。但是Dalvik 采用的一些技术并不适用于 ART,但不妨碍我们了解和学习dalvik字节码。

2、我们再来了解一下dalvik寄存器和寄存器的命名方法:

Dalvik寄存器中的寄存器都是32位大小,支持所有类型,对于小于或等于32位的类型,使用一个寄存器就可以了;对于64位(long和double)类型,需要使用两个相邻的寄存器来存储。

寄存器的命名方法有两种:v命名法和p命名法:

​ v命名法:局部变量寄存器用v开头数字结尾的符号来表示,如v0、 v1、v2。

​ p命名法:函数参数寄存器则使用p开头数字结尾的符号来表示,如p0、p1、p2。

特别注意的是,p0不一定是函数中的第一个参数,在非static函数中,p0代指“this",p1表示函数的第一个 参数,p2代表函数中的第二个参数。而在static函数中p0才对应第一个参数(因为Java的static方法中没有this方法)

寄存器赋值:

const/4和const/16是表示定义一个4位和16位值,这里只是修饰一下,记住Dalvik寄存器是32位寄存器,这只是表示32位寄存器中存的值是4位和16位而已,不要被这个给影响了。

3、再来了解一下dalvik字节码的类型与Java数据类型的对应关系:

数据类型对应:

long类型和double类型都是64位,需要两个寄存器来存储,有时在smali代码中,原本从0到n的寄存器在中间少了一个,那就有可能是其中有数据是long类型或者double类型的,在赋值代码中虽然用到了两个寄存器,但隐藏了其中一个。

4、了解smali和Java之间的转换流程:

xxx.java ==> xxx.class ==> 使用dx工具将.class打包成.dex文件 ==> 使用baksmali工具将.dex文件反编译成.smali文件 ==> 反编译成smali文件后对该文件进行修改成功 ==> 使用samli工具将.smali文件打包成.dex文件(回编译)

5、了解dalvik方法:

.method表示定义了一个方法;

private、static、final表示该方法是一个一个私有、静态、不可变的方法;

onCreate$lambda-2是该方法定义的方法名,方法名后面括号中是该方法的参数;

[Ljava/lang/String;该参数表示接收字符串类型的一维数组,其中[表示一维数组,在smali的方法参数中每一个[就表示一维数组,比如[[就表示二维数组;java中类是一种数据类型,类用L表示,L后面接的是完整类名,也就是包名和类名。

6、了解dalvik字段:

dalvik创建字段需要指定字段的访问标志(修饰符)、字段名、字段类型和初始值等信息。下面是一个示例字段的创建代码:

.field private static count:I = 0

上述代码创建了一个名为count的私有静态字段,类型为整型,初始值为0。

1、dalvik指令集格式:

基础字节码-名称后缀/字节码后缀 目的寄存器 源寄存器/常量

Dalvik指令集中参数采用从源寄存器到目标寄存器的方式。

根据dalvik字节码的大小与类型不同,可能会添加名称后缀以消除岐义。具体来说,字节码的类型可以分为常规类型和特殊类型,根据类型的不同添加的后缀也不同,常规类型的字节码不添加任何后缀,而特殊类型的字节码根据具体类型添加后缀,可能是以下几种后缀之一:

常规类型的字节码主要有以下几种:

此外,根据字节码的大小与布局的不同,也可能添加字节码后缀以消除岐义。这些后缀通过在字节码主名称后添加斜杠“/”来分隔开。例如,一些指令可能会添加以下后缀:

-wide:64位宽度

-from16:源为16位寄存器引用

-to16:目标为16位寄存器引用

-range:指示该指令的操作数是一个范围。

32位常规类型的字节码没有添加任何后缀。

64位常规类型的字节码添加 -wide后缀。

例如这条指令:“move-wide/from16 vAA, vBBBB”:

2、dalvik指令集的使用:

空操作指令

数据操作指令

move指令的三种作用:

move指令集的使用:

返回指令(重点)

数据定义指令(重点)

const/4 vA,#+B:用于将一个4位的数值符号扩展为32后赋值给寄存器vA。其中,vA表示目标寄存器,#+B表示常量值,取值范围为-8到7。

const/16 vAA, #+BBBB:用于将一个16位的数据符号扩展为32位后赋给寄存器vAA。其中,vAA表示目标寄存器,#+BBBB表示常量值,取值范围为-32768到32767。

const vAA, #+BBBBBBBB:用于将一个32位的数值赋给寄存器vAA。其中,vAA表示目标寄存器,#+BBBBBBBB表示常量值,取值范围为-2147483648到2147483647。

const/high16 vAA, #+BBBB0000:用于将一个高16位的数值以低16位为零的方式扩展为32位后赋给寄存器vAA。其中,vAA表示目标寄存器,#+BBBB0000表示常量值的高16位。

const-wide/16 vAA,#+BBBB:用于将16位的数值符号扩展为64位后赋值给寄存器vAA和vAA+1。其中,vAA和vAA+1表示目标寄存器(因为dalvik寄存器是32位的,所以存储64位数据需要两个寄存器存储),#+BBBB表示常量值,取值范围为-32768到32767。

const-wide/32 vAA, #+BBBBBBBB:用于将32位的数值符号扩展为64位后赋值给寄存器vAA和vAA+1。其中,vAA和vAA+1表示目标寄存器,#+BBBBBBBB表示常量值。

const-wide vAA, #+BBBBBBBBBBBBBBBB:用于将64位的数值赋给寄存器vAA和vAA+1。其中,vAA和vAA+1表示目标寄存器,#+BBBBBBBBBBBBBBBB表示常量值。

const-wide/high16 vAA, #+BBBB000000000000:用于将一个高16位的数值以其他位为零的方式扩展为64位后赋给寄存器vAA和vAA+1。其中,vAA和vAA+1表示目标寄存器,#+BBBB000000000000表示常量值的高16位。

const-string vAA,string@BBBB:用于将字符串常量引用存储到寄存器vAA中,通过字符串ID或字符串。其中,vAA表示目标寄存器,string@BBBB表示字符串在常量池中的索引。

const-string/jumbo vAA, string@BBBBBBBB:与const-string类似,但用于存储较长的字符串常量引用。

const-class vAA,type@BBBB:用于将类对象常量存储到寄存器vAA中,通过类型ID或类型(如Object.class)。其中,vAA表示目标寄存器,type@BBBB表示类在常量池中的索引。

const-class/jumbo vAAAA, type@BBBBBBBB:与const-class类似,但const-class/jumbo指令占用两个字节,值为0xooff,并且其操作数比常规的const-class指令操作数更大。这是Android4.0中新增的指令。

实例操作指令

数组操作指令

array-length vA, vB:计算vB寄存器中数组引用的元素长度并将长度存入vA寄存器。

new-array vA, vB, type@CCCC:根据指定类型或类型ID(type@CCCC)与大小(vB寄存器存入数组的长度)构造一个数组,并将数组的引用赋给vA寄存器。

filled-new-array {vC, vD, vE, vF, vG}, type@BBBB:根据指定类型或类型ID(type@BBBB)与大小(vA)构造一个数组并填充数组内容,vA寄存器是隐含使用的,除了指定数组的大小外还指定了参数的个数,vC~vG是使用到的参数寄存器序号。指令会将寄存器 vC 到 vG 中的值填充到新数组中,并将新数组引用保存到寄存器 vA 中。还有一点需要注意,filled-new-array 指令创建的数组是一个新的对象,它与原数组没有任何关系。另外,该指令只能用于创建引用类型的数组,不能用于创建基本类型的数组。

filled-new-array/range {vCCCC .. vNNNN}, type@BBBB:filled-new-array/range 指令与 filled-new-array 指令非常类似,只是它的参数寄存器是以范围形式给出的,vCCCC 和 vNNNN 分别表示参数寄存器的起始位置和结束位置,type@BBBB 表示数组元素的类型。vC 是 filled-new-array/range 指令的第一个参数寄存器,表示数组大小。N=A+C-1 表示参数寄存器的结束位置,其中 A 表示数组大小,C 表示第一个参数寄存器的编号。因为 filled-new-array/range 指令的参数寄存器是连续的一段,所以 N 的值等于 A+C-1,即参数寄存器的结束位置。例如,如果 filled-new-array/range {v2 .. v5}, type@BBBB 指令被执行,那么数组大小为 v2 中存储的整数值,参数寄存器的编号为 v2、v3、v4、v5,因此 N=A+C-1=4+v2-1=3+v2。

fill-array-data vAA, 偏移量:用指定的字面量数组数据填充到目标数组中,字面量数组数据的位址是当前指令位址加偏移量的和。该指令填充的数组类型必须为基本类型的数组和字符串数组,其中基本类型包括 boolean、byte、short、char、int、float 和 double。填充数组时,字面量数组数据的每个元素都必须与目标数组的元素类型相同。另外,该指令只能用于填充已经创建的目标数组,不能用于创建新的数组对象。

举个例子,假设有如下代码:

异常指令

跳转指令(重点)

无条件跳转指令

分支跳转指令

packed-switch vAA, 索引表偏移量:实现一个switch语句,case常量是连续的,这个指令使用索引表,vAA是在表中找到具体case的指令偏移量的索引,如果无法在表中找到vAA对应的索引将继续执行下一个指令(即default case),执行完case中的代码后,如果Java源代码设置了break强制跳出switch,就会无条件跳转到所有case的出口处,如果没有强制跳出switch,就会执行完default case中的代码后再跳转到所有case的出口处继续执行代码。

举个例子,假设有如下代码:

Java代码:

smali代码:

sparse-switch vAA, 查询表偏移量:实现一个switch语句,case常量是非连续的,这个指令使用查询表,用于表示case常量和每个case常量的偏移量。如果vAA寄存器中的值无法在表中匹配将继续执行下一个指令(即default case)。执行完case中的代码后,如果Java源代码设置了break强制跳出switch,就会无条件跳转到所有case的出口处,如果没有强制跳出switch,就会执行完default case中的代码后再跳转到所有case的出口处继续执行代码。

举个例子,假设有如下代码:

Java代码:

smali代码:

条件跳转指令

if-eq vA, vB, 目标:如果vA == vB,跳转到目标。

if-ne vA, vB, 目标:如果vA != vB,跳转到目标。

if-lt vA, vB, 目标:如果vA < vB,跳转到目标。

if-ge vA, vB, 目标:如果vA >= vB,跳转到目标。

if-gt vA, vB, 目标:如果vA > vB,跳转到目标。

if-le vA, vB, 目标:如果vA <= vB,跳转到目标。

if-eqz vA, 目标:如果vA == 0,跳转到目标。

if-nez vA, 目标:如果vA != 0,跳转到目标。

if-ltz vA, 目标:如果vA < 0,跳转到目标。

if-gez vA, 目标:如果vA >= 0,跳转到目标。

if-gtz vA, 目标:如果vA > 0,跳转到目标。

if-lez vA, 目标:如果vA <= 0,跳转到目标。

比较指令

字段操作指令

iput-object vAA,vBB,字段ID:根据字段ID将vAA寄存器的值存入实例的对象引用字段,vBB寄存器中是该实例的引用。例如:

可以看到例子中将v0寄存器中Ljava/lang/String;类型的值(实例的对象引用)存入到p0寄存器中,p0寄存器中是Lbin/mt/apksignaturekillerplus/HookApplication;类型下字段名为appPkgName的实例字段,并且实例字段appPkgName的存储值类型为Ljava/lang/String;

iput-boolean vAA,字段ID:根据字段ID将vAA寄存器的值存入实例的boolean型字段,vBB寄存器中是该实例的引用。例如:

可以看到例子中将p1寄存器中boolean型的值存入到p0寄存器中,p0寄存器中是Lbin/mt/plugin/api/translation/TranslationEngine$Configuration;类型下字段名为acceptTranslated的实例字段,并且实例字段acceptTranslated的存储值类型为boolean。

iput-wide vAA,vBB,字段ID:根据字段ID将vAA,vAA+1寄存器的值存入实例的double/long型字段,vBB寄存器中是该实例的引用。例如:

可以看到例子中将v1,v2寄存器中long型的值存入到p0寄存器中,p0寄存器中是Lcom/alipay/android/phone/mrpc/core/l;类型下字段名为e的实例字段,并且实例字段e的存储值类型为long。

iput vAA,vBB,字段ID:根据字段ID将vAA寄存器的值存入实例的int型字段,vBB寄存器中是该实例的引用。例如:

可以看到例子中将v0寄存器中int型的值存入到p0寄存器中,p0寄存器中是Lcom/alipay/android/phone/mrpc/core/e;类型下字段名为a的实例字段,并且实例字段a的存储值类型为boolean。

iget-object vAA,vBB,字段ID:根据字段ID读取一个实例的对象引用字段到vAA,vBB寄存器中是该实例的引用。例如:

可以看到例子中将p0寄存器中Ljava/lang/String;类型的值(实例的对象引用)存入到v4寄存器中,p0寄存器中是Lbin/mt/apksignaturekillerplus/HookApplication;类型下字段名为appPkgName的实例字段,并且实例字段appPkgName的存储值类型为Ljava/lang/String;

iget-boolean vAA,vBB,字段ID:根据字段ID读取实例的boolean型字段到vAA,vBB寄存器中是该实例的引用。例如:

可以看到例子中将p0寄存器中boolean型的值存入到v1寄存器中,p0寄存器中是Lbin/mt/plugin/api/translation/TranslationEngine$ConfigurationBuilder;类型下字段名为acceptTranslated的实例字段,并且实例字段acceptTranslated的存储值类型为boolean。

iget-wide vAA,vBB,字段ID:根据字段ID读取实例的double/long型字段到vAA,vAA+1,vBB寄存器中是该实例的引用。例如:

可以看到例子中将p0寄存器中long型的值存入到v3,v4寄存器中,p0寄存器中是Lcom/alipay/android/phone/mrpc/core/l;类型下字段名为g的实例字段,并且实例字段g的存储值类型为long。

iget vAA,vBB,字段ID:根据字段ID读取实例的int型字段到vAA,vBB寄存器中是该实例的引用。例如:

可以看到例子中将v8寄存器中int型的值存入到v1寄存器中,v8寄存器中是Landroid/util/TypedValue;类型下字段名为data的实例字段,并且实例字段data的存储值类型为int。

sput-object vAA,字段ID:根据字段ID将vAA寄存器中的对象引用赋值到对象引用静态字段。例如:

可以看到例子中将v0寄存器中Ljava/text/SimpleDateFormat;类型的值(对象引用)存入到Lbin/tools/inject/InjectedLog;类型下的存储值为Ljava/text/SimpleDateFormat;类型的对象引用静态字段TIME_FORMAT1中。

sput-boolean vAA,字段ID:根据字段ID将vAA寄存器中的值赋值到boolean型静态字段。例如:

可以看到例子中将v6寄存器中boolean类型的值存入到Lcom/alipay/sdk/cons/a;类型下的存储值为boolean类型的静态字段r中。

sput-wide vAA,字段ID:根据字段ID将vAA,vAA+1寄存器中的值赋值到double/long型静态字段。例如:

可以看到例子中将v0,v1寄存器中long类型的值存入到Lcom/alipay/sdk/app/PayTask;类型下的存储值为long类型的静态字段i中。

sput vAA,字段ID:根据字段ID将vAA寄存器中的值赋值到int型静态字段。例如:

可以看到例子中将v0寄存器中int类型的值存入到Lcom/google/android/material/appbar/AppBarLayout;类型下的存储值为int类型的静态字段DEF_STYLE_RES中。

sget-object vAA,字段ID:根据字段ID读取静态对象引用字段到vAA。例如:

可以看到例子中将Ljava/lang/System;类型下的静态字段out中的Ljava/io/PrintStream;类型的值(对象引用)存入v17寄存器中。

sget-boolean vAA,字段ID:根据字段ID读取静态boolean型字段到vAA。例如:

可以看到例子中将Lbin/mt/plus/Features;类型下的静态字段f中的boolean类型的值存入v1寄存器中。

sget-wide vAA,字段ID:根据字段ID读取静态double/long型字段到vAA,vAA+1。例如:

可以看到例子中将Lcom/alipay/android/phone/mrpc/core/b;类型下的静态字段a中的long类型的值存入v2,v3寄存器中。

sget vAA,字段ID:根据字段ID读取静态int型字段到vAA。例如:

可以看到例子中将Landroid/os/Build$VERSION;类型下的静态字段SDK_INT中的int类型的值存入v0寄存器中。

方法调用指令(重点)

invoke-virtual {参数},方法名:调用带参数的实例的虚方法(虚方法相当于Java中的普通方法)。

invoke-virtual/range {vAA .. vBB},方法名:调用以寄存器范围为参数的虚方法。该指令第一个寄存器和寄存器的数量将会传递给方法。该指令与普通的invoke-virtual指令相比,可以同时传递多个参数,提高了效率。例如:

在这条指令中,寄存器范围为{v17 .. v18},调用java.io.PrintStream类的println(String)方法,该方法需要一个参数,参数类型为java.lang.String,并且不返回任何值。该指令的第一个寄存器为v17,寄存器的数量为2,因此v17和v18两个寄存器都将作为参数传递给方法,其中v18存储了传递给println()方法的参数值,v17则存储了PrintStream对象的引用。在执行该指令之前,需要先将参数值存储在寄存器v18中,将PrintStream对象的引用存储在寄存器v17中。该虚方法的作用是将参数值打印到控制台上。在Dalvik字节码中,方法的参数和返回值都是通过寄存器进行传递的。

invoke-super {参数},方法名:调用带参数的直接父类的虚方法。

invoke-super/range {vAA .. vBB},方法名:调用以寄存器范围为参数的直接父类的虚方法。该指令第一个寄存器和寄存器的数量将会传递给方法。

invoke-direct {参数},方法名:不解析直接调用带参数的方法,例如构造方法、静态语句块。

invoke-direct/range {vAA .. vBB},方法名:不解析直接调用以寄存器范围为参数的方法。该指令第一个寄存器和寄存器的数量将会传递给方法。

invoke-static {参数},方法名:调用带参数的静态方法。

invoke-static/range {vAA .. vBB},方法名:调用以寄存器范围为参数的静态方法。该指令第一个寄存器和寄存器的数量将会传递给方法。

invoke-interface {参数},方法名:调用带参数的接口方法。

invoke-interface/range {vAA .. vBB},方法名:调用以寄存器范围为参数的接口方法。该指令第一个寄存器和寄存器的数量将会传递给方法。

数据转换指令

数据运算指令

无论是普通类、抽象类、接口类还是内部类,在反编译出来的代码中,它们都是以单独的smali文件进行存放的。每个smali文件中的语句都遵循着一套语法规范,这里想要讲解的正是这套smali文件中的语法规范。

Smali中开头的包信息以及包信息格式:

.class <访问权限> [修饰关键字] <类名>

.super <父类名>

.source <源文件名>

例如:

静态字段定义

格式:

例如:

实例字段定义(java中的普通字段)

相比起静态字段定义,实例字段就少了一个静态修饰符static而已,格式:

例如:

直接方法定义

例如:

虚方法(java中的普通方法)

除了顶头的注释和直接方法不同,其他没什么区别,我们来看一个例子,例如:

接口

例如:

注解

在讲注解之前先解释一下什么是类型签名:在Java中,每个类、接口、数组、枚举、注解和基本类型都有一个唯一的类型签名。类型签名是一个字符串,用来描述该类型的全限定名、类型参数、维度等信息。类型签名的格式如下:

下面是类型签名的详细解释:

在Smali代码中,类型签名的格式与Java基本一致,只是用L来表示类类型,用[来表示数组类型,用T来表示类型变量。例如,类型签名Ljava/lang/Deprecated表示的是Java内置类Deprecated的类型签名。

注解的格式:

在Smali中,可以使用注解来提供额外的信息和指令给编译器和虚拟机。

以下是Smali注解的一些常见规范和详解:

注解的作用范围可以是类、方法或者字段。

如果注解的作用范围是类,".annotation"指令会直接定义在smali文件中,例如:

如果是方法或字段,".annotation"指令则会包含在方法或字段中定义。

注解方法,例如:

注解字段,例如:

到此Java和smali基础已经学习结束,接下来我们一起学习写一个简单的安卓APP,写完后我们一起把我们写的这个安卓APP逆向分析后破解掉,这样我们就会对安卓逆向和安卓开发之间的关系也有一定了解,并且也会提升我们的逆向水平。孙子曰:“知彼知己者,百战不殆;不知彼而知己,一胜一负,不知彼,不知己,每战必殆。”

那我们开始写一个简单的安卓APP吧!我们使用的工具是Android Studio,在写之前,我们需要安装Android Studio并创建一个项目:

安装Android Studio并创建项目可以参考这篇帖子:

Android studio安装与创建第一个helloworld项目_安卓helloworld项目下载_王 三 二的博客-CSDN博客

但是我们不使用empty activity项目类型,而是使用empty views activity项目类型,因为我的empty activity项目类型的项目和网上的完全不一样,反而是empty views activity项目类型的项目和网上的empty activity项目类型的项目是一致的,所以我就创建empty views activity项目类型的项目了。

配置好环境后我们就来写一个简单的安卓APP,作用就是从主Activity跳转到我们创建的Activity。

写一个安卓APP与写一个网站颇有相似之处,第一步我们需要去编写布局文件,下面是主Activity布局的XML代码:

让我们逐行解释每个元素的作用:

既然我们需要从主Activity跳转到我们创建的Activity,那么第二步我们就创建我们自己的Activity,那自然需要先去编写布局文件,下面是我们需要创建的Activity的布局文件:

让我们逐行解释每个元素的作用:

这个Activity的需要显示字符串被我写到了res/values/strings.xml文件中:

写完了我们需要创建的Activity布局文件,第三步我们需要在Java代码里面完成交互的逻辑编写:

让我们逐行解释每个元素的作用:

我们完成了我们自己创建的Activity在Java代码里面的交互逻辑编写,第四步是主Activity在Java代码里面的交互逻辑编写:

让我们逐行解释每个元素的作用:

现在完成了主Activity在Java代码里面的交互逻辑编写,最后一步便是将我们自己编写的Activiity在应用清单文件中声明:

到现在简单的安卓APP已经写完了,下面让我们看看我们自己编写的简单的安卓APP:

点击跳转后:

从结果来看,这个简单的安卓APP是编写成功了。但是我们开发安卓APP还未结束,还需将APK进行打包才可以将其安装在手机或者模拟器上,Android Studio打包APK可以参考下面这篇文章:

Android开发之打包APK详解_android打包apk_不服输的小乌龟的博客-CSDN博客

将APK打包成功后便可以成功安装在我们自己的手机或者模拟器上了,到此我们自己写的这个简单的安卓APP也就编写完成了。

下一步我们需要将自己写的安卓APP逆向了做一件事情——>那就是替换掉点击跳转按钮后跳转的Activity。

首先软件逆向第一步找到关键代码的位置是重中之重,而我们要完成替换掉点击跳转按钮后跳转的Activity那首先需要先找到跳转按钮,如果我们需要找的内容并未写在Native层,没有被加密,也不是从服务器返回的,那就主要从这几个方向找:首先肯定是直接搜索字符串;其次通过工具获取到要找内容的资源ID后再去资源文件夹寻找相关的资源调用;还有就是通过一些相关的消息框、按钮等这些控件的调用代码来寻找位置;最次就是从程序入口处一步步往下追去寻得蛛丝马迹。

而我们写的安卓APP简单,后三者皆可使用,要问我为什么第一种在这不好使,那是因为显示在屏幕上的字符串全写在了资源文件中。这里我就打算使用最次的方法,因为我们写的这个简单,而且还好运用刚刚学会的smali汇编语言。

首先我们可以直接通过MT管理器的Activity记录功能直接获得入口点Activity的类名,然后便可搜索类名"com.example.myapplication.MainActivity"寻到以下内容:

让我们逐行解释以上代码:

分析完以上代码后,我们下一步就需要将我们要替换成的Activity的新布局文件给写出来。

我并不打算去改Java代码,而是去添加一个布局文件供MyActivity类调用,你可能会问为什么不将这个布局文件替换掉原先的布局文件,这不是更加轻松吗?确实这样就不用去声明新添加资源的资源ID了,但是我们主要是要讲smali代码如何在实战中运用,当然要给smali代码多点戏份了。

点击跳转按钮后会触发MainActivityMainActivity1的实例,那下一步我们去看看MainActivity1的实例,那下一步我们去看看MainActivity1的实例:

让我们逐行解释以上代码:

分析完以上代码,我们可以去看看MyActivity类被反编译成smali代码后是什么样:

让我们逐行解释以上代码:

因为我们是给这个类替换一个新的布局文件,所以我们先不改它,我们需要先去将新的布局文件声明好,这我们需要做三件事情:

第一件:将新的布局文件丢res/layout文件夹里去;

第二件:在values\public.xml文件中声明我们新的布局文件的资源ID;

第三件:在R$layout类中声明我们新的布局文件的资源ID;

我们完成以上步骤后,我们需要将MyActivity类中的activity_my替换成activity_new:

以上寄存器要联系上下文,比如以上代码是将activity_new布局的资源ID储在寄存器v0中,再将v0寄存器中的资源ID作为参数传递给setContentView方法。所以寄存器要联系上下文,不要直接复制粘贴。

然后回编译成功后,再使用MT管理器对修改后的apk文件进行重新签名,让我们来看看最终的结果如何:

点击跳转后:

从结果来看我们成功完成了替换掉点击跳转按钮后跳转的Activity!现在这篇文章也到此结束,这篇文章重点是让大家对Java基础、smali汇编基础、安卓开发基础有简单的了解,最后还是那句话:“知彼知己者,百战不殆;不知彼而知己,一胜一负,不知彼,不知己,每战必殆。”

package com.java.TypeDome;
 
public class TypeDome {
    public static void main(String[] args) {
        // 1、自动类型转换
        // 占内存小的类型的变量可以赋值给内存占的大的类型的变量
        byte a = 10;
        int b = a;  // 发生了自动的类型转换
        System.out.println(a);
        System.out.println(b);
 
        System.out.println("---------------------------------------------");
 
        // 2、表达式的自动类型转换
        // 表达式的最终结果类型由表达式中的最高类型决定
        int i = 10;
        int j = 10;
        double ij = 1.0;
        // int ji = i + j + ij;  java: 不兼容的类型: 从double转换到int可能会有损失
        double ji = i + j + ij;
        System.out.println(ji);
        // 在表达式中,byte、short、char是直接转换成int类型参与运算的
        byte one = 20;
        byte two = 25;
        // byte three = one + two;  java: 不兼容的类型: 从int转换到byte可能会有损失
        int three = one + two;
        System.out.println(three);
 
        System.out.println("---------------------------------------------");
 
        // 3、强制类型转换
        // 强制类型转换可能造成数据的丢失
        // 下面的数据都为有符号位
        int o = 1500// 00000101 11011100
        byte t = (byte) o;  // 11011100
        System.out.println(t);  // 输出结果:-36
        // 小数强制转换为整数是直接舍弃小数保留整数
        double dou = 81.5;
        int interesting = (int) dou;
        System.out.println(interesting);  // 输出结果:81
    }
}
package com.java.TypeDome;
 
public class TypeDome {
    public static void main(String[] args) {
        // 1、自动类型转换
        // 占内存小的类型的变量可以赋值给内存占的大的类型的变量
        byte a = 10;
        int b = a;  // 发生了自动的类型转换
        System.out.println(a);
        System.out.println(b);
 
        System.out.println("---------------------------------------------");
 
        // 2、表达式的自动类型转换
        // 表达式的最终结果类型由表达式中的最高类型决定
        int i = 10;
        int j = 10;
        double ij = 1.0;
        // int ji = i + j + ij;  java: 不兼容的类型: 从double转换到int可能会有损失
        double ji = i + j + ij;
        System.out.println(ji);
        // 在表达式中,byte、short、char是直接转换成int类型参与运算的
        byte one = 20;
        byte two = 25;
        // byte three = one + two;  java: 不兼容的类型: 从int转换到byte可能会有损失
        int three = one + two;
        System.out.println(three);
 
        System.out.println("---------------------------------------------");
 
        // 3、强制类型转换
        // 强制类型转换可能造成数据的丢失
        // 下面的数据都为有符号位
        int o = 1500// 00000101 11011100
        byte t = (byte) o;  // 11011100
        System.out.println(t);  // 输出结果:-36
        // 小数强制转换为整数是直接舍弃小数保留整数
        double dou = 81.5;
        int interesting = (int) dou;
        System.out.println(interesting);  // 输出结果:81
    }
}
package com.java.ProcessControl;
 
import java.util.Scanner;
 
public class IfElse {
    public static void main(String[] args) {
        // if(条件表达式1) {
        //  条件表达式1返回值为true时执行的代码……
        // } else if(条件表达式2) {
        //  条件表达式1不成立但条件表达式2成立时执行的代码
        // }……else {
        //  如果上面的所有条件表达式都不成立时执行的代码
        // }
        Scanner sca = new Scanner(System.in);
        System.out.println("请输入该同学的成绩:");
        int grades = sca.nextInt();
        if (grades < 0 || grades > 100) {
            System.out.println("成绩不存在");
        }else  if (grades >= 90) {
            System.out.println("该同学必成龙凤!!");
        }else if (grades >= 80) {
            System.out.println("该同学成绩优秀");
        }else if (grades >= 70) {
            System.out.println("该同学成绩一般");
        }else if (grades >= 60) {
            System.out.println("该同学成绩及格,还需努力!");
        }else {
            System.out.println("挂科");
        }
    }
}
package com.java.ProcessControl;
 
import java.util.Scanner;
 
public class IfElse {
    public static void main(String[] args) {
        // if(条件表达式1) {
        //  条件表达式1返回值为true时执行的代码……
        // } else if(条件表达式2) {
        //  条件表达式1不成立但条件表达式2成立时执行的代码
        // }……else {
        //  如果上面的所有条件表达式都不成立时执行的代码
        // }
        Scanner sca = new Scanner(System.in);
        System.out.println("请输入该同学的成绩:");
        int grades = sca.nextInt();
        if (grades < 0 || grades > 100) {
            System.out.println("成绩不存在");
        }else  if (grades >= 90) {
            System.out.println("该同学必成龙凤!!");
        }else if (grades >= 80) {
            System.out.println("该同学成绩优秀");
        }else if (grades >= 70) {
            System.out.println("该同学成绩一般");
        }else if (grades >= 60) {
            System.out.println("该同学成绩及格,还需努力!");
        }else {
            System.out.println("挂科");
        }
    }
}
package com.java.circulate;
 
public class Circulate {
    public static void main(String[] args) {
        /*
        * for (初始化数据语句;循环条件;迭代语句) {
        *   循环代码;
        * }
        * */
        int sum=0;
        for (int i=1;i<6;i++) {
            sum += i;
        }
        System.out.println(sum);
 
        /*
        * while (循环条件) {
        *   循环语句;
        *   迭代语句;
        * }
        * */
        int i=1;
        while (i<=1000) {
            int num=1;
            int j=i;
            for (;i!=0;i/=10) {
                num*=(i%10);
            }
            i=j;
            if (num == i) {
                System.out.println(i);
            }
            i++;
        }
 
        /*
        * do {
        *   循环语句;
        *   迭代语句;
        * } while (循环条件);
        * do{}while循环的特点:一定会先执行一次循环体里的内容。
        * */
        int n=1;
        do {
            System.out.println("我是" + n);
            n++;
        }while (n%2==0);
 
        /*
        * break:跳出并结束当前所在循环的执行,或者结束所在switch分支的执行
        * continue:用于跳出当前循环的当次执行,进入下一次循环,只能在循环中使用
        * */
    }
}
package com.java.circulate;
 
public class Circulate {
    public static void main(String[] args) {
        /*
        * for (初始化数据语句;循环条件;迭代语句) {
        *   循环代码;
        * }
        * */
        int sum=0;
        for (int i=1;i<6;i++) {
            sum += i;
        }
        System.out.println(sum);
 
        /*
        * while (循环条件) {
        *   循环语句;
        *   迭代语句;
        * }
        * */
        int i=1;
        while (i<=1000) {
            int num=1;
            int j=i;
            for (;i!=0;i/=10) {
                num*=(i%10);
            }
            i=j;
            if (num == i) {
                System.out.println(i);
            }
            i++;
        }
 
        /*
        * do {
        *   循环语句;
        *   迭代语句;
        * } while (循环条件);
        * do{}while循环的特点:一定会先执行一次循环体里的内容。
        * */
        int n=1;
        do {
            System.out.println("我是" + n);
            n++;
        }while (n%2==0);
 
        /*
        * break:跳出并结束当前所在循环的执行,或者结束所在switch分支的执行
        * continue:用于跳出当前循环的当次执行,进入下一次循环,只能在循环中使用
        * */
    }
}
public class Student {
    private String name;
    private int age;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
public class Student {
    private String name;
    private int age;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
未知的叫声
汪汪汪
喵喵喵
未知的叫声
汪汪汪
喵喵喵
汪汪汪
喵喵喵
汪汪汪
喵喵喵
Outer.Inner inner = new Outer.Inner();
inner.print();
Outer.Inner inner = new Outer.Inner();
inner.print();
package com.java.day5_exception;
 
import java.util.Scanner;
 
public class ExceptionThree {
    public static void main(String[] args) {
        // 捕获到下层抛来的异常对象就告诉用户需要重新输入,并重新执行runs方法,直到用户输入的int型数据合理就会break
        while (true) {
            try {
                runs();
                break;
            } catch (Exception e) {
                System.out.println("您需要输入整数型数据,而非其他类型数据,需要您重新输入!");
            }
        }
    }
 
    // 如果捕获到AgeUnreasonableRuntimeException、AgeUnreasonableException异常对象就让用户重新输入。
    // 想要终止输入,就需要用户输入的int型数据合理就会break
    // 如果捕获到Exception异常对象而非我们自定义的异常对象,就将Exception异常对象抛给上层调用者。
    public static void runs() throws Exception{
        while (true) {
            System.out.println("请输入合理的年龄:");
            Scanner scan = new Scanner(System.in);
            int num = scan.nextInt();
            try {
                detection(num);
                detection2(num);
                break;
            } catch (AgeUnreasonableRuntimeException e) {
                System.out.println("您输入的年龄不合理!触发了运行时异常。");
            } catch (AgeUnreasonableException e) {
                System.out.println("您输入的年龄不合理!触发了编译时异常。");
            }
        }
    }
 
    // 编译时异常
    public static void detection2(int age) throws AgeUnreasonableException{
        if (age>0 && age<150) {
            System.out.println(age + "岁年龄是合理的");
        }else {
            throw new AgeUnreasonableException("The age you entered is" + age +  "years old is not reasonable");
        }
    }
 
    // 运行时异常
    public static void detection(int age) {
        if (age>0 && age<150) {
            System.out.println(age + "岁年龄是合理的");
        }else {
            throw new AgeUnreasonableRuntimeException("The age you entered is" + age +  "years old is not reasonable");
        }
    }
}
package com.java.day5_exception;
 
import java.util.Scanner;
 
public class ExceptionThree {
    public static void main(String[] args) {
        // 捕获到下层抛来的异常对象就告诉用户需要重新输入,并重新执行runs方法,直到用户输入的int型数据合理就会break
        while (true) {
            try {
                runs();
                break;
            } catch (Exception e) {
                System.out.println("您需要输入整数型数据,而非其他类型数据,需要您重新输入!");
            }
        }
    }
 
    // 如果捕获到AgeUnreasonableRuntimeException、AgeUnreasonableException异常对象就让用户重新输入。
    // 想要终止输入,就需要用户输入的int型数据合理就会break
    // 如果捕获到Exception异常对象而非我们自定义的异常对象,就将Exception异常对象抛给上层调用者。
    public static void runs() throws Exception{
        while (true) {
            System.out.println("请输入合理的年龄:");
            Scanner scan = new Scanner(System.in);
            int num = scan.nextInt();
            try {
                detection(num);
                detection2(num);
                break;
            } catch (AgeUnreasonableRuntimeException e) {
                System.out.println("您输入的年龄不合理!触发了运行时异常。");
            } catch (AgeUnreasonableException e) {
                System.out.println("您输入的年龄不合理!触发了编译时异常。");
            }
        }
    }
 
    // 编译时异常
    public static void detection2(int age) throws AgeUnreasonableException{
        if (age>0 && age<150) {
            System.out.println(age + "岁年龄是合理的");
        }else {
            throw new AgeUnreasonableException("The age you entered is" + age +  "years old is not reasonable");
        }
    }
 
    // 运行时异常
    public static void detection(int age) {
        if (age>0 && age<150) {
            System.out.println(age + "岁年龄是合理的");
        }else {
            throw new AgeUnreasonableRuntimeException("The age you entered is" + age +  "years old is not reasonable");
        }
    }
}
Class<?> a = Zoon.class// 想要完成反射第二步、第三步,那都需要得到类的Class对象
Class<?> a = Zoon.class// 想要完成反射第二步、第三步,那都需要得到类的Class对象
Constructor[] b = a.getConstructors();
Constructor[] b = a.getConstructors();
for (Constructor constructor : b) {
    System.out.println("这是第一种方式获得的构造器对象:"+constructor.getName()+",这个构造器有"+constructor.getParameterCount()+"个参数!");
}
for (Constructor constructor : b) {
    System.out.println("这是第一种方式获得的构造器对象:"+constructor.getName()+",这个构造器有"+constructor.getParameterCount()+"个参数!");
}
Class<?> a = Zoon.class// 想要完成反射第二步、第三步,那都需要得到类的Class对象
Class<?> a = Zoon.class// 想要完成反射第二步、第三步,那都需要得到类的Class对象
Constructor[] c = a.getDeclaredConstructors();
Constructor[] c = a.getDeclaredConstructors();
for (Constructor constructor : c) {
    System.out.println("这是第二种方式获得的构造器对象:"+constructor.getName()+",这个构造器有"+constructor.getParameterCount()+"个参数!");
}
for (Constructor constructor : c) {
    System.out.println("这是第二种方式获得的构造器对象:"+constructor.getName()+",这个构造器有"+constructor.getParameterCount()+"个参数!");
}
const/4 p5, 0x1 //p5赋值1
 
const/16 v0, 0xa //v0赋值10,在16进制里a表示10
const/4 p5, 0x1 //p5赋值1
 
const/16 v0, 0xa //v0赋值10,在16进制里a表示10
smali类型 java类型 注释
V void 无返回值
Z boolean 布尔值类型,返回0或1
B byte 字节类型,返回字节
S short 短整数类型,返回数字
C char 字符类型,返回字符
I int 整数类型,返回数字
J long (64位 需要2个寄存器存储) 长整数类型,返回数字
F float 单浮点类型,返回数字
D double (64位 需要2个寄存器存储) 双浮点类型,返回数字
string String 文本类型,返回字符串
Lxxx/xxx/xxx object 对象类型,返回对象
...
fill-array-data v0, :array_0  // 将字面量数组数据填充到 v0 寄存器所代表的数组中,:array_0为字面量数组数据的偏移地址(偏移量)
...
:array_0
    .array-data 2            // 表示每个数组元素占用两个字节
        0x14ebs             // 数组元素值为 0x14eb(short 类型)
        0x15f0s             // 数组元素值为 0x15f0(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
...
...
fill-array-data v0, :array_0  // 将字面量数组数据填充到 v0 寄存器所代表的数组中,:array_0为字面量数组数据的偏移地址(偏移量)
...
:array_0
    .array-data 2            // 表示每个数组元素占用两个字节
        0x14ebs             // 数组元素值为 0x14eb(short 类型)
        0x15f0s             // 数组元素值为 0x15f0(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
        0x15c7s             // 数组元素值为 0x15c7(short 类型)
...
packed-switch v0, :pswitch_data_5e   # 带着v0寄存器中的值跳转到索引表偏移量:pswitch_data_5e位址寻找对应的case指令偏移量的索引
 
    .line 120
    # default分支语句
    iget-wide v0, p0, Landroidx/constraintlayout/motion/utils/Oscillator;->PI2:D
 
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr v0, p1
 
    invoke-static {v0, v1}, Ljava/lang/Math;->sin(D)D
 
    move-result-wide p1
 
    return-wide p1
 
    .line 132
    :pswitch_17 # case 6
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr p1, v1
 
    rem-double/2addr p1, v1
 
    sub-double/2addr p1, v3
 
    invoke-static {p1, p2}, Ljava/lang/Math;->abs(D)D
 
    move-result-wide p1
 
    sub-double p1, v5, p1
 
    mul-double/2addr p1, p1
 
    :goto_25  # 所有case的出口,也可以说是在case中执行break后跳转到此处
    sub-double/2addr v5, p1
 
    return-wide v5
 
    .line 130
    :pswitch_27 # case 5
    iget-wide v0, p0, Landroidx/constraintlayout/motion/utils/Oscillator;->PI2:D
 
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr v0, p1
 
    invoke-static {v0, v1}, Ljava/lang/Math;->cos(D)D
 
    move-result-wide p1
 
    return-wide p1
 
    .line 128
    :pswitch_33 # case 4
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr p1, v3
 
    add-double/2addr p1, v5
 
    rem-double/2addr p1, v3
 
    goto :goto_25 # 跳转到偏移量为goto_25的目标位址
 
    .line 126
    :pswitch_3b # case 3
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr p1, v3
 
    add-double/2addr p1, v5
 
    rem-double/2addr p1, v3
 
    sub-double/2addr p1, v5
 
    return-wide p1
 
    .line 124
    :pswitch_44 # case2
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr p1, v1
 
    add-double/2addr p1, v5
 
    rem-double/2addr p1, v1
 
    sub-double/2addr p1, v3
 
    invoke-static {p1, p2}, Ljava/lang/Math;->abs(D)D
 
    move-result-wide p1
 
    goto :goto_25 # 跳转到偏移量为goto_25的目标位址
 
    :pswitch_51 # case 1
    const-wide/high16 v0, 0x3fe0000000000000L    # 0.5
 
    .line 122
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    rem-double/2addr p1, v5
 
    sub-double/2addr v0, p1
 
    invoke-static {v0, v1}, Ljava/lang/Math;->signum(D)D
 
    move-result-wide p1
 
    return-wide p1
 
    :pswitch_data_5e
    .packed-switch 0x1 # 索引表,case常量从1开始,依次递增
        :pswitch_51 # case 1
        :pswitch_44 # case 2
        :pswitch_3b # case 3
        :pswitch_33 # case 4
        :pswitch_27 # case 5
        :pswitch_17 # case 6
    .end packed-switch
packed-switch v0, :pswitch_data_5e   # 带着v0寄存器中的值跳转到索引表偏移量:pswitch_data_5e位址寻找对应的case指令偏移量的索引
 
    .line 120
    # default分支语句
    iget-wide v0, p0, Landroidx/constraintlayout/motion/utils/Oscillator;->PI2:D
 
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr v0, p1
 
    invoke-static {v0, v1}, Ljava/lang/Math;->sin(D)D
 
    move-result-wide p1
 
    return-wide p1
 
    .line 132
    :pswitch_17 # case 6
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr p1, v1
 
    rem-double/2addr p1, v1
 
    sub-double/2addr p1, v3
 
    invoke-static {p1, p2}, Ljava/lang/Math;->abs(D)D
 
    move-result-wide p1
 
    sub-double p1, v5, p1
 
    mul-double/2addr p1, p1
 
    :goto_25  # 所有case的出口,也可以说是在case中执行break后跳转到此处
    sub-double/2addr v5, p1
 
    return-wide v5
 
    .line 130
    :pswitch_27 # case 5
    iget-wide v0, p0, Landroidx/constraintlayout/motion/utils/Oscillator;->PI2:D
 
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr v0, p1
 
    invoke-static {v0, v1}, Ljava/lang/Math;->cos(D)D
 
    move-result-wide p1
 
    return-wide p1
 
    .line 128
    :pswitch_33 # case 4
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr p1, v3
 
    add-double/2addr p1, v5
 
    rem-double/2addr p1, v3
 
    goto :goto_25 # 跳转到偏移量为goto_25的目标位址
 
    .line 126
    :pswitch_3b # case 3
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr p1, v3
 
    add-double/2addr p1, v5
 
    rem-double/2addr p1, v3
 
    sub-double/2addr p1, v5
 
    return-wide p1
 
    .line 124
    :pswitch_44 # case2
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    mul-double/2addr p1, v1
 
    add-double/2addr p1, v5
 
    rem-double/2addr p1, v1
 
    sub-double/2addr p1, v3
 
    invoke-static {p1, p2}, Ljava/lang/Math;->abs(D)D
 
    move-result-wide p1
 
    goto :goto_25 # 跳转到偏移量为goto_25的目标位址
 
    :pswitch_51 # case 1
    const-wide/high16 v0, 0x3fe0000000000000L    # 0.5
 
    .line 122
    invoke-virtual {p0, p1, p2}, Landroidx/constraintlayout/motion/utils/Oscillator;->getP(D)D
 
    move-result-wide p1
 
    rem-double/2addr p1, v5
 
    sub-double/2addr v0, p1
 
    invoke-static {v0, v1}, Ljava/lang/Math;->signum(D)D
 
    move-result-wide p1
 
    return-wide p1
 
    :pswitch_data_5e
    .packed-switch 0x1 # 索引表,case常量从1开始,依次递增
        :pswitch_51 # case 1
        :pswitch_44 # case 2
        :pswitch_3b # case 3
        :pswitch_33 # case 4
        :pswitch_27 # case 5
        :pswitch_17 # case 6
    .end packed-switch
switch (transit) {
            case 4097:
                return 8194;
            case 4099:
                return 4099;
            case 8194:
                return 4097;
            default:
                return 0;
        }
switch (transit) {
            case 4097:
                return 8194;
            case 4099:
                return 4099;
            case 8194:
                return 4097;
            default:
                return 0;
        }
sparse-switch p0, :sswitch_data_e  # sparse-switch指令,传入寄存器p0的值作为查找表的索引,:sswitch_data_e是查询表的偏移量
                                   
    .line 2025                    # 源代码的行号
    :goto_4                       # 这里是所有case的出口,如果case常量没匹配上查询表中的值,那么v0寄存器中的值就为0,这里就返回零,也就是default下的代码return 0;
    return v0                     # 返回v0寄存器中的值
 
    .line 2016                    # 源代码的行号
    :sswitch_5                    # 标签,对应表中的第一个case常量0x1001
    const/16 v0, 0x2002           # 将0x2002赋值给v0寄存器                
    .line 2017                    # 源代码的行号
    goto :goto_4                  # 无条件跳转到:goto_4标签处执行
 
    .line 2019                    # 源代码的行号
    :sswitch_8                    # 标签,对应表中的第三个case常量0x2002
    const/16 v0, 0x1001           # 将0x1001赋值给v0寄存器      
    .line 2020                    # 源代码的行号
    goto :goto_4                  # 无条件跳转到:goto_4标签处执行
 
    .line 2022                    # 源代码的行号                      
    :sswitch_b                    # 标签,对应表中的第二个case常量0x1003
    const/16 v0, 0x1003           # 将0x1003赋值给v0寄存器
    goto :goto_4                  # 无条件跳转到:goto_4标签处执行
 
    .line 2014                    # 源代码的行号
    :sswitch_data_e               # 标签,作为查询表的偏移量
    .sparse-switch                # 表示接下来是一个查询表
        0x1001 -> :sswitch_5      # case常量0x1001跳转到标签:sswitch_5处执行
        0x1003 -> :sswitch_b      # case常量0x1003跳转到标签:sswitch_b处执行
        0x2002 -> :sswitch_8      # case常量0x2002跳转到标签:sswitch_8处执行
    .end sparse-switch            # 标志着查询表的结束
sparse-switch p0, :sswitch_data_e  # sparse-switch指令,传入寄存器p0的值作为查找表的索引,:sswitch_data_e是查询表的偏移量
                                   
    .line 2025                    # 源代码的行号
    :goto_4                       # 这里是所有case的出口,如果case常量没匹配上查询表中的值,那么v0寄存器中的值就为0,这里就返回零,也就是default下的代码return 0;
    return v0                     # 返回v0寄存器中的值
 
    .line 2016                    # 源代码的行号
    :sswitch_5                    # 标签,对应表中的第一个case常量0x1001
    const/16 v0, 0x2002           # 将0x2002赋值给v0寄存器                
    .line 2017                    # 源代码的行号
    goto :goto_4                  # 无条件跳转到:goto_4标签处执行
 
    .line 2019                    # 源代码的行号
    :sswitch_8                    # 标签,对应表中的第三个case常量0x2002
    const/16 v0, 0x1001           # 将0x1001赋值给v0寄存器      
    .line 2020                    # 源代码的行号
    goto :goto_4                  # 无条件跳转到:goto_4标签处执行
 
    .line 2022                    # 源代码的行号                      
    :sswitch_b                    # 标签,对应表中的第二个case常量0x1003
    const/16 v0, 0x1003           # 将0x1003赋值给v0寄存器
    goto :goto_4                  # 无条件跳转到:goto_4标签处执行
 
    .line 2014                    # 源代码的行号
    :sswitch_data_e               # 标签,作为查询表的偏移量
    .sparse-switch                # 表示接下来是一个查询表
        0x1001 -> :sswitch_5      # case常量0x1001跳转到标签:sswitch_5处执行
        0x1003 -> :sswitch_b      # case常量0x1003跳转到标签:sswitch_b处执行
        0x2002 -> :sswitch_8      # case常量0x2002跳转到标签:sswitch_8处执行
    .end sparse-switch            # 标志着查询表的结束
iput-object v0, p0, Lbin/mt/apksignaturekillerplus/HookApplication;->appPkgName:Ljava/lang/String;
iput-object v0, p0, Lbin/mt/apksignaturekillerplus/HookApplication;->appPkgName:Ljava/lang/String;
iput-boolean p1, p0, Lbin/mt/plugin/api/translation/TranslationEngine$Configuration;->acceptTranslated:Z
iput-boolean p1, p0, Lbin/mt/plugin/api/translation/TranslationEngine$Configuration;->acceptTranslated:Z
iput-wide v1, p0, Lcom/alipay/android/phone/mrpc/core/l;->e:J
iput-wide v1, p0, Lcom/alipay/android/phone/mrpc/core/l;->e:J
iput v0, p0, Lcom/alipay/android/phone/mrpc/core/e;->a:I
iput v0, p0, Lcom/alipay/android/phone/mrpc/core/e;->a:I
iget-object v4, p0, Lbin/mt/apksignaturekillerplus/HookApplication;->appPkgName:Ljava/lang/String;
iget-object v4, p0, Lbin/mt/apksignaturekillerplus/HookApplication;->appPkgName:Ljava/lang/String;
iget-boolean v1, p0, Lbin/mt/plugin/api/translation/TranslationEngine$ConfigurationBuilder;->acceptTranslated:Z
iget-boolean v1, p0, Lbin/mt/plugin/api/translation/TranslationEngine$ConfigurationBuilder;->acceptTranslated:Z
iget-wide v3, p0, Lcom/alipay/android/phone/mrpc/core/l;->g:J
iget-wide v3, p0, Lcom/alipay/android/phone/mrpc/core/l;->g:J
iget v1, v8, Landroid/util/TypedValue;->data:I
iget v1, v8, Landroid/util/TypedValue;->data:I
sput-object v0, Lbin/tools/inject/InjectedLog;->TIME_FORMAT1:Ljava/text/SimpleDateFormat;
sput-object v0, Lbin/tools/inject/InjectedLog;->TIME_FORMAT1:Ljava/text/SimpleDateFormat;
sput-boolean v6, Lcom/alipay/sdk/cons/a;->r:Z
sput-boolean v6, Lcom/alipay/sdk/cons/a;->r:Z
sput-wide v0, Lcom/alipay/sdk/app/PayTask;->i:J
sput-wide v0, Lcom/alipay/sdk/app/PayTask;->i:J
sput v0, Lcom/google/android/material/appbar/AppBarLayout;->DEF_STYLE_RES:I
sput v0, Lcom/google/android/material/appbar/AppBarLayout;->DEF_STYLE_RES:I
sget-object v17, Ljava/lang/System;->out:Ljava/io/PrintStream;
sget-object v17, Ljava/lang/System;->out:Ljava/io/PrintStream;
sget-boolean v1, Lbin/mt/plus/Features;->ۘf:Z
sget-boolean v1, Lbin/mt/plus/Features;->ۘf:Z
sget-wide v2, Lcom/alipay/android/phone/mrpc/core/b;->a:J
sget-wide v2, Lcom/alipay/android/phone/mrpc/core/b;->a:J
sget v0, Landroid/os/Build$VERSION;->SDK_INT:I
sget v0, Landroid/os/Build$VERSION;->SDK_INT:I
sget-object v17, Ljava/lang/System;->out:Ljava/io/PrintStream;
 
const-string/jumbo v18, "PmsHook success."
 
invoke-virtual/range {v17 .. v18}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
sget-object v17, Ljava/lang/System;->out:Ljava/io/PrintStream;
 
const-string/jumbo v18, "PmsHook success."
 
invoke-virtual/range {v17 .. v18}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.class public interface abstract Landroid/support/v4/os/IResultReceiver;
// 这个类是android.support.v4.os这个包下名为IResultReceiver的接口类
.super Ljava/lang/Object; // 该类继承了父类java.lang.Object
.source "IResultReceiver.java" // 这个smali文件在还是Java文件时,文件名为IResultReceiver.java
.class public interface abstract Landroid/support/v4/os/IResultReceiver;
// 这个类是android.support.v4.os这个包下名为IResultReceiver的接口类
.super Ljava/lang/Object; // 该类继承了父类java.lang.Object
.source "IResultReceiver.java" // 这个smali文件在还是Java文件时,文件名为IResultReceiver.java
**.field <访问权限> static [修饰关键字] <字段名>:<字段类型>**
**.field <访问权限> static [修饰关键字] <字段名>:<字段类型>**
.field private static final PARAMETER_BUFFER:Ljava/lang/ThreadLocal;
.field private static final PARAMETER_BUFFER:Ljava/lang/ThreadLocal;
.field <访问权限> [修饰关键字] <字段名>:<字段类型>
.field <访问权限> [修饰关键字] <字段名>:<字段类型>
.field public final mConstructorArgs:[Ljava/lang/Object;
.field public final mConstructorArgs:[Ljava/lang/Object;
# direct methods
.method <访问权限> [修饰关键字] <方法名称、参数还有返回值>
    <.registers>  // 指定了方法中寄存器的总数,这个数量是参数寄存器和本地寄存器的总和。
    <.local>  // 这个指令表明方法中非参数寄存器(本地寄存器)的个数
    [.param]  // 表明了方法的参数,每个.param指令表示一个参数,方法使用了几个参数就有几个.param指令。
    [.prologue]  // 表明了方法中代码的开始处
    [.line]  // 表名了该处代码在源代码中的行号
    <代码体>
.end method
# direct methods
.method <访问权限> [修饰关键字] <方法名称、参数还有返回值>
    <.registers>  // 指定了方法中寄存器的总数,这个数量是参数寄存器和本地寄存器的总和。
    <.local>  // 这个指令表明方法中非参数寄存器(本地寄存器)的个数
    [.param]  // 表明了方法的参数,每个.param指令表示一个参数,方法使用了几个参数就有几个.param指令。
    [.prologue]  // 表明了方法中代码的开始处
    [.line]  // 表名了该处代码在源代码中的行号
    <代码体>

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-3-31 22:44 被黎明与黄昏编辑 ,原因:
上传的附件:
收藏
免费 10
支持
分享
最新回复 (8)
雪    币: 3535
活跃值: (31011)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2023-8-24 14:13
1
雪    币: 3700
活跃值: (4091)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
收藏学习。
2023-8-25 12:51
0
雪    币: 2670
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
写的挺好的,感谢
2023-8-25 15:46
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
收藏了
2023-8-28 08:53
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
非常适合入门者学习,感谢!
2024-2-23 23:31
0
雪    币: 302
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
抽象类的例子里少了个getName方法
2024-2-27 16:47
0
雪    币: 1804
活跃值: (3437)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
8
椰子汁 抽象类的例子里少了个getName方法
我的疏忽,确实少了个getName方法,感谢提醒
2024-2-28 22:22
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
不错,学习了
2024-11-7 21:04
0
游客
登录 | 注册 方可回帖
返回
//