反向分析LFS

 
<script src="http://blog.chinaunix.net/js/toolbar.js"></script>

青橄榄

入口时虽然是苦的,但之后能品味到那淡淡而又清爽的甜。
<script language=javascript> document.getElementById("blogbrief").innerHTML="入口时虽然是苦的,但之后能品味到那淡淡而又清爽的甜。"; </script>
youbest.cublog.cn 首页 | 文章 | 相册 | 收藏夹 | 留言


[原创]换个角度看LFS——反向分析LFS
[原创]换个角度看LFS——反向分析LFS

更新日志
2006-08-12:修改有歧义的句子一处。
2006-06-21:增加对结尾插图的说明。
2006-06-21:修改最后一副插图。
2006-06-21:修改笔误一处。
前言
  写了几篇关于LFS的制作过程中的文章,但分析性的文章还没怎么写过,论坛上也有一些分析性的文章,但大多数都是真对某个特定部分的,最近酝酿了一下,准备写点分析性质的文章调剂调剂。
  这次用的标题大概已经能说明本文分析的角度,按照LFS的顺序写,似乎总不能摆脱LFS的制作过程的牵制,总觉得像写制作教程,所以决定反过来写,利用一个大家熟悉的情景为开始反过来推出整个LFS的过程,本文不能算专业的分析文章,只是想简单的说明白LFS为什么要这样的过程。
  本文并不是要完全还原LFS,只是为了说明一种分析过程,因此文中部分内容和实际的LFS略有出入。
  限于水平的问题,我只能将我现在的理解来写,如果有什么错误或者不当的地方希望大家及时指出。
  本文的读者应该是一个已经经历过LFS至少一次的朋友,如果你从来没搞过LFS,建议亲自动手制作一遍后再看本文,应至少看过下面文章中的一篇:
  《Linux from scratch》英文版本
  《LFS-Book 6.1.1 中文正式版》
  《手把手教你如何建立自己的Linux系统(LFS速成手册)》

  更新,由于篇幅比较长所以难免出现一些错误或者笔误,也有可能加入新内容,因此难免会进行修正或增删一些内容,如果本文被转载您可以在本人的Blog或者www.linuxsir.org的LFS版中中查看最新版本。
  我的Blog:http://youbest.cublog.cn
  linuxsir:http://www.linuxsir.org/bbs/showthread.php?t=262010
  如须转载请注明作者为冲天飞豹(youbest),并提供转载出处。


工作情景:
  我正在用VIM编辑一篇文件


分析:
  问:那么要完成这个任务我需要些什么呢?
  答:硬件(略),本文将不对硬件做任何分析。
    软件:VIM

  问:那么运行VIM又需要什么条件呢?
  答:一个Linux内核
    一组支持VIM运行的动态库,按照比较标准的组合,应该是glibc和ncurses这两个库来承担VIM的运行时动态库

  问:那么内核需要什么条件呢?
  答:符合内核运行的硬件环境。

  问:glibc又需要什么条件呢?
  答:于glibc相适应的Linux内核

  问:ncurses需要什么条件呢?
  答:合适的glibc

最后我们来画一副图来描述这个关系


  那么这个关系图基本上就可以描述一个VIM运行的环境需求,当然在启动VIM的过程中少不了一个shell的参与,通常我们用BASH来完成,那么这个任务的整个大致环境和关系大致如下图所示。


  清楚了这个问题,下面需要解决的就是这个运行环境从哪里来的呢?
  通常Linux下的软件都提供了源代码,我们可以用这些源代码来组合成我们自己需要的二进制程序。
  在这个例子中,我们想要有一个VIM,那么我们要先下载一份VIM的完整源代码,然后利用一组编译工具来完成VIM的编译。
  对于一个通常编译的过程大致如下图

  这其中最复杂的就是make阶段,make会调用目录中的Makefile来执行一系列的工作,其中包括创建必要的文件,以及调用gcc和binutils来编译源代码和链接目标文件,最终生成需要的可执行文件和附属文件。
  所以make过程中一般需要用到的是gcc,binutils,make和一些系统程序(不同的软件需求不同)。


