哈工大ICS2024大作业

摘  要

C语言源代码通过编译器得到可执行文件(P2P),再通过OS执行(020)。其中,编译器通过预处理检查语法并连接源代码,编译预处理文件得到汇编代码,再汇编得到目标文件,最后通过链接得到可执行文件。对于可执行文件,在shell环境下,进程管理系统为可执行文件创建进程,执行程序并终止删除进程。其中,涉及到动态链接器、进程调度、时间片模式、异常信号处理机制、分页内存管理、I/O设备管理等多个方面。

关键词:计算机系统、bash、shell、预处理、编译、汇编、链接、P2P、020

目  录

第1章 概述................................................... - 4 -

1.1 Hello简介............................................ - 4 -

1.2 环境与工具........................................... - 4 -

1.3 中间结果............................................... - 4 -

1.4 本章小结............................................... - 4 -

第2章 预处理............................................... - 5 -

2.1 预处理的概念与作用........................... - 5 -

2.2在Ubuntu下预处理的命令................ - 5 -

2.3 Hello的预处理结果解析.................... - 5 -

2.4 本章小结............................................... - 5 -

第3章 编译................................................... - 6 -

3.1 编译的概念与作用............................... - 6 -

3.2 在Ubuntu下编译的命令.................... - 6 -

3.3 Hello的编译结果解析........................ - 6 -

3.4 本章小结............................................... - 6 -

第4章 汇编................................................... - 7 -

4.1 汇编的概念与作用............................... - 7 -

4.2 在Ubuntu下汇编的命令.................... - 7 -

4.3 可重定位目标elf格式........................ - 7 -

4.4 Hello.o的结果解析............................. - 7 -

4.5 本章小结............................................... - 7 -

第5章 链接................................................... - 8 -

5.1 链接的概念与作用............................... - 8 -

5.2 在Ubuntu下链接的命令.................... - 8 -

5.3 可执行目标文件hello的格式........... - 8 -

5.4 hello的虚拟地址空间......................... - 8 -

5.5 链接的重定位过程分析....................... - 8 -

5.6 hello的执行流程................................. - 8 -

5.7 Hello的动态链接分析........................ - 8 -

5.8 本章小结............................................... - 9 -

第6章 hello进程管理.......................... - 10 -

6.1 进程的概念与作用............................. - 10 -

6.2 简述壳Shell-bash的作用与处理流程.. - 10 -

6.3 Hello的fork进程创建过程............ - 10 -

6.4 Hello的execve过程........................ - 10 -

6.5 Hello的进程执行.............................. - 10 -

6.6 hello的异常与信号处理................... - 10 -

6.7本章小结.............................................. - 10 -

第7章 hello的存储管理...................... - 11 -

7.1 hello的存储器地址空间................... - 11 -

7.2 Intel逻辑地址到线性地址的变换-段式管理............................................................ - 11 -

7.3 Hello的线性地址到物理地址的变换-页式管理........................................................ - 11 -

7.4 TLB与四级页表支持下的VA到PA的变换................................................................ - 11 -

7.5 三级Cache支持下的物理内存访问 - 11 -

7.6 hello进程fork时的内存映射......... - 11 -

7.7 hello进程execve时的内存映射..... - 11 -

7.8 缺页故障与缺页中断处理................. - 11 -

7.9动态存储分配管理.............................. - 11 -

7.10本章小结............................................ - 12 -

第8章 hello的IO管理....................... - 13 -

8.1 Linux的IO设备管理方法................. - 13 -

8.2 简述Unix IO接口及其函数.............. - 13 -

8.3 printf的实现分析.............................. - 13 -

8.4 getchar的实现分析.......................... - 13 -

8.5本章小结.............................................. - 13 -

结论............................................................... - 14 -

附件............................................................... - 15 -

参考文献....................................................... - 16 -

第1章 概述

1.1 Hello简介

P2P:预处理、编译、汇编、链接

020: 创建进程、执行、内存映射、分配时间片、结束进程

1.2 环境与工具

1.2.1 硬件环境

X64CPU; 2.30GHz; 64G RAM; 1TGHD Disk

1.2.2 软件环境

Windows11 64; Vmvare pro 64; Ubuntu 17 64

