Smali学习笔记

Smali学习笔记

该文转载自乱码三千 – 分享实用IT技术

1.smali和Java基本数据类型对比

Javasmali
byteB
shortS
intI
longJ
floatF
doubleD
charC
booleanB
voidV
数组[
objectL+全类名路径(用/分割)

2.注释

在smli语言中注释用"#"表示
# 我是注释

3.类声明

.class +权限修饰符 +类名;
比如以下Java代码:
public class Test{
}
用smali代码表示为:
.class public LTest; # 声明类(必须)
.super Ljava/lang/Object; # 声明父类 默认继承Object(必须)
.implements Ljava/lang/CharSequence;#如果实现了接口则添加
.source "Test.java" # 源码文件(非必须)

4.关于分号;

凡是L开头全包名路径结尾都需要加分号

5.字段声明(成员/全局变量)

.field 权限修饰符+静态修饰符(如果是) +变量名:变量全类名路径;
比如以下Java代码:
private static String a;
用smali代码表示为:
.field private static a:Ljava/lang/String;    
补充:
基本数据类型示例:
.method public final pubFinalMethod()V
.field private boType:Z # boolean
.field private byteType:B # byte
.field private shortType:S # short
.field private charType:C # char
.field private intType:I # int
.field private longType:J # long
.field private floatType:F # float
.field private doubleType:D # double    

6.常量声明

.field 权限修饰符+静态修饰符(如果有) final +变量名:变量全类名路径;=常量值
比如以下Java代码:
private static final String a="hello";
用smali代码表示为:
.field private static final a:Ljava/lang/String;="hello"   
注意:
  1. 静态属性赋值在clinit代码块中进行:public static String a=“A”;

    .field public static a:Ljava/lang/String;
    .method static constructor <clinit>()V
        const-string v0,"A"
        sput-object v0,LTest;->a:Ljava/lang/String;
    	return-void
    .end method
    
  2. 非静态属性赋值(包括final)在init构造器中进行:public final String a=“A”;

    .field public final a:Ljava/lang/String;
    .method public constructor <init>()V
        invoke-direct {p0},Ljava/lang/Object;-><init>()
        const-string v0,"A"
        iput-object v0,p0,LTest;->a:Ljava/lang/String;
        return-void
    .end method    
    

7.成员方法/函数声明

.method 权限修饰符+静态修饰符(如果有) +方法名(参数类型)返回值类型
# 方法体
.end method # 方法结尾标志    
比如以下Java代码:
public static void getName(){}
用smali代码表示为:
.method public static getName()V
    return-void
.end method
7.1如果是带参并且带有返回值的方法
比如以下Java代码:
public String getName(String p){
    return "hello"
}
用smali代码表示为:
.method public getName(Ljava/lang/String;)Ljava/lang/String;
	const-string v0,"hello"
    return-object v0
.end method

8.关于方法返回关键字

主要有以下四种:
return-void
return-object
return
return-wide # 表示返回值为64位非对象类型的值(8个字节)  如:long、double   
数据类型队友关系表如下:
Javasmali方法返回关键字
charreturn
booleanreturn
bytereturn
shortreturn
intreturn
floatreturn
longreturn-wide
doublereturn-wide
voidreturn-void
数组return-object
objectreturn-object

9.构造方法/构造函数声明

.method 权限修饰符 constructor <init>(参数类型)返回值类型
	//super();
    invoke-direct {p0},Ljava/lang/Object;-><init>()V
    //逻辑代码
    return-void
.end method
比如以下Java代码
public class Test{
    public Test(String a){}
}
用smali代码表示为:
.method public constructor <init>(Ljava/lang/String;)V
    invoke-direct {p0},Ljava/lang/Object;-><init>()V
    return-void
.end method
需要注意的是内部类构造函数时,会先调用外部类的默认初始化
  1. 反射表示为:使用反射获取内部类实例对象时,需要将外部类实例对象传入,且只能通过clazz2.getConstructors()[0].newInstance(instance)获取实例对象

    public class Test{
        public class Test2{}
    }
    
    //反射获取Test2的对象
    Class<?> clazz = Class.forName("Test");//外部类
    test=clazz.getConstructor().newInstance();//外部类实例对象
    Class<?> clazz2 = Class.forName("Test$Test2");//内部类
    test2=clazz2.getConstructors()[0].newInstance(test);//内部类对象
    
    //错误的
    //test2=clazz2.getConstructor().newInstance(test);
    
    //如果是静态内部类,则不需要传入外部类的引用
    test2=clazz2.getConstructors()[0].newInstance();
    
  2. smali表示为:内部类newInstance的第一个参数必须是外部类实例的引用(如果是static内部类,则不需要传入外部类的实例)

    new-instance v0,LTest$Test2;
    //p0表示外部类this
    invoke-direct {v0,p0},LTest$Test2;-><init>(LTest;)V
        
    //静态内部类
    invoke-static {v0},LTest$Test2;-><init>()V
    
    原因:内部类定义了外部类的对象引用,在内部类初始化时,会通过iput方法将外部类的实例赋值给这个属性
    //synthetic合成的   固定写法
    .field final synthetic this$0:Lcom/hza/mytestdemo/TestDemo;
    
    .method public constructor <init>(Lcom/hza/mytestdemo/TestDemo;)V
        //将外部类实例赋值给this$0属性
        iput-object p1, p0, Lcom/hza/mytestdemo/TestDemo$InterClass;->this$0:Lcom/hza/mytestdemo/TestDemo;
        invoke-direct {p0}, Ljava/lang/Object;-><init>()V
        return-void
    .end method
    
    this$0说明:表示类内的第一层内部类,即类的内部类。
    this$1说明:表示类内的第二层内部类,即类的内部类的内部类。
    public class Test{
        public TestInner{}//this$0
    }
    //this$1表示类内的第二层内部类
    public class Test{
        public TestInner{
            public TestInner_Inner{}//this$1
        }//this$0
    }
    
  3. 匿名内部类当成普通属性反射即可

    class OuterClass {
        public Runnable runnable = new Runnable() {
            public void run() {
                System.out.println("runnable.");
            }
        };
    }
    Class<?> test = Class.forName("OuterClass");
    Field runnable = test.getDeclaredField("runnable");
    //获取属性对象
    Object o=runnable.get(test.newInstance());
    //设置属性对象
    runnable.set(test.newInstance(),new Runnable() {
                @Override
                public void run() {
                }
            });
    
    smali中匿名内部类:跟内部类一样,生成的文件是Test$1
    第二个匿名内部类是Test$2,依次类推,其余跟内部类一样
    new-instance v0, LTest$1;
    invoke-direct {v0, p0}, LTest$1;-><init>(LTest;)V
    
  4. smali中内部类的一些说明:

    system Ldalvik/annotation/EnclosingMethod;表示外部为方法,常见于匿名内部类
    system Ldalvik/annotation/EnclosingClass;表示外部为类,常见于内部类
    system Ldalvik/annotation/InnerClass;表示内部类
    accessFlags访问标识,一般内部类为0x0,内部接口为0x609
    system Ldalvik/annotation/MemberClasses;外部类中指明内部类
    //在内部类中
    .annotation system Ldalvik/annotation/EnclosingClass;//外部类说明
        value = LTest;//外部类全路径
    .end annotation
        
    .annotation system Ldalvik/annotation/InnerClass;//内部类说明
        accessFlags = 0x0 //访问标识,表示为普通内部类
        name = "InterClass" //内部类名称,表示LTest类的"InterClass"内部类
    .end annotation 
            
    //在外部类中
    .annotation system Ldalvik/annotation/MemberClasses;//类中的内部类成员
        value = {
            Lcom/hza/mytestdemo/TestDemo$InterClass1;,
            Lcom/hza/mytestdemo/TestDemo$InterClass2;,
        }
    .end annotation
    

10.静态代码块的声明

.method static constructor <clinit>()V
.end method
比如以下Java代码:
public class Test{
    static{      
    }
}
用smali代码表示为:
.method public static constructor <clinit>()V
.end method

11.方法调用

11.1关键字
invoke-virtual 用于非私有实例方法的调用
invoke-direct 用于构造方法及私有方法的调用
invoke-static 静态方法的调用
invoke-super 调用父类方法
invoke-interface 调用接口方法
11.2非私有实例方法得调用
invoke-virtual {参数}, 方法所属类名;->方法名(参数类型)返回值类型
比如以下Java代码:
public class Test{
    pubic Test(String a){
        getName();
    }
    public String getName(){
        return "hello";
    }
}
用smali代码表示为:
.method public constructor <init>(Ljava/lang/String;)V
	invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    invoke-virtual {p0}, LTest;->getName()Ljava/lang/String;
	move-result-object v0
	return-void
.end method
        
.method public getName()Ljava/lang/String;
	const-string v0,"hello"
    return-object v0
.end method
11.3私有方法或者构造方法的调用
invoke-direct {参数}, 方法所属类名;->方法名(参数类型)返回值类型
私有方法调用:
比如以下Java代码:
public class Test{
    pubic Test(String a){
        getName();
    }
    private String getName(){
        return "hello";
    }
}
用smali代码表示为:
.method public constructor <init>(Ljava/lang/String;)V
	invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    invoke-direct {p0}, LTest;->getName()Ljava/lang/String;
	move-result-object v0
	return-void
.end method
        
.method private getName()Ljava/lang/String;
	const-string v0,"hello"
    return-object v0
.end method
构造方法调用
比如以下代码:
public class Test{
    public Test(String a){
        new Test2("hello");
    }
    public class Test2{
        public Test2(String a){}
    }
}
用smali代码表示为:
# 匿名内部类得声明
.annotation system Ldalvik/annotation/MemberClasses;
	value={
        LTest$Test2;
    }
.end annotation

.method public constructor <init>(Ljava/lang/String;)V
	invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    # 创建类对象
    new-instance v0,LTest$Test2;
	# 定义参数
    const-string v1,"hello"
    # 创建对象
    invoke-direct {v0,p0,v1}, LTest&Test2;-><init>(LTest;Ljava/lang/String;)
	return-void
.end method
11.4静态方法的调用并返回值
invoke-static {参数}, 方法所属类名;->方法名(参数类型)返回值类型;
比如以下Java代码:
public class Test{
    public Test(String a){
        String b=getName();
        System.out.print(b);
    }
    public static String getName(){
        return "hello";
    }
}
用smali代码表示为:
.method public constructor <init>(Ljava/lang/String;)V
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    invoke-static {}, LTest;->getName()Ljava/lang/String;
	move-result-object v0
	return-void
.end method
.method public static getName()Ljava/lang/String;
	const-string v0,"hello"
    return-object v0
.end method    
11.5父类成员的方法调用
invoke-super

比如以下Java代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
}

