从入门到精通汇编语言 第七章(高级汇编语言技术)

参考教程:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili

一、子程序的另一种写法

1、另一种写法

(1)类似于C语言,汇编语言中也可以将子程序写得像子函数一样可读性更强(C语言中的子函数有花括号做边界)。

(2)具体格式如下,用CALL命令调用子程序时使用子程序名即可

<子程序名> proc    ;子程序起始

        <子程序标号>:  

                <子程序内容>

                ret/retf    ;子程序返回(ret/retf取决于是近调还是远调)

<子程序名> endp    ;子程序结束

2、举例

(1)原汇编程序:

assume cs:code, ss:stack
stack segment
            db 16 dup (0)
stack ends
code segment
    start: 	mov ax, stack
            mov ss, ax
            mov sp, 16
            mov ax, 1000
            call s 			    ;调用子程序
            mov ax, 4c00h
            int 21h
    s: 		add ax,ax 		    ;子程序开始
            ret 				;子程序返回
code ends
end start

(2)近调用改写:

assume cs:code, ss:stack
stack segment
            db 16 dup (0)
stack ends
code segment
main proc					;主程序起始
    start: 	mov ax, stack
            mov ss, ax
            mov sp,16
            mov ax, 1000
            call subp 		;调用子程序
            mov ax, 4c00h
            int 21h
main endp					;主程序结束
subp proc					;子程序subp起始
    s: 		add ax, ax
            ret				;子程序subp返回
subp endp					;子程序subp结束
code ends
end start

(3)远调用改写:

assume cs:code, ss:stack
stack segment
            db 16 dup (0)
stack ends
code segment
main proc					    ;主程序起始
    start: 	mov ax, stack
            mov ss, ax
            mov sp, 16
            mov ax,1000
            call far ptr subp 	;调用子程序
            mov ax, 4c00h
            int 21h
main endp					    ;主程序结束
subp proc						;子程序subp起始
    s: 		add ax, ax
            retf				;子程序subp返回
subp endp				    	;子程序subp结束
code ends
end start

二、程序的多文件组织

1、多文件程序的编写和编译/链接

(1)一个程序不一定只能使用一个.asm文件编写,它可以分布在若干个.asm文件中,如同C语言一样。

(2)使用在其它文件中的子程序时,需要在该文件头部声明欲使用的子程序在其它文件已有定义,格式为“extrn <子程序名>:far”

(3)文件中定义的子程序被外部使用时,需要在该文件头部声明子程序可被外部使用,格式为“public <子程序名>”。

(4)所有文件编写好后,需要对它们分别进行编译,编译完成后要将它们链接在一起

2、举例

(1)原汇编程序:

assume cs:code, ss:stack
stack segment
            db 16 dup (0)
stack ends
code segment
    start: 	mov ax, stack
            mov ss, ax
            mov sp, 16
            mov ax, 1000
            call s 			    ;调用子程序
            mov ax, 4c00h
            int 21h
    s: 		add ax,ax 		    ;子程序开始
            ret 				;子程序返回
code ends
end start

(2)改写的汇编程序:

①编写p1.asm文件:

extrn subp:far						;声明要使用的外部子程序subp
assume cs:code, ss:stack
stack segment stack
            db 16 dup (0)
stack ends
code segment
main proc
    start: 	mov ax, stack
            mov ss, ax
            mov sp, 16
            mov ax, 1000
            call far ptr subp		;调用外部子程序subp
            mov ax, 4c00h
            int 21h
main endp
code ends
end start

②编写p2.asm文件:

public subp					;声明外部程序要使用的子程序subp
assume cs:code
code segment
subp proc
    s: 	add ax,ax
        retf
subp endp
code ends
end

③在DOSBox中输入“p1.asm”并按下回车,编译p1.asm文件生成p1.obj文件,然后输入“p2.asm”并按下回车,编译p2.asm文件生成p2.obj文件,最后输入“link p1.obj+p2.obj”,将二者链接,生成可执行文件。

三、汇编指令汇总(并非全部详细介绍)

1、数据传送指令

(1)通用数据传送指令:MOV、PUSH、POP、XCHG。

(2)累加器专用传送指令:IN、OUT、XLAT。