1.2.3 开发工具

Code blocks; Geddit; Gcc; visual studio

1.3 中间结果

列出你为编写本论文,生成的中间结果文件的名字,文件的作用等。

1.4 本章小结

       给出了hello.c程序编译执行的全过程,介绍了开发环境和工具

第2章 预处理

2.1 预处理的概念与作用

预处理指将源代码文件转化为预处理文件,编译器会检查代码语法,并执行预处理命令#include #tepedef等,将库函数和main函数放入同一个文件中

2.2在Ubuntu下预处理的命令

命令为:gcc -E hello.c > hello.i

在ubuntu终端中输入

图2.2 预处理命令

2.3 Hello的预处理结果解析

得到预处理文件

图2.3.1 预处理文件

预处理文件第一部分给出了引用的库和参数

图2.3.2 预处理文件

第二部分给出了与系统相关的宏定义

图2.3.3 预处理文件

接下来是库中函数、结构体和枚举类型的定义

图2.3.4 预处理文件

最后是源代码。即,预处理命令将引用的库和源代码放入同一个文件里,便于生成汇编文件。

2.4 本章小结

       介绍了由源代码生成预处理文件的方法,预处理步骤将会检查语法并把源文件和引入的库文件连接在一起。

第3章 编译

3.1 编译的概念与作用

编译指编译器将预处理文件转换为汇编文件的过程。汇编器将对代码进行优化并按对应规则将c语句转化为汇编指令块。

3.2 在Ubuntu下编译的命令

命令为:gcc -S hello.i > hello.s

在UBUNTU终端输入:

图3.2 编译命令

3.3 Hello的编译结果解析

3.3.1 string常量

常量放置在内存的.section.rodata(只读数据段)中,在本例中,常量是.string即字符串,如下所示:

图3.3.1 编译文件

只读段中包括两个常量:.LC0的“用法:hello 姓名 学号 手机号 秒数!”,和.LC1的“Hello %s %s %s\n”,并按8字节对齐内存(.align 8)

3.3.2 main函数结构

main函数被定义为一个全局变量,且为@function类型,放置在多个段中并用跳转指令连接各段,对应的起始段如下所示

图3.3.2 编译文件

其中.cfi是控制栈回滚的伪指令,.LFB6对应源代码的部分如下所示

图3.3.3 编译文件

其中argc表示输入参数个数,存放在-20(%rbp)的内存地址中,若其等于5则跳转.L2,否则继续执行,将.LC0段字符串首地址作为参数调用puts@PLT(即printf“用法:hello 姓名 学号 手机号 秒数!”),再将1作为参数调用用exit@PLT(即exit(1));

其余汇编程序如下:

图3.3.4 编译文件

其对应源代码

图3.3.5 编译文件

的部分,具体逻辑为,再.L2中,由-4(%rbp)存储int i,并初始化为0,跳转到.L3,比较i<=9,则跳转到.L4(循环体),否则继续执行,调用getchar@PLT等待输入字符,将0作为返回值退出(对应return 0);

3.3.3 函数调用

函数调用包括储存当前函数数据、参数传递、记录当前地址、程序跳转、开辟函数栈帧等操作。记录栈帧首地址和尾地址的寄存器分别为rsp和rbp寄存器。在函数体中的变量存储在栈帧中,例如,打印字符串和退出程序的函数调用汇编程序如下:

图3.3.6 编译文件

即首先使用寄存器(rdi记录字符串指针,edi记录整型1)传递参数,再用call指令记录地址并跳转;

建立栈帧的操作,如在mian函数中,汇编程序前几行为

图3.3.7 编译文件

即通过改变栈指针寄存器的值来划出可用区域;

3.3.4 函数返回

函数返回包括参数传递,恢复参数,程序跳转等操作,例如main函数的return过程,对应的汇编语句如下

图3.3.8 编译文件

mov指令对应设置返回参数的过程,这里用eax寄存器存储返回参数;

leave指令对应恢复栈帧寄存器参数即(%rsp)和(%rbp)的过程,即将帧顶上移并pop得到原来的帧底地址,相当于栈帧释放;

ret指令对应return跳转的过程,即跳转到call存储的程序下地址处;