这样我们再来画最开始的运行环境的图


  好了,这样我们就清楚了这个整个运行时候的环境是从哪里来的了。用红色虚线框起来的就是整个构造运行时环境的必要条件了,其实就是我们通常在LFS中最常见到的一个词汇:工具链。
  那么下面的一个问题就是这些工具运行的条件是什么?
  这些编译环境中的应用程序也和其它程序一样必须有运行的环境:
  GCC依赖于glibc
  binutils依赖于glibc
  make依赖于glibc
  头文件是在编译时候gcc所需要的,但本身都是一些文本文件,因此没有需要的运行环境。
  常用工具依赖于glibc和各种需要用到的动态库。

  这里的一个新问题就是编译环境中使用的glibc和Linux内核和“运行时环境”中的glibc与Linux内核是否是同一个。
  答案很显然,绝对不是,因为“运行时环境”中的glibc和Linux内核是依靠工具链中编译工具来完成的,所以工具链所依赖的glibc和Linux内核于目标系统的不可能是同一个(但版本什么的是可以一样的,这里说的不同是指已经编译成二进制的实体不同)。
说明:
  为了说明方便,下面将“运行时环境”称为目标系统。
  但就LFS而言目标就是要做一个通用的可自主扩展(编译)的系统,所以在完成目标系统的glibc后又编译了一整套工具链中的东西,目的是将工具链中的工具全部移植到目标系统中,以便在完成目标系统后可以抛弃工具链而又能够自主的进行编译,而这些工具依赖的环境就是目标系统的glibc了。
  内核这东西比较特殊,虽然运行任何程序都需要用的内核,但本身在制作目标系统过程中,目标系统的Linux内核却不需要先进行编译,因为使用Linux内核并不像glibc那样是依靠动态链接库的方式被调用的,内核不是用动态库的方式被调用的,因此不需要先编译,只要提供对应的头文件即可,后面将不再探讨 Linux内核的问题。
  作为LFS的另一个目标就是要建立一个“纯净”的系统,因此编译glibc的编译器和最后目标系统里的编译器应该保持一致,同时目标系统是完全依靠工具链编译出来的,而工具链应该是在目标系统建立完毕后可以很方便的剥离掉的,而且为了保持工具链的稳定工具链中的工具所依赖的glibc以及其它用到的动态库应该是不会被替换掉的。
  要解决上面这个问题,那么最好的方法就是将工具链放置在一个独立的目录中,LFS将其放在了 /tools目录下,因此在用工具链建立目标系统的过程中将PATH设置为/bin:/usr/bin:/tools/bin来让bash调用命令时能首先调用目标系统中已经建立好的命令,如果没有则从工具链中调用。这样的话在目标系统还没有编译工具的情况下使用工具链来进行编译。
  好了,现在已经可以用工具链来完成目标系统的编译了,下面的问题就是这个工具链是如何来的呢?
  这个问题就要回到前面所提到的工具链运行时所依赖的环境,还是这个glibc,要编译这个glibc必须是在工具链的编译工具生成之前,而工具链的编译工具又依赖于glibc,那么这个glibc就不能是现在工具链中的编译工具编译出来的,那么是谁编译的呢?
  这个问题的答案:当然还是编译器编译的这个glibc和工具链里的编译工具,也就是说在工具链中的编译工具编译目标系统之前需要另一个编译器来编译这个在使用的工具链中的编译器和编译器所依赖的glibc。


  用蓝色的虚线框起来的部分就是生成工具链的工具链,我暂时称为“预工具链”。
  “预工具链”的存在则能完成工具链中的glibc,以及工具链中的编译工具,并且工具链中的工具将被编译成依赖于工具链中的glibc。
  那么现在要解决的问题就是这个“预工具链”是如何建立起来的。
  答案还是一样,需要一套编译工具来编译出这个“预工具链”。

  现在要提到的一个问题就是,用不同版本的gcc编译出来的程序可能不一样,而不同的gcc编译出来的目标文件要用能正确处理的binutils的版本来链接成可执行文件,因此我们现在已经能确定的gcc、binutils版本是工具链中的版本,那么用工具链编译出来的目标系统是符合我们的要求的。
  那么编译工具链中的glibc和gcc以及binutils的gcc和binutils的版本现在还没有确定,但根据LFS的“纯净”目标,也就是说 gcc和binutils也必须和工具链中的gcc和binutils相同才行。而其它的常用工具及make虽然参与,但不会对编译的二进制产生影响(前提是必须能正确处理它应该做的事情)。

  现在的问题就集中到“预工具链”中的gcc和binutils上了,只要能编译出和工具链中的gcc与binutils相同版本就可以了,也就是说只要一套编译环境能编译gcc和binutils就可以了。

  那么问题是这一套编译环境是怎么来的呢?
  如果还继续按照前面的套路,这个问题就成了无穷无尽的了,这个不是我们想做的,现在已经能“纯净”的建立一套符合条件的工具链就已经达到我们的目的了,因此编译“预工具链”的binutils和gcc的任务我们就采用一套能正确编译的发行版或者预制好的编译环境就可以了,我们可以称这个发行版或者预制好的编译环境为“主系统”。
  那么我们现在只要能用这个“主系统”编译出“预工具链”的binutils和gcc就可以了,“预工具链”中的其它部分就直接采用“主系统”里的就可以了。
  但这里有一个问题就是:“主系统”中的gcc和binutils与“预工具”中要求的gcc和binutils版本不同(通常会老些),但只要能正确编译binutils、gcc就行了,gcc具备自我编译的功能,因此建议编译不同版本的gcc采用bootstrap的方式比较好。当然“主系统”中的在参与编译过程中的其它工具也需要符合要求就成。
  最后用一副图来表达简单的表达整个过程,也算本文的结尾。