(3)地址传送指令:LEA、LDS、LES。

(4)标志寄存器传送指令:LAHF、SAHF、PUSHF、POPF。

(5)类型转换指令:CBW、CWD。

2、算术指令

(1)加法指令:ADD、ADC、INC。

(2)减法指令:SUB、SBB、DEC、NEG、CMP。

(3)乘法指令:MUL、IMUL。

(4)除法指令:DIV、IDIV。

(5)十进制调整指令:DAA、DAS、AAA、AAS、AAM、AAD。

压缩的BCD码(用4位二进制数表示1位十进制数)调整指令:

        ● DAA——加法的十进制调整指令

        ● DAS——减法的十进制调整指令

非压缩的BCD码(用8位二进制数表示1位十进制数)调整指令:

        ● AAA——加法的ASCII码调整指令

        ● AAS——减法的ASCII码调整指令

        ● AAM——乘法的ASCII码调整指令

        ● AAD——除法的ASCII码调整指令

3、逻辑指令

(1)逻辑运算指令:AND、OR、NOT、XOR、TEST。

(2)移位指令:SHL、SHR、SAL、SAR、ROL、ROR、RCL、RCR。

4、串处理指令

(1)设置方向标志指令:CLD、STD。

(2)串处理指令:MOVSB / MOVSW、STOSB / STOSW、LODSB / LODSW、CMPSB / CMPSW、SCASB / SCASW。

(3)重复执行指令前缀:REP、REPE / REPZ、REPNE / REPNZ。

5、控制转移指令

(1)无条件转移指令:JMP。

(2)条件转移指令:JZ / JNZ、JE / JNE、JS / JNS、JO / JNO、JP / JNP、JB / JNB、JL / JNL、JBE / JNBE、JLE / JNLE、JCXZ。

(3)循环指令:LOOP、LOOPZ / LOOPE、LOOPNZ / LOOPNE。

(4)子程序调用和返回指令:CALL、RET。

(5)中断与中断返回指令:INT、INTO、IRET。

6、处理机控制与杂项操作指令

(1)标志处理指令:CLC、 STC、 CMC、CLD、STD、CLI、STI。

(2)其它处理机控制与杂项操作指令:

①NOP——无操作(机器码占一个字节)。

②HLT——暂停机(等待一次外中断,之后继续执行程序)。

③WAIT——等待(等待外中断,之后仍继续等待)

④ESC——换码。

⑤LOCK——封锁(维持总线的锁存信号,直到其后的指令执行完)。

四、汇编伪操作汇总(并非全部详细介绍)

1、汇编指令与伪操作

(1)汇编指令对应机器指令,在程序运行期间由计算机执行。

(2)伪操作指的是在汇编程序对源程序汇编期间,由汇编程序处理的操作,可以完成如数据定义、分配存储区、指示程序结束等功能。

2、处理器选择伪操作

(1).8086——选择8086指令系统。

(2).286——选择80286指令系统。

(3).286P——选择保护模式下的80286指令系统。

(4).386——选择80386指令系统。

(5).386P——选择保护模式下的80386指令系统。

(6).486——选择80486 指令系统。

(7).486P——选择保护模式下的80486指令系统。

(8).586——选择Pentium 指令系统。

(9).586P——选择保护模式下的Pentium指令系统。

3、段定义伪操作

(1)定义段的基本格式:

assume cs:<代码段名>, ds:<数据段名>, es:<附加段名>

<数据段名> segment ; 定义数据段

                …

<数据段名> ends

;----------------------------------------

<附加段名> segment ; 定义附加段

                …

<附加段名> ends

;----------------------------------------

<代码段名> segment ; 定义代码段

        start:

                …

<代码段名> ends

end start

(2)在定义段时还可以添加段声明,如下所示:

ASSUME <段寄存器>:<段名>

<段名> SEGMENT [<定位类型>] [<组合类型>] [<使用类型>] ['<类别>']

        ……

        …… ; 语句序列

<段名> ENDS

①定位类型align_type用于指定段在分配空间时的策略,可选的选项有PARA、BYTE、WORD、DWORD、PAGE。