3.3.5 整型变量的存储、赋值、计算

变量储存在函数栈帧中,如int I;对应的存储地址为main函数栈帧-4(%rbp),其中-4代表int i整型占4字节的存储空间;其赋值操作在.L2中完成

图3.3.9 编译文件

在.L4中,实现了i的自加1操作:

图3.3.10 编译文件

在.L3中,实现了i的比较操作

图3.3.11 编译文件

由该例子可知,变量的声明作用于变量生效的整个函数中,其告知编译器增加变量大小的栈帧空间,并记录该变量的存储地址(相对栈帧),确定对应该变量使用的汇编指令的具体形式,便于后续使用;

变量的赋值操作由mov指令完成,可能为立即数或其他变量;

变量的各类计算,如自加(add $1, x)和比较(cmp x,y)都是由对应的汇编语句完成;

3.3.6 if条件分支

if对应的汇编操作是通过cmp比较指令和条件跳转(如je/jle等)完成的;首先,通过cmp和多种运算指令确定if()的括号中的表达式的值(设置flag寄存器的标志位),在通过条件跳转指令,根据标志位将程序跳转到指定位置(即某些汇编语句块)。在本例中,if条件分支由下列汇编语句实现

图3.3.12 编译文件

对应源代码中的

图3.3.13 编译文件

3.3.7 for循环

For循环包括循环值的定义、初始化、判断跳转、循环体执行、更新循环值、跳转判断等操作。在本例中,for循环由下列汇编语句实现

图3.3.14 编译文件

对应源代码中的

图3.3.15 编译文件

其中.L2块实现了i循环变量的初始化,.L3块实现了循环变量的判断跳转,.L4实现了循环体的执行和循环变量i的更新(自加1);

3.4 本章小结

       本章详细分析了hello.s汇编文件的功能,各汇编代码的作用,汇编器将c源代码编译为汇编代码的过程,与c源代码各个数据类型和操作对应的汇编语句。

第4章 汇编

4.1 汇编的概念与作用

       汇编指汇编器将.s编译文件转换为.o二进制机器语言文件的过程,作用是生成可链接的目标文件,便于生成可执行文件。目标文件具有良好的封装特性,适用于闭源程序的发布。

4.2 在Ubuntu下汇编的命令

汇编命令:gcc hello.c > hello.out

在UBUNTU终端输入:

图4.2 汇编命令

4.3 可重定位目标elf格式

利用指令:readelf -a hello.o > oelf.txt 获取包含全部elf信息的txt文件。

ELF 头:

图4.3.1 ELF文件

ELF头中记录了文件格式(Magic魔法模式)、数据格式(补码,小端序)、系统架构(Advanced Mico Devices X86-64 即AMD的X86-64架构)和各头节点的大小信息等项目。

节头:

图4.3.2 ELF文件

包含了内存各节的地址偏移量和类型信息、旗标信息等。

重定位节:

图4.3.3 ELF文件

即重定位项目的相关信息,在本例中,主要包括7个重定位项目:

1个main函数,具有157字节的有效内容

6个被调函数,包括puts、exit、printf、atoi、sleep、getchar

重定位项目的入口在rela.text和rela.frame中定义

4.4 Hello.o的结果解析

利用指令:objdump -d -r hello.o > oasm.txt 将hello.o目标文件反汇编,得到oasm.txt汇编文本如下:

图4.4.1 目标反汇编文件

对比hello.s,hello.o的反汇编文件新增了各个被调函数即PLT@func_name的具体汇编代码,去除了各类伪指令(包括块.L1、.L2,栈处理指令.cfi等),并用具体的地址偏移量而非字符串表示被调函数和代码块。

观察机器语言的十六进制表示,它是顺序存储的,从<main>对应的0地址偏移开始,每行机器指令长短不同,格式为(OP码 操作数,操作数)或(OP码 操作数)或(OP码)等等。

目标文件的机器语言和汇编语言间存在映射关系,即将汇编代码(助记符)映射为二进制串,将代码块名和函数名映射为偏移地址,将伪指令映射为具体语句。

例如,函数调用call PLT@exit被映射为:

exit具体语句为

图4.4.2 目标反汇编文件

if 条件分支和for循环中,条件分支语句的跳转操作数由语句块名映射为对应语句块首地址