(转载请保持文章的完整性,请注明作者和出处)

                               作者:冲天飞豹(youbest)
                               Email:youbest@sina.com
                               2006年6月21日

希望大家能多写一些分析性的文章,共同提高。
 
说明:上图的内容实际上并不是标准的LFS编译关系,按照LFS的做法,应该在预工具链编译完成工具链中的glibc、gcc和 binutils,工具链后续的部分是由工具链的gcc和binutils来完成的,但本文并不追求和LFS完全一致,只是为了说明整个过程是如何反向推出来的,而且我认为按照图里的方法也完全没问题,完全不影响目标系统的“纯净”度。
图中实际上也表现了一种可以用来编译不同平台上的工具链的方法,因为在编译整个工具链的过程中都由预工具链来完成的(此方法我自己没实验过,只是一个设计)。


更新日志:

2006年6月21日:
修改笔误一处
由linuxsir上的doom3d发现并报告

2006年6月21日:
在结尾的图片上加入gcc的bootstrap的标记。
由linuxsir上的doom3d建议

2006年6月21日:
对结尾的图片做出说明。
2006年8月12日:
修改有歧义的句子一处。
创建于: 2006-06-21 14:06:26,修改于: 2006-08-12 12:42:16,已浏览4219次,有评论36条


网友评论
内容:
能写一些"安装包之间的相互倚赖关系"的文章就好了. 
abcdefgl123 评论于:2006-06-28 09:34:35 (222.56.116.★)
内容:
LFS和BLFS对每个包的依赖关系都写的挺详细的啊. 
冲天飞豹 评论于:2006-06-28 20:07:17 (58.212.103.★)
内容:
这个问题就要回到前面所提到的工具链运行时所依赖的环境,还是这个glibc,要编译这个glibc必须是在工具链的编译工具生成之前,而工具链的编译工具又依赖于glibc,那么这个glibc就不能是现在工具链中的编译工具编译出来的,那么是谁编译的呢?
  这个问题的答案:当然还是编译器编译的这个glibc和工具链里的编译工具,也就是说在工具链中的编译工具编译目标系统之前需要另一个编译器来编译这个在使用的工具链中的编译器和编译器所依赖的glibc。