②组合类型combine_type用于当多个文件编译链接时指定段的共享范围,可选的选项有PRIVATE、PUBLIC、COMMON、STACK、AT exp。

③使用类型use_type用于指定使用内存的方式,可选的选项有USE16(16位)、USE32(32位)。

(3)简化版的段定义伪操作有:“.code [name]”、“.data”、“.data?”、“.fardata [name]”、“.fardata? [name]”、“.const”、“.stack [size]”。

(4)定义段的存储模式(指定在内存中如何安放各段):

①使用伪操作“.MODEL <存储模式> [,其它选项]”。

②存储模式可选的选项有tiny、small、medium、compact、large、huge、flat。

(5)举例——简写程序:

①非简化版:

assume cs:codesg, ss:stacksg, ds:datasg
datasg segment
            str db 'hello world!$' 
datasg ends
stacksg segment
            db 32 dup (0)
stacksg ends
codesg segment
    start: 	mov ax, datasg
            mov ds, ax
            mov ax, stacksg
            mov ss, ax
            mov sp, 20h
            lea bx, str
    output:	mov dl, [bx]
            cmp dl, '$' je stop
            mov ah, 02H
            int 21h
            inc bx
            jmp output
    stop: 	mov ax,4c00h
            int 21h
codesg ends
end start

②简化版:

.8086
.MODEL small 
.data
            str db 'hello world!$' 
.stack 20H
.code
    start: 	mov ax, @data
            mov ds, ax
            lea bx, str
    output:	mov dl, [bx]
            cmp dl, '$'
            je stop
            mov ah, 02H
            int 21h
            inc bx
            jmp output
    stop:	mov ax, 4c00h
            int 21h
end start

4、程序开始和结束伪操作

(1)“TITLE text”操作可为子程序起一个标题。

(2)“NAME module_name”操作可为子程序起一个模块名。

(3)“END [ label ]”操作指定子程序在此行结束,且子程序在标号label处起始。

(4)“. STARTUP”操作用于初始化DS寄存器,相当于“mov ax, @data”和“mov ds, ax”。

(5)“. EXIT [ return_value ]”操作用于结束程序,相当于“mov ax, 4c00h”、“int 21h”。

5、数据定义及存储器分配伪操作

(1)伪操作“<变量名/标号(可选)> <助记符> <操作数> ,<操作数(可选)>, …”可以用于定义数据,其中可选的助记符有DB、DW、DD、DF、DQ、DT。

DATA_BYTE   DB  10, 4, 10H, ?     ;其中“?”表示随机数

DATA_WORD  DW  100, 100H, -5, ?

PAR1    DW  100, 200

PAR2    DW  300, 400

ADDR_TABLE  DW  PAR1, PAR2

VAR    DB  100  DUP (?)

DB  2  DUP (0,2 DUP(1,2),3)

(2)伪操作“<变量名> LABEL <type>”可以使同一变量(同一内存空间)以不同的数据类型解析,如下例所示。

BYTE_ARRAY  LABEL  BYTE          ;指定后面的数据可使用字节类型解析

WORD_ARRAY  DW   50  DUP (?)   ;以字类型定义数据

        ;使用BYTE_ARRAY标号访问变量时,以字节类型解析数据

        ;使用WORD_ARRAY 标号访问变量时,以字类型解析数据

6、表达式赋值伪操作

(1)伪操作“<表达式1> EQU <表达式2>”可以实现将<表达式2>的结果赋值给<表达式1>的功能,类似于C语言中的赋值语句,不同的是,伪操作在编译阶段就有确切的结果了,它将被编译器优化,并非汇编语句。

(2)一个表达式允许多次重复赋值。

7、地址计数器与对准伪操作

(1)伪操作“ORG <表达式>”:设置当前地址计数器的值(地址计数器$——保存当前正在汇编的指令的地址)。

(2)伪操作“ALIGN <表达式(2的整数次幂)>”:保证数组边界地址能被操作数整除。

(3)伪操作“EVEN”:使下一个变量或指令开始于偶数字节地址。