例如,跳转到语句块.L2:

映射为

4.5 本章小结

本章给出了由编译文件hello.s生成目标文件hello.o的流程,介绍了汇编语言到二进制语言的映射过程。

5章 链接

5.1 链接的概念与作用

链接指根据hello.o目标文件以及多个目标文件生成hello可执行文件的过程。

链接可以将hello.o中的地址偏移转换为确切地址,并生成_start程序段,使可执行文件有确定入口。可链接多个目标文件,使源代码可分别进行编译。

5.2 在Ubuntu下链接的命令

链接指令:ld -o hello -dynamic-linker /lib64/ld-linux-x86-64.so.2 /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o hello.o /usr/lib/x86_64-linux-gnu/libc.so /usr/lib/x86_64-linux-gnu/crtn.o

在UBUNTU终端中输入:

图5.2 链接命令

5.3 可执行目标文件hello的格式

利用指令:readelf -a hello >  asm.txt 得到包含全部ELF信息的txt文件elf.txt;

ELF头:

图5.3.1 ELF文件

发现文件变为可执行类型,入口点地址为0x4010f0;

节头:

图5.3.2 ELF文件

该部分给出了各节偏移量和具体大小

重定位头:

图5.3.3 ELF文件

得到了各函数的偏移量(入口地址)

5.4 hello的虚拟地址空间

  

图5.4 EDB界面

Edb的 Data Dump窗口显示了程序头表,从地址0x401000到0x402000

5.5 链接的重定位过程分析

使用指令:objdump -d -r hello > asm.txt 得到反汇编代码,main部分如下所示

图5.5.1反汇编文件

与oasm.txt不同的是,跳转地址不再用偏移量表示,而是用地址“0x40…”表示;除了main函数和被调函数的汇编代码外,还包括start等段的代码:

图5.5.2反汇编文件

结合5.3中的节表,可知<mian>的首地址为0x401125,则链接后的函数首地址域表一致。链接过程使得每个函数有唯一地址。

5.6 hello的执行流程

由指令sudo gdb hello.c 进入GDB环境,用 b <函数名>在各个函数前设置断点,用指令 run 名称 学号 手机号 秒数 得到运行结果:

图5.6.1 hello执行示意图

结合反汇编代码,可知函数首地址:

5.7 Hello的动态链接分析

利用指令sudo edb ./hello 名称 学号 号码 秒数

得到结果如下

图5.7 动态链接

动态链接器的原理是,通过偏移表实时生成函数的真实地址。因此,函数地址值相对运行前会发生变化。

5.8 本章小结

介绍了链接过程和动态链接器的原理。

6章 hello进程管理

6.1 进程的概念与作用

进程是程序在操作系统下占用系统资源的实例;

       其作用是为每一个程序分配对应的运行资源,使得多个程序可以近似为并发运行,同时便于操作系统管理每一个运行的程序。

6.2 简述壳Shell-bash的作用与处理流程

作用:Shell-bash可用于读入用户指令并执行一系列任务,便于人机交互

处理流程:

1.提取分隔字符(space tab等)并分割指令;

2.检查程序块tokens,查找对应指令;

3.   将一些内嵌指令展开为可执行指令

4.计算某些算术表达式

5.替换通配符

6.   检查命令,一次查找可执行指令、可执行文件、脚本文件

7.进行输入输出重定向

8.   执行命令

6.3 Hello的fork进程创建过程

通过调用fork函数创建子进程,子进程可调用父进程资源,最后父进程得到返回值,即为子进程PID

6.4 Hello的execve过程

通过调用exceve在当前父进程中加载一个子进程。仅当子进程出错时,函数才会返回。即,当执行execve函数时,就将待执行目标文件拷贝到内存中,并跳转到目标的第一条指令,开始执行目标程序。

6.5 Hello的进程执行

进程调度:当系统中存在多个正在运行的进程中,需要将有限的计算资源按某种顺序循环分配给各个进程,使得多进程具有并发特性,即不同进程在不同时间片占有同一个处理机。该调度过程由内核决策,方法是按照某种规则终止正在运行的进程,保存进程运行时堆栈和寄存器值(即运行上下文),并跳转到某一个先前被终止的进程,加载该进程终止时刻的堆栈和寄存器值,将CPU控制权转交该进程。在进程执行中,一些信号或内核本身可决定用户态和内核态的转换。