这几句话,能不能换个说法啊, 好绕口,看得头晕晕~~~~麻烦楼主讲明白一点 :) 
yeah 评论于:2006-08-10 12:46:33 (219.135.214.★)
内容:
是饶口了一些,大概的意思就是工具链可以用来编译glibc,但工具链本身也需要一个glibc来维持运行. 
冲天飞豹 评论于:2006-08-11 10:51:01 (58.212.107.★)
内容:
为什么需要一个预工具链呢 直接用“主系统”的不行吗
还有LFS6.1.1的第五章和第六章的工具链是你这里提到的预工具链还是工具链呢 
shangkouai 评论于:2006-08-12 13:18:28 (218.193.184.★)
内容:
回shangkouai:
你说的没错,直接用"主系统"中的工具是可以的,但是主系统提供的gcc不一定符合glibc的要求,当然即使符合,我们也希望glibc能够被一个我们指定的gcc来编译.
LFS第五章就是在制作我这里提到的预工具链和工具链,第六章已经是在做目标系统了。 
冲天飞豹 评论于:2006-08-12 15:46:57 (222.95.25.★)
内容:
豹哥好,我最近迫于无奈必须写一篇论文,刚好最近搞了LFS很长时间,所以想参考本文来写一篇LFS原理方面的文章,想征求下你的意见。希望你不会反对啊。 
sky 评论于:2006-10-05 21:48:51 (59.69.74.★)
内容:
只要文章里有自己的东西就行。
记得在最后的参考资料里给个位置就好~:) 
冲天飞豹 评论于:2006-10-05 23:55:43 (222.95.24.★)
内容:
谢谢
 
sky 评论于:2006-10-06 14:52:43 (221.235.93.★)
内容:
另外,如果你有什么心得或者论点论据什么的也希望能写出来供大家参考,这样才能共同提高。:-) 
冲天飞豹 评论于:2006-10-06 16:32:37 (222.95.27.★)
内容:
其实也没有什么了,就是看了网上你文章的和另一篇(LFS中gcc需要编译几次的原理分析[ZT] ),也就是相当于自己的学习总结了,还得继续像豹哥学习啊。 
sky 评论于:2006-10-06 19:58:44 (59.69.74.★)
内容:
我一直以来有个想法,想要以Windows作为起始操作系统构建LFS。在LFS论坛上问了一下,还比较棘手。

一般来说,利用cygwin环境是可以在Windows里面制作一个工具链的。chroot似乎也还可以完成。然后,能否用这个工具链在Windows下编译一个linux内核呢?因为系统差异较大,因此制作过程应该参照CLFS进行,首先采用交叉编译方案,尽快地弄出一个交叉编译的内核,然后安装grub,重启到新内核里面继续工作。这个内核应该尽可能地精简,只需要支持必要的文件系统和磁盘驱动即可。此后在这个内核里面做完整的LFS就应该没问题了

所以,是否有可能在cygwin环境下编译出一个可以运行的内核,就成了问题的焦点所在。将来,如果能利用Visual studio平台编译出一个工具链,那就更强了。虽然Windows下没有chroot,但另一方面看,由于Windows的目录结构完全不同,不存在覆盖原有文件问题,因此应该就不需要chroot了,直接在一个盘符下工作就可以。 
地球发动机 评论于:2006-10-29 23:35:40 (121.33.10.★)
内容:
有想法就动手试试吧。 
冲天飞豹 评论于:2006-10-30 14:56:15 (222.95.25.★)
内容:
今天,我完成了在Cygwin下编译Binutils-2.16.1和gcc-4.0.3(静态编译),Glibc-2.3.6正在编译中,估计要早上才有结果了。采用的是老的CLFS 1.0编译方法。

Windows平台:Windows 2003 Server 在VMWare 5.5下运行。

我不知道如何在Cygwin下切换到非特权用户(Cygwin甚至没有root,只有Windows的Administrator),所以没有创建lfs用户。CLFS_HOST=x86-cross-cygwin,CLFS_TARGET=x86_64-unknown-linux-gnu。
gcc-4.0.3没找到官方的补丁,只好对照官方补丁手动修改了几处,不知道会否影响后面的编译过程。

此外,不知道楼主有没有利用64位宿主Linux编译LFS的经验,我今天失败了。原因是没有留意到64位平台下的spec以及动态链接库名称有所改变,现在是/lib64/ld-linux-x86-64.so.2 而不是32位平台的/lib/ld-linux.so.2 
地球发动机 评论于:2006-11-01 01:20:49 (121.33.9.★)
内容:
不用lfs用户也没关系,只是不要敲错命令导致系统崩溃就行.
我这里没有64位机,没办法试. 
冲天飞豹 评论于:2006-11-01 16:39:15 (222.95.26.★)
内容:
先顶了。读了一边,目前迷茫中 
corncc 评论于:2007-01-11 09:51:43 (218.206.193.★)
内容:
如果不考虑要用指定的(自己编译出的新版)工具(gcc,binutil)编译目标系统,有没有一种方法能够直接利用主系统上的工具一次编译整个目标系统阿?