SEG1 SEGMENT

        ORG 10    ;设置当前地址计数器的值为10

        VAR1 DW 1234H

        ORG 20    ;设置当前地址计数器的值为20

        VAR2 DW 5678H

        ORG $+8    ;设置当前地址计数器的值为原值+8

        VAR3 DW 1357H

        ALIGN 4    ;保证数组边界从4的整数倍地址开始

        ARRAY db 100 DUP(?)

        A DB ‘morning’

        EVEN    ;使下一个变量开始于偶数字节地址

        B DW 2 DUP (?)

SEG1 ENDS

8、基数控制伪操作

(1)一般来说,无特别声明时(操作数没有H之类的后缀),操作数默认为十进制数。

(2)使用伪指令“. RADIX <表达式>”,可以将默认进制修改为<表达式>。

五、汇编操作符汇总(并非全部详细介绍)

1、操作符的作用

(1)操作符用于操作数中,通过操作符,将常数、寄存器、标号、变量等,组合成表达式,实现求值的目的。

(2)操作符仅在汇编期间起作用,编译结束后操作符将会被优化,也可认为它属于伪指令,但需要注意的是,使用了操作符的表达式,需保证其意义是明确的。

2、算术操作符

(1)算术操作符有:+、-、*、/、Mod。

(2)举例:

BLOCK  DB  25*80*2 DUP(?)

ARRAY  DW  1, 2, 3, 4, 5, 6, 7

ARYEND DW ?

MOV CX, (ARYEND - ARRAY) / 2

ADD AX, BLOCK+2  ;符号地址加减一个常数,有明确意义,但地址不允许乘除

MOV AX, BX+1   ;使用错误,这种情况必须使用加减法指令

MOV AX, [BX+1]   ;寄存器间接寻址,有明确意义

3、逻辑和移位操作符

(1)逻辑和移位操作符有AND、OR、XOR、NOT、SHL、SHR,它们和一些汇编指令雷同,实现的功能也是一样的,不同的是,它只负责在汇编期间计算表达式结果,汇编结束后表达式将直接被优化为一个确切的操作数。

(2)举例:

OPR1 EQU 25      ;00011001B

OPR2 EQU 7       ;00000111B

AND AX, OPR1 AND OPR2   ;优化为AND AX, 1

MOV AX, 0FFFFH SHL 2    ;优化为MOV AX, 0FFFCH

PORT_VAL = 61H     ;01100001B

IN AL, PORT_VAL     ;优化为IN AL, 61H

OUT PORT_VAL AND 0FEH, AL  ;优化为OUT 60H, AL

4、关系操作符

(1)关系操作符有EQ、NE、LT、LE、GT、GE,它和C语言中的关系操作符一样,用于关系表达式中,关系表达式的结果只有真、假两种结果,当为真时,表示结果的二进制数全部位为1,当为假时,表示结果的二进制数全部位为0。

(2)举例:

MOV FID, (OFFSET Y - OFFSET X) LE 128  ;语句T

X: ……

    ……

Y: ……

;若≤128 (真)   语句T汇编结果: MOV FID, -1 ;MOV FID, 0FFFFH

;若>128 (假)   语句T汇编结果: MOV FID, 0  ;MOV FID, 0H

5、数值回送操作符

(1)数值回送操作符有OFFSET、SEG、TYPE、LENGTH、SIZE。

(2)OFFSET的操作数为一个变量名或标号,其作用是回送变量或标号的偏移地址。

(3)SEG的操作数为一个变量名或标号,其作用是回送变量或标号的段地址。

(4)TYPE的操作数为一个变量名或标号或常数,其作用如下:

①若操作数为变量名,则结果取决于定义它的助记符,规则如下图所示。

②若操作数为标号,则结果取决于它是近转移还是远转移,规则如下所示。

③若操作数为常数,则返回0。

(5)LENGTH的操作数为一个变量名,其作用是回送由DUP定义的变量的单元数,若变量不是由DUP定义,则回送1。

(6)SIZE的操作数为一个变量名,其作用是回送由DUP定义的变量的占用总字节数,若变量不是由DUP定义,则回送通过变量名直接访问到的内存所占用字节数。

(7)举例:

ARRAY DW 100 DUP (?)

TABLE DB 'ABCD'

ADD SI, TYPE ARRAY   ;优化为ADD SI, 2