6.6 hello的异常与信号处理

1、利用指令 ./hello 郑岐 2022111180 18522713088 3,程序正常运行

按ctrl+z

Ctrl+z会向系统发送中断信号,使用ps发现hello进程未终止。

若再使用fg指令,则进程继续运行,如下如所示

       重新运行程序,按ctrl+z,使用ps巨鹿进程PID,再kill PID,使用fg运行,如下所示:

       则kill指令可以结束进程;

2、重新运行程序,按ctrl+c

Ctrl+C表示发送终止信号,使用ps发现进程已被终止。

3、重新运行程序,不停乱按,得到结果如下所示

则输入字符被bash显示到屏幕上。

4、以上结果说明,

运行过程中可能出现的异常种类包括中断、陷阱、故障、终止。其中中断来自I/O设备且异步发生;陷阱由特定命令触发,结束后程序继续运行;故障是由程序错误引起的,可能被修正,也可能导致程序终止(如段错误导致程序直接终止);终止通常由I/O或硬件引起,不可被恢复。

6.7本章小结

本章介绍了bash环境下hello进程的创建、执行、终止流程。

7章 hello的存储管理

7.1 hello的存储器地址空间

逻辑地址:是hello反汇编代码中的偏移地址,与段有关,需要经过基址寻址寻址(即段基址+偏移地址)才得到线性地址;

线性地址:是逻辑地址到物理地址变换之间的中间层,是按基址寻址,利用段和偏移量得到。线性地址是进程所用的寻址模式,即每个进程的线性地址都从0x0开始。对于linux系统,线性地址等同于逻辑地址。

虚拟地址:即线性地址,由进程使用,与真实内存容量或内存寻址无直接关联,需要经过转换才得到物理地址。

物理地址:只寻址总线上的地址信号,直接影响内存的寻址结果,需要在线性地址的基础上进行变换映射才能得到真实的物理地址。Hello在运行中由CPU按照逻辑地址产生线性地址,再经过MMU(系统管理单元)得到物理地址。

7.2 Intel逻辑地址到线性地址的变换-段式管理

Intel逻辑地址包括段选择符和偏移量,线性地址是选择符对应的段首地址与偏移量组成。其中,段描述符记录段首地址,描述符表存放段描述符,即全局描述符表或局部描述符表,有如下特点:

1.段式管理以段为单位分配内存,每段分配一个大小不一的连续的内存区。

2.同一进程的各段之间不一定连续。

3.段的分配与释放可动态进行。

7.3 Hello的线性地址到物理地址的变换-页式管理

页式管理是将各进程的虚拟空间划分成若干个等长的页,再按页的大小划分成页面,然后建立页表将页虚拟地址映射为内存地址。页技术可实现内外村的统一管理。

页式管理的线性地址由虚拟页号和虚拟页偏移组成。首先,内存管理单元按虚拟页号检查页表,查找该页是否命中。若命中,则将缓存部分返回给内存管理单元;否则产生缺页,由内核调入所需页面。将线性地址中的两部分虚拟地址映射并连接就得到了真实物理地址。

7.4 TLB与四级页表支持下的VA到PA的变换

当CPU产生线性地址时,内存管理单元查阅页表条目,并将其映射为物理地址。若页表条目在LI缓存中,则访存开销只有一到两个周期。

当页表条目未命中时,虚拟页号被划分为四个片,每片用作一个页表偏移量,依次表示L1\L2\L3\L4对应的基地址。从L4页表中取出页表条目,将其与虚拟页偏移连接,即可得到物理地址。

7.5 三级Cache支持下的物理内存访问

内存管理单元将物理地址发给L1缓存,从物理地址中取出缓存偏移CO、缓存组索引CI以及缓存标记CT。存在匹配条目则读出其偏移量,并返回给内存单元,随后将其传递给CPU。若未命中,则访问下一级cache或主存读入数据,并再次尝试处理。

7.6 hello进程fork时的内存映射