比如:直接使用主系统上的编译工具依次编译目标系统的
内核
glibc
然后还是直接使用主系统上的编译工具编译
bintool,
bash
等,但是让它们使用目标系统的glibc

也就是说,通过设置编译参数,配置文件等方法一次编译出目标系统来? 
一种想法 评论于:2007-02-02 14:10:02 (221.6.14.★)
内容:
想到过,应该是可以,不过比较麻烦,而且可能会有不少新问题出现。 
冲天飞豹 评论于:2007-02-02 22:31:16 (222.95.26.★)
内容:
果然,看图就是比看文字要直观,因为以前学过c,所以现在基本了解LFS是怎么一回事了,打算补充一些linux的知识后再来试着玩玩LFS 
whi 评论于:2007-03-09 23:56:56 (58.48.26.★)
内容:
有帮助就好啊!:-) 
冲天飞豹 评论于:2007-03-11 15:01:17 (222.95.25.★)
内容:
我可以不可以这样理解,首先用宿主系统来创建工具链,然后再用工具链来创建目标系统 
cnhnyu 评论于:2007-08-02 15:57:17 (220.152.255.★)
内容:
回cnhnyu:基本上是的。 
冲天飞豹 评论于:2007-08-03 21:55:26 (222.92.8.★)
内容:
豹哥好,刚接触LFS,有个疑问哈,还请解答一下。手册上先利用宿主系统编译出binutils和gcc,然后再利用生成的gcc,binutils生成glibc,为什么不先用宿主系统编译出glibc,然后宿主系统再连接到/tools/lib下的库编译生成gcc与binutils呢。我觉得这样也是可以的呀。 
LPZGBD 评论于:2007-08-16 20:42:37 (61.157.97.★)
内容:
主要跟系统的"纯净"度有点关系,另外这样做调整工具链不太方便。 
冲天飞豹 评论于:2007-08-16 21:15:38 (222.92.8.★)
内容:
想了一下,和系统的“纯净”度的确是点关系 ,不过还是对“调整工具链不太方便”不太理解,	豹哥可否在百忙之中详细解答一下,谢谢 
LPZGBD 评论于:2007-08-16 22:24:45 (61.157.97.★)
内容:
主系统的gcc和binutils一般是不支持直接连接到/tools/lib下的库的,所以调整工具链不太方便,不知道我这么说是否清楚. 
冲天飞豹 评论于:2007-08-20 13:45:41 (222.92.8.★)
内容:
明白了,非常感谢 
LPZGBD 评论于:2007-08-22 19:37:53 (61.157.97.★)
内容:
“现在要提到的一个问题就是,用不同版本的gcc编译出来的程序可能不一样,而不同的gcc编译出来的目标文件要用能正确处理的binutils的版本来链接成可执行文件,因此我们现在已经能确定的gcc、binutils版本是工具链中的版本,那么用工具链编译出来的目标系统是符合我们的要求的。”这句话我理解的不是很清楚,想请教豹哥一下几个问题:
1、某个版本的gcc编译出来的object文件是不是需要与该版本gcc相关的特定版本的binutils才能生成可执行文件??
2、工具链编译出来的目标系统是符合我们的要求的,其中我们的要求是什么?? 
风中之花 评论于:2007-09-17 11:27:45 (61.186.249.★)
内容:
1、这个是能处理你编译出来的平台文件的交叉版本的binutils,不过最好是使用和gcc同时期的binutils。
2、我们的要求就是编译出我们想要在哪个平台上运行的程序。 
冲天飞豹 评论于:2007-09-27 16:28:16 (222.92.8.★)
内容:
将“因此在用工具链建立目标系统的过程中将PATH设置为/bin:/usr/bin:/tools/bin来让bash调用命令时能首先调用目标系统中已经建立好的命令,如果没有则从工具链中调用”中的“/bin:/usr/bin:/tools/bin”修改为“/tools/bin:/bin:/usr/bin”,这样更严谨一些 
本站网友 评论于:2008-05-14 14:35:14 (221.222.161.★)
内容:
这里就应该用“/bin:/usr/bin:/tools/bin” 
冲天飞豹 评论于:2008-05-14 22:15:29 (58.213.254.★)
内容:
两个问题:
1,预工具链中gcc和binutils有什么特殊要求?是不是要求与工具链中的gcc和binutils版本一致,或者是某个特殊版本?如果是,是不是因为工具链中预工具链所没有的组件(如make,glibc等)要求预工具链需要有这个特性?
2,预工具链使用的glibc是不是宿主系统中的glibc?
因为我想,预工具链的存在和工具链有两个不同的地方:一是它的工作是编译工具链;二是它是不完整的,很多地方用到了宿主机上的工具。
但是编译工具链,宿主机上的工具也可以,为什么要预工具链呢?所以预工具链存在的理由只能是它有宿主机工具没有的特性,这个特性又恰恰是编译工具链所要求的,想来想去,我觉得只有可能是编译那些工具链当中有而预工具链当中没有的组件(如make,glibc)要求编译它们的工具的版本有特定的限制,所以有第一个疑问。
但是这就牵涉到另外一个问题,预工具链既然不需要glibc等其他组件,那么gcc和binutils能否和这些宿主机上的工具保持兼容,比如说glibc,它是不是用的宿主机上glibc?如果用了,make等工具也许不会对工具链产生影响,主机上glibc的不同又会不会对工具链造成不同?如果造成了不同,那么预工具链就没有保证工具链的“纯净”进而导致生成的目标系统有差异。又或者由于预工具链的工作仅仅是产生工具链,没有用到glibc?
不是很明白,请解答。
补充一下,我觉得预工具链就像一堵墙,屏蔽了宿主系统对工具链造成的影响。由于你文中说预工具链就是gcc和binutils两个,那么言下之意就是gcc和binutils只要保证一致了,无论glibc和make等其他的内容有多大差异,生成的工具链是没有任何差异的。对不对?
 