用smali代码表示为:

.method protected onCreate(Landroid/os/Bundle;)V
    invoke-super {p0,p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
    return-void
.end method
11.6接口的调用
invoke-interface {参数}, 方法所属类名;->方法名(参数类型)返回值类型;
比如以下java代码:
public class Test{
    private InterTest a=new Test2();
    public Test(String a){}
    public void setAa(){
        InterTest aa=a;
        aa.est2();
    }
    public class Test2 implements InterTest{
        public Test2(){}
        public void est2(){}
    }
    interface InterTest{
        public void est2();
    }
}
用smali代码表示为:
.method public constructor <init>(Ljava/lang/String;)V
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    new-instance v0, LTest$Test2;
    invoke-direct {v0, p0}, Lest$Test2;-><init>(LTest;)V
	return-void
.end method
    
.method public setAa()V
    iget-object v0,p0,LTest;->a:
	invoke-interface {v0},LTest$InterTest;->est2()V
.end method

12.类使用注解

12.1注解说明
.annotation 注解属性(runtime、或者build之类的) 注解类目
    注解字段=注解值
.end annotation
如以下Java代码:
//属性
@MyAnnotation("Hello World!")
public String sayWhat;

//注释
public @interface MyAnnotation {
    String value();
}
转换为smali代码:
.field
    .annotation
    	value=
    .end annotation
.end field

.field public sayWhat:Ljava/lang/String;
    .annotation build Lcom/hza/mytestdemo/MyAnnotation;//注解属性 注解类目
        value = "Hello World!"//注解字段=注解值
    .end annotation
.end field

13.创建对象

13.1对象的创建分多步进行
//声明实例
new-instance v寄存器(存放对象引用) 对象全包名路径;
//调用构造方法实例化(如果调用有参构造,那么在调用之前还需要提前声明,然后在invoke的时候当作参数一并传入)
invoke-direct {变量名},对象全包名路径;-><init>(参数)返回类型

如:
new-instance v0,Ljava/lang/StringBuilder;
//参数
const-string v1,"hehe"
//调用有参构造方法
invoke-direct {v0,v1},Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
13.2数组的创建(数组本身也是对象)
//指定数组个数
const/4 v1,0x4
//声明数组实例
new-array v寄存器(存放数组对象引用),v寄存器(个数),[I
//填充数组
fill-array-data v寄存器(数组对象引用),:ayyay_8
:array_8
//填充
    .array-data
        0x0
    .end array-data
                                   
如:
const/4 v1,0x4
new-array v0,v1,[I
fill-array-data v0, :array_8
:array_8
.array-data 4
      0x0
      0x1
      0x2
      0x3
.end array-data                                   

14.数据的定义

14.1分三大类
  1. 字符串类型数据:const-string

  2. 字节码数据:const-class

  3. 数值型数据:const

    数值型数据拆分
    第一种 const开头 占用一个容器(寄存器) 32位/容器(第一位默认为符号位) 
    const (/4/16/high16) vA,xx表示将xx赋值给vA
    const v寄存器,数值   
        *const/4:最大值允许存放4位数值(4个二进制位) 符号位第二位第三位第四位(-8~7)
        *const/16最大值允许存放16位数值    
        *const/32最大32位  
        *const/high16 v0,0xFFFF0000    
    
    第二种 const-wide 占用两个容器(寄存器) 64位
    const-wide(/16/32/high16) vAA,xxxx表示将xxxx赋值给寄存器vAA、vAA+1    
    const-wide v寄存器,数值L  //占用v0和v1        
    
14.2总结
1.const-string
//String str="hello";    
const-string v0,"hello" //定义字符串 将字符串hello赋值给v0

2.const-class
//Class clazz=GoActivity.class;
const-class v0,LGoActivity;  

3.const
//以下数据定义高位默认为符号位
const/4 v0,0x2 # 定义一个容器 最大只允许存放半字节4位数据 取值范围为 -8 and 7
const/16 v0 , 0xABCD # 定义定义一个容器 最大只允许存放16位数据 比如short类型数据 取值范围为-32768~32767
const v0 , 0xA# 定义一个容器 最大只允许存放32位数据,比如int类型数据 将数字10赋值给v0 取值范围-2147483647~2147483647
const/high16 #定义一个容器 最大只允许存放高16位数值 比如0xFFFF0000末四位补0 存入高四位0XFFFF

//const-wide 占用两个寄存器vx和v(x+1)且这两个寄存器相连, 数值必须以L结尾 否则编译不通过
const-wide/16 # 定义两个相连容器 最大只允许存放16位数据
const-wide/32 # 定义两个相连容器 最大只允许存放32位数据
const-wide # 定义两个相连容器 最大只允许存放64位数据
const-wide/high16 # 定义两个相连容器 只允许存放高16位数据    

15.字段赋值/取值

赋值(iput-object/sput-object):->
  • static显式初始化在clinit构造器中,非static显式初始化在init构造器中
  • iput-object v0,p0,LTest;->a:Ljava/lang/String;//将v0的值赋给p0的a属性
  • sput-object v0,LTest;->a:Ljava/lang/String;//将v0的值赋给Test.a
取值(iget-object/sget-object):<-
  • iget-object v0,p0,LTest;->a:Ljava/lang/String;//将p0的a属性的值赋给v0寄存器
  • sget-object v0,LTest;->a:Ljava/lang/String;//将Test.a赋给v0寄存器
15.1静态字段赋值
关键代码
sput-object 源寄存器(赋值内容),类全包名路径;->字段:字段类型全包名路径//s指static
比如以下Java代码:
public class Test{
    private static String a="g";
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.source "Test.java" 
.field private static a:Ljava/lang/String;
.method public constructor <clinit>(Ljava/lang/String;)V
    const-string v0, "g"
    sput-object v0,LTest;->a:Ljava/lang/String;
    return-void
.end method
15.2类非静态字段赋值
关键代码
iput-object 源寄存器(赋值内容),目的寄存器(对象引用),类全包名路径;->字段:字段类型全包名路径//i指instance
如以下Java代码:
public class Test{
    private String a="g";
    public Test(String a){
    }
    public void setAa(){
        a="b";
    }
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.source "Test.java" 
.field private a:Ljava/lang/String;
.method public constructor <init>(Ljava/lang/String;)V
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    const-string v0, "g"
    iput-object v0, p0, LTest;->a:Ljava/lang/String;
    return-void
.end method
.method public setAa()V
    const-string v0, "b"
    iput-object v0, p0, LTest;->a:Ljava/lang/String;
    return-void
.end method
15.3静态字段取值
关键代码
sget-object 目的寄存器,类全包名路径;->字段:字段类型全包名路径
比如以下Java代码:
public class Test{
    private static String a="hello";
    public Test(String a){
    }
    public void setAa(){
        String aa=a;
    }
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.source "Test.java" 
.field private a:Ljava/lang/String;
.method public constructor <clinit>()V
    const-string v0, "hello"
    iput-object v0, p0, LTest;->a:Ljava/lang/String;
    return-void
.end method
.method public constructor <init>(Ljava/lang/String;)V
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method
.method public setAa()V
    sget-object v0,LTest;->a:Ljava/lang/String;
    return-void
.end method
15.4类非静态字段取值
关键代码:
iget-object 目的寄存器,源寄存器,类全包名路径;->字段:字段类型全包名路径
比如以下Java代码:
public class Test
{
    private  String a="hello";
    public Test(String a){
    }
    public void getA(){
       String aa=a;
   }
    
}
用smali代码表示为:
.class public LTest;
.super Ljava/lang/Object;
.source "Test.java" 
.field private a:Ljava/lang/String;
.method public constructor <init>(Ljava/lang/String;)V
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    const-string v0, "hello"
    iput-object v0, p0, LTest;->a:Ljava/lang/String;
    return-void
.end method
.method public getA()V
    iget-object v0, p0, LTest;->a:Ljava/lang/String;
    return-void
.end method
15.5注意:以上赋值/取值方法都是以String对象举例,如果是基本数据类型,那么按照如下处理
const/4 v0,0x1 //实例变量值内容定义 值皆为十六进制
smali取值赋值和值定义关键字java
iget-byte、iput-byte、const/4byte
iget-short、iput-short、const/4short
iget-char、iput-char、const/16char
iget-boolean、iput-boolean、const/4boolean
iget、iput、const/4int
iget、iput、const/high16float
iget-wide、iput-wide、const/high16double
iget-wide、iput-wide、const-wide/16long
iget-object、iput-object引用数据类型

16.基本运算

加减乘除描述
int-to-double v1,v0基本运算转换(转换v0存入v1)
add-int/2addr v1,v0两个int数值v0,v1相加(结果存入v1)
add-int v2,v1,v0两个int数值v0,v1相加(结果存入v2)
位运算描述
or-int/lit8 v1,v0,0x1v0进行或运算|结果放入v1
and-int/lit8 v1,v0,0x1v0进行与运算&结果放入v1
shr-int/lit8 v1,v0,0x1v0进行与右移>结果放入v1
shl-int/lit8 v1,v0,0x1v0进行左移<结果放入v1
rem-int/lit8 v1,v0,0x1v0进行取模结果放入v1
16.1加法(减法sub、乘法mul、除法div)
如以下Java代码:
int a = 1;
float b = 1.5f;
return  a + b;
对应的smali代码为:
const/4 v0, 0x1 //int a=1;
const/high16 v1, 0x3fc00000 //float b=1.5f
int-to-float v2, v0 //将int转为float
add-float/2addr v2, v1 //将v1 v2的值相加,结果存入v2
add-float v3 v2, v1 //将v1 v2的值相加,结果存入v3    
16.2布尔运算(了解 &有对应的smali关键字,&&没有按条件语句判断)
如以下Java代码:
private boolean bool(boolean a, boolean b,boolean c) {
        return a && b || c;
}
对应的smali代码为:
.method private bool(ZZZ)Z
    .registers 5
    .param p1, "a"    # Z
    .param p2, "b"    # Z
    .param p3, "c"    # Z
    .prologue
    .line 11
    if-eqz p1, :cond_4
    if-nez p2, :cond_6
    :cond_4
    if-eqz p3, :cond_8
    :cond_6
    const/4 v0, 0x1
    :goto_7
    return v0
    :cond_8
    const/4 v0, 0x0
    goto :goto_7
.end method

17.逻辑语句

17.1条件跳转分支
//v0与v1比较  比如if-lt v0,v1 如果v0<v1
if-eq v0,v1, :cond_1//equal
if-ne v0,v1, :cond_1//not equal
if-lt v0,v1, :cond_1//less than
if-ge v0,v1, :cond_1//great equal   
if-le v0,v1, :cond_1//less equal   
if-eqz v0,v1, :cond_1//equal 0     
if-nez v0,v1, :cond_1//not equal 0
if-ltz v0,v1, :cond_1//less than 0   
if-lez v0,v1, :cond_1//less equal 0     
if-gez v0,v1, :cond_1//great equal 0    
if-gtz v0,v1, :cond_1//great than 0     
17.2循环
const/4 v0,0x0int i=0
const/4 v1,0xa10为终止条件
if-ge v0,v1如果i>=10
add-int/lit8 v0,v0,0x1i=i+1
比如以下Java代码:
for(int i=0;i<=10;i++){}
对应的smali代码为:
const/4 v0, 0x0 //int i=0
:goto_1
const/16 v1, 0xa //10
if-ge v0, v1, :cond_8 //if i>=10
add-int/lit8 v0, v0, 0x1
goto :goto_1 //继续循环
:cond_8 //退出循环
return-void

18.smali语法关键字

.line:表示Java中的一行
:cond_0:条件分支,配合if使用
.prologue:程序的开始,可省略
:goto_0:跳转分支,配合goto关键字使用
.local:显示局部变量别名信息
.local v2, "b":D //v2存储double类型的变量b
.locals N:标明这个函数中最少要用到的本地寄存器的个数,即v寄存器(v0~v15)
.register N:标明这个函数中最少要用到的寄存器个数,即p寄存器+v寄存器

19.一些Dalvik指令操作

  • 返回操作

    • return
    • return-void
    • return-object
    • return-wide
  • 赋值操作

    • const
    • const-string
    • const-class
  • get/put操作

    • get/put-byte
  • get/put-char

    • get/put-short
    • get/put
    • get/put-object
    • get/put-wide
  • 移位操作

    • move

      move v1,v2将v2的值移到v1
      move/from 16 v1,v2将16位的v2的值移到8位的v1
      move/16 v1,v2将16位的v2的值移到16位的v1
    • move-wide

      move-wide v1,v2将v2的值移到v1
      move-wide/from 16 v1,v2将16位的v2的值移到8位的v1
      move-wide/16 v1,v2将16位的v2的值移到16位的v1
    • move-object

      move-object v1,v2将v2的值移到v1
      move-object/from 16 v1,v2将16位的v2的值移到8位的v1
      move-object/16 v1,v2将16位的v2的值移到16位的v1
    • move-result

      move-result v1将结果移到v1
    • move-result-object

      move-result-object v1将结果移到v1
    • move-result-wide

      move-result-wide v1将结果移到v1寄存器对
    • move-exception

      move-exception v1将异常移到v1

PDF下载地址smali学习笔记

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值