调用fork函数会为程序创建虚拟内存,系统读取进程的内存结构和页表,并创建副本,保存程序的私有地址空间。则每当fork返回时,都会依原样创建内存结构副本,使得每个进程都有独立的内存区域。

7.7 hello进程execve时的内存映射

execve函数跳入hello入口并将控制权交由hello,包括:

  1. 移除用户区域

2、映射私有区域(创建堆栈和数据段等)

3、映射共享区域(针对共享对象)

4、设置PC指向起始位置。

7.8 缺页故障与缺页中断处理

页故障需要硬件和操作系统内核的协同处理:

首先,CPU生成虚拟地址,交由内存管理单元调用对应页内容。若出现页未命中,则进入操作系统内核中的缺页异常处理程序。

缺页处理程序检测物理内存中的被修改页,将其存入磁盘;之后调入新页面并更新页表;最后返回到原进程断点继续执行程序。此时页已存入缓存,因此重新加载指令时必定命中。

7.9动态存储分配管理

定义:对内存空间的分配和回收可在进程执行过程中动态进行,提高内存利用率。

类型:(1)显示分配器(对程序可见)(2)隐式分配器(自动收集)

实现方法:将虚拟空间设置为空闲链表,当程序请求k字节块时,分配器搜索空闲链表并取出对应字节的块交由程序管理。该链表具有前驱与后缀,便于释放空间时重新链接空闲链表。

7.10本章小结

介绍了LINUX系统的内存管理方法、从程序逻辑内存映射到物理内存的机制,以及分页内存管理的原理。

8章 hello的IO管理

8.1 Linux的IO设备管理方法

1、将所有I/O设备模型化为文件,输入输出对应文件的读写操作。

2、设备管理依靠UNIX I/O接口,即将设备映射为文件的通用管理方法。

8.2 简述Unix IO接口及其函数

1、打开文件open

通过Unix IO接口访问设备的操作映射为打开设备对应文件,内核将返回一个描述符,并记录关于该文件的所有信息,应用程序只需利用该描述符即可实现对设备的访问和管理。

2、读写文件read/write

读操作即按照文件指针,从文件复制一段数据到内存,然后增加读入字节数的指针偏移;写操作即将内存的一段数据复制到设备并更新指针。

3、关闭文件close

当程序完成对文件的访问后,通知LINUX内核释放文件并删除描述符。

8.3 printf的实现分析

printf的输入fmt,和不定长的参数,并调用了两个函数vsprintf和write;首先,vsprintf函数将待输出参数内容格式化存入buf, write函数将buf中的元素写到终端。引用系统内核调用字符显示驱动子程序,即按ASCII码访问字模库,再访问色彩存储文件,向显示器传输RGB分量。按特定频率扫描建立屏幕图像后,即可显示对应字符。

8.4 getchar的实现分析

调用getchar时,程序将等待用户按键,并把输入的字符存放在键盘缓冲区直到发现回车。之后,getchar从stdin流中每次读入一个字符(调用内核read函数),并传递给程序。

8.5本章小结

介绍了linux系统I/O设备管理方法和相关函数,给出了I/O设备与异常处理的关系。

结论

Hello.c在编译器管理下,逐步得到预处理文件、编译文件、目标文件、可执行文件,其中,目标文件使得单一可执行文件能够被拆分为各个子程序分别开发、编译,便于程序的共享;得到的可执行文件hello在shell环境和OS、内存管理、I/O管理系统的调度下,创建可执行文件进程、分配运行内存空间、执行程序、收发I/O信息显示运行结果、最后程序结束并回收内存。在程序运行期间,I/O设备可发送各种信号使程序进入异常处理阶段,内核实时进行进程调度使得系统能够同时运行多个进程。

对于现代计算机系统,可充分发挥云计算和物联网优势,将物理设备同计算机硬件云连接,实现一个面向人、机、物交流的宏操作系统,以适配工业4.0的发展需要。

参考文献

[1] https://blog.csdn.net/hahalidaxin/article/details/84442132

[2] 《深入理解计算机系统》Randal E. Bryant  David R.O`Hallaron

[3] linux edb使用手册,反汇编及linux下edb的下载-CSDN博客

[4] https://blog.csdn.net/misayaaaaa/article/details/77622202

  • 9
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值