本站网友 评论于:2008-06-11 03:19:42 (58.240.127.★)
内容:
还有就是,不同的宿主系统,能不能保证生成无差别的gcc和binutils? 
本站网友 评论于:2008-06-11 03:31:12 (58.240.127.★)
内容:
1、原则上应该在预工具链中的gcc和binutils和工具链中的相同,但这个不是必须的,其实只要保证预工具链中的gcc和binutils可以正常的编译工具链就可以了,因为最终的系统是依靠工具链来生成的,预工具链实际上只起到了一个过渡的作用,同时预工具链的重要目的是保证用预工具链编译出来的东西所使用的库都是是链接/tools目录下的,这个是它存在的最主要的功能。
2、预工具链本身并不编译glibc,这个时候glibc是用的主系统中的,目的无它就是为了让预工具链能运行,虽然预工具链是不完整的但是借助主系统提供的工具是可以完成一个“纯净”的工具链的,因为gcc和binutils会对编译出来的东西造成平台相关的影响。
3、LFS制作预工具链的目的就是这个。 
冲天飞豹 评论于:2008-06-21 17:26:35 (121.229.37.★)
内容:
”内核这东西比较特殊,虽然运行任何程序都需要用的内核,但本身在制作目标系统过程中,目标系统的Linux内核却不需要先进行编译,因为使用Linux内核并不像glibc那样是依靠动态链接库的方式被调用的,内核不是用动态库的方式被调用的,因此不需要先编译,只要提供对应的头文件即可“我想问一下linux内核是怎样调用的呢 
围剿 评论于:2008-08-21 14:49:12 (218.4.189.★)
内容:
回“围剿”:一般是通过libc库来调用。 
冲天飞豹 评论于:2008-08-29 21:45:25 (222.92.8.★)

发表评论
 昵称:
 
<script language=javascript> function $(s){return document.getElementById(s);} function check(){ if($("iscomment").value==0){ if($("username").value==""){ alert('请输入您的用户名!'); $("username").select(); return false; } if($("password").value==""){ alert('请输入您的密码!'); $("password").select(); return false; } }else{ if($("nickname").value==""){ alert('请输入您的称呼!'); $("nickname").select(); return false; } } if($("comment").value==""){ alert('你忘记输入内容了!'); $("comment").select(); return false; } return true; } </script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值