ADD SI, TYPE TABLE    ;优化为ADD SI, 1

MOV CX, LENGTH ARRAY  ;优化为MOV CX, 100

MOV CX, LENGTH TABLE  ;优化为MOV CX, 1

MOV CX, SIZE ARRAY   ;优化为MOV CX, 200

MOV CX, SIZE TABLE   ;优化为MOV CX, 1

6、属性操作符

(1)属性操作符有PTR、段操作符、SHORT、THIS、HIGH、LOW、HIGHWORD、LOWWORD。

(2)举例:

MOV WORD PTR [BX], 5   ;指定以字的形式访问[BX]

MOV ES: [BX], AL    ;指操作数1的定段地址为ES

JMP SHORT NEXT    ;短转移

TA EQU THIS BYTE    ;解释下面的内容时可以以BYTE类型数据解释

TD DW 1234H

NEXT EQU THIS FAR    ;可使用远转移根据标号NEXT跳转到此处

MOV AX, 2

CONS EQU 1234H

MOV AH, HIGH CONS    ;将CONS的高8位存入AH

MOV AL, LOW CONS    ;将CONS的低8位存入AL

六、汇编过程

1、可执行文件的生成过程

(1)可执行文件的生成过程回顾:

(2)在生成可执行文件的过程中,会有几个过程文件产生,如下所示:

①列表文件LST:将源程序(汇编语言)、目标程序(机器语言)、错误信息列表示出,以供检查程序用。

②交叉引用文件CRF:包含标识符(段名、过程名、变量名、标号)在源程序中定义的位置和被引用的位置,对源程序所用的各种符号进行前后对照的文件。

2、两遍汇编过程

(1)源文件要生成可执行文件,需要经过两次汇编:

①第一次汇编:确定地址,翻译成各条机器码,字符标号原样写出。

②第二次汇编:将字符标号用计算出的地址值或偏移量代换。

(2)举例:

七、宏汇编

1、汇编中的宏

(1)宏是指源程序中一段有独立功能的程序代码。

(2)宏指令是用户自定义的指令,在编程时,可以将多次使用的功能用一条宏指令来代替。

①宏定义:

<macro_name> MACRO [哑元表] ;哑元表中列出虚参,或者说形参

        ……

        ……  ;宏定义体

ENDM

②宏调用(必须先定义后调用):

<macro_name> [实元表]    ;实元表中列出实参

③宏展开:将宏定义体复制到宏指令位置,并用实参代替宏定义中的虚参。

(3)举例:

(4)对比子程序,宏定义的参数传送简单,执行效率高,但因为它没有封装的概念,汇编过程中每一个宏调用都直接拷贝一整段宏定义的代码,容易导致代码占用空间大,所以对于功能复杂的代码,建议还是使用子程序进行封装。

2、宏中的局部标号

(1)当宏定义中涉及标号时,容易引起错误,因为一个宏可能在多个地方调用,但整个程序不能有同名的多个标号存在,为了解决这种矛盾,需要引入局部标号。

(2)在宏定义中用“LOCAL”声明宏定义体中的标号,即可将其声明为局部标号,这样,不同地方调用宏时,宏展开后编译器会自动区分这些宏中的标号,不会让他们产生冲突。

(3)举例:

3、变元

(1)变元是操作码中一部分,使用符号“&”进行声明,这样,当宏展开时,实参将会以文本的形式替换宏定义体中的变元虚参。

(2)举例:

4、宏库

(1)可以将用到的宏定义分类放到不同的.mac宏库文件中,同时在程序中用“include”包含宏库文件并调用宏,这样可以使程序文件的阅读性更强。

(2)举例:

①宏库文件asmio.mac:

input macro
    mov ah, 01h
    int 21h
endm
output macro addr
    mov dx, offset addr
    mov ah, 09h
    int 21h
endm
mult macro mult1, mult2, mult3
    local lop, exit1
    mov dx, mult1
    mov cx, mult2
    xor ax, ax
    jcxz exit1
    lop:
        add ax, dx
    loop lop
    exit1:
        mov mult3, ax
endm

②源程序文件ptest.asm:

include asmio.mac
assume cs:codes,ds:datas
datas segment
            string db 'hello world' ,13, 10, '$'
datas ends
codes segment
    start:
            mov ax, datas
            mov ds, ax
            output string
            mov ah, 4ch
            int 21h
codes ends
end start

八、条件汇编与重复汇编

1、条件汇编

(1)在汇编过程中,可以根据某些条件把一段源程序包括在汇编语言程序内或者排除在外,大致格式如下:

IF××

        ……   ;满足条件则汇编此块

ELSE   ;可选分支

        ……   ;不满足条件则汇编此块

ENDIF

(2)条件汇编“IF××”大全:

(3)举例:

①IF条件汇编示例:求1~3个数中的最大值并放入AX中。

MAX MACRO K,A,B,C   ;K为参与比较的参数个数

        LOCAL  NEXT,OUT

        MOV AX, A

        IF K-1

                IF K-2

                        CMP C, AX

                        JLE NEXT

                        MOV AX, C

                ENDIF

NEXT:   

        CMP B, AX

        JLE OUT

        MOV AX, B

        ENDIF

OUT:

ENDM

②IFDEF条件汇编示例:参数一共有3个,如果实参传递不符合参数个数要求,将不汇编相关语句,防止出错。

divide macro dividend,divisor,quotient

        local comp, out

        cnt=0

        ifndef dividend

        cnt=1

        endif

        ifndef divisor

        cnt=1

        endif

        ifndef quotient

        cnt=1

        endif

        if cnt

        exitm

        endif

        mov ax, dividend

        mov bx, divisor

        sub cx, cx

comp:

        cmp ax, bx

        jb out

        sub ax, bx

        inc cx

        jmp comp

out:

        mov quotient, cx

endm

2、重复汇编

(1)重复汇编用于连续产生完全相同或基本相同的一组代码,其伪操作格式如下:

①定重复伪操作REPT:

REPT <表达式>   ;表达式结果为重复次数

        …… ;重复块

ENDM

②不定重复伪操作IRP:

IRP <哑元>,<自变量表> ;每次重复从自变量表中取一个值替代哑元,直到取完为止

        ……  ;重复块

ENDM

③不定重复伪操作IRPC:

IRPC <哑元>,<字符串> ;每次重复从字符串中取一个字符替代哑元,直到取完为止

        …… ;重复块

ENDM

(2)举例:

①把字符'A'到'Z'的ASCII码填入数组TABLE。

CHAR = 'A'

TABLE LABEL BYTE

        REPT 26

                DB CHAR

                CHAR = CHAR+1

        ENDM

②生成一组入栈指令,依次将AX、BX、CX、DX中的内容入栈。

        IRP REG, <AX, BX, CX, DX>

                PUSH REG

        ENDM

③生成一组定义若干“No.x”字符串的汇编语句。

array label byte

        IRPC K, 12345

                db 'NO.&K'

        ENDM

九、反汇编(逆向工程)

1、反汇编概述

(1)把目标代码(二进制机器语言)转为汇编代码的过程就是反汇编,即把机器语言代码转换为汇编语言代码,由低级转高级。

(2)反汇编常用于软件破解(例如找到它是如何注册的,从而解出它的注册码或者编写注册机)、外挂技术、病毒分析、逆向工程、软件汉化等领域。

(3)学习和理解反汇编,对软件调试、漏洞分析、OS的内核原理及理解高级语言代码都有相当大的帮助,在此过程中我们可以领悟到软件作者的编程思想。

2、使用Debug对可执行程序进行反汇编

        用debug运行可执行程序即可进行反汇编。

十、混合编程

1、混合编程概述

(1)混合编程是指使用两种或两种以上的程序设计语言来开发应用程序的过程。

(2)程序设计语言有多种,它们有各自的优势和不足,混合编程可以充分利用各种程序设计语言的优势。

2、混合编程举例

3、混合编程的其它方案

(1)不管是用高级语言还是汇编语言编写程序,它们经过编译后都能生成.obj文件,甚至可以打包为.lib文件,而其它程序,不管使用高级语言还是汇编语言,都能对.obj文件和.lib文件进行调用,实现混合编程。

(2)混合编程并不随意,在各种语言进行交互时,需参考编程接口规范。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zevalin爱灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值