想解决BUG,但不想修改代码,更不想重新编译!怎么办?

Go编译缓存导致C文件修改后未重新编译 今天和同事一起看了一个很诡异的问题,研究了半天发现是由于Go的编译缓存导致的,记录下来方便其他遇到问题的同学参考。 首先编写一个hello.go文件,里面调用了C函数: package main /* #include "./libhello.c" */ import "C" import "fmt" func main() { fmt.Println(C.hello()) } 对应... 阅读详情

前几天,有小伙伴问,是否有办法,可以在不修改源码的情况下,把程序中的BUG解决掉!

乍一听,这个要求似乎有点过分,你咋不上天!

但仔细一想,其实现实中有很多这样的例子,比如各种PJ软件、WG等等,大多都是通过直接修改二进制的可执行文件来实现的,尽管它们的目的并不是解决BUG。。。此外,很多系统支持的热补丁功能,也勉强算是不修改代码来修复BUG或者功能升级吧。当然,这些都不是本文重点。

后来细问之下,才知道,原来这位小伙伴所在的项目体量很大,而且历史包袱比较多,每次修改源码、编译、部署等都非常麻烦且耗时。而定位BUG时候,通常又必须要反复修改源码,比如加log或测试验证等,这就导致调试效率非常低。而且,调试结束后,临时添加的调试log还要再逐一删除掉,这也是很头疼的事情。

所以,他在想,有没有什么好的调试方法,可以不需要修改源码就可以随意添加调试log,找到问题后,还能很容易验证解决思路是否正确,这样的话,就可以减少反复修改源码、编译、部署的过程,可以节省很多时间。

这个真的有!本文将介绍一种调试技巧,可以一边调试,一边修复Bug,能够在不修改代码、不重新编译的前提下修复BUG,并且验证解决方案,大幅提高调试效率!

先看下最终效果吧!

本文预期效果

如下图,冒泡排序中,有三个常见的BUG:

图片

图中已经把三个BUG都标注了出来。编译运行,结果如下:

图片

GDB中执行时:

图片

不管是正常方式执行,还是在GDB中执行,程序都异常终止,无法得到正常结果。

但是,利用本文介绍的调试技巧,可以利用GDB给这个程序制作一个“热补丁”,在不修改代码、不重新编译的前提下,解决掉程序中的三个BUG,让程序正常执行,并得到预期结果!

最终效果,如下图所示:

图片

所有的黑魔法,都在这个补丁文件bubble.fix中!

是不是很有趣呢?下面开始介绍!

关于GDB

我之前写了几篇文章,专门介绍GDB的一些非常实用却鲜为人知的高阶用法,感兴趣的小伙伴可以去翻看下调试系列专题文章。

GDB的基本用法,相信大家都很熟悉了,就不过多介绍了,直接讲重点吧!

Breakpoint Command Lists

GDB支持在断点触发后,自动执行用户预设的一组调试命令。使用方法:

commands [bp_id...]
  command-list
end

其中:

  • • commands是GDB内置关键字。

  • • bp_id是i(info)命令显示出来的断点ID,可以指定多个,也可以不指定。不指定时,默认只对最近一次设置的那个断点有效。

  • • command-list是用户预设的一组命令,当bp_id指定的断点被触发时,GDB会自动执行这些命令。

  • • end表示结束。

简单来说,就是当bp_id所表示的断点被触发时,GDB会自动执行command-list中所指定的命令。

这个功能适用于各种类型的断点,如breakpoint、watchpoint、catchpoint等。

适用场景举例

利用GDB的breakpoint commands lists这个特性可以做很多有趣的事情,本文仅列举其中的几个。

随时随地printf,不需修改代码和重新编译

我之前写过一篇文章,详细介绍过GDB的动态打印(Dynamic Printf)功能,可以用dprintf命令在代码的任意地方设置动态打印断点,并自动进行格式化打印。相当于在不修改代码,不重新编译的情况下,可以让你随意添加printf打印日志信息。

利用GDB的breakpoint commands lists,可以实现一样的功能,而且除了格式化打印之外,还可以做其它更多的操作,比如dump内存,dump寄存器等。

修改程序执行逻辑

在GDB中可以做很多有趣的事情,比如修改变量、修改寄存器、调用函数等。结合breakpoint command list功能,可以在调试的同时,修改程序执行逻辑,给程序打上"热补丁"。从而可以在调试过程中,快速修复Bug和验证解决方案,避免重新修改代码和重新编译,大大提高程序调试的效率!

这也是本文重点讲解的场景,稍后会演示如何利用这个功能,在调试的过程中,不修改代码,就能修复掉上文冒泡排序程序中的三个Bug。

进行自动化调试,提高调试效率

很多童鞋可能不知道,GDB支持非常强大的脚本功能,除了GDB自己特定的脚本外,它甚至还支持Python脚本!

有了breakpoint commands lists功能,结合GDB支持的脚本功能,以及自定义命令功能,甚至可以实现调试自动化。

其他还有很多非常有趣且实用的功能场景,限于篇幅,不再展开,有机会再写文章专门介绍吧!

接下来,正式开始解决冒泡排序的三个Bug!

给冒泡排序打上"热补丁"

现在,我们利用GDB的breakpoint command lists功能,给文中的冒泡排序程序打上"热补丁",演示如何在不修改源码、不重新编译的前提下,解决掉程序中的三个BUG。

再看一下示例程序:

图片

解决第一个BUG

先解决第22行的BUG:数组arr元素个数是10,但是传递给了bubble_sort()的参数却是sizeof(arr),也就是40。

要解决这个BUG,我们只需要把参数修改成正确的值就行了。

我们知道,在x64上,优先采用寄存器传递函数参数。那么,有这几种方式可以选择:

  • • 把断点设置在bubble_sort()入口第一条指令,然后直接修改存放数组长度n的那个寄存器中的值。

  • • 把断点设置在bubble_sort()入口处(不必是第一条指令),在第7行for循环之前,把存放数组长度的变量n的值改掉。

  • • 把断点设置在main()函数第22行,也就是调用bubble_sort()的地方,然后以正确的参数手动调用bubble_sort()函数,并利用GDB的jump命令,跳过第22行代码的执行。

考虑到有些童鞋对x64 CPU不是非常了解,或者对GDB的jump命令不熟悉,我们采用第2种方式。而且,这种方式也更简单通用。

我们先在bubble_sort()函数设置断点,然后利用commands命令预设一条命令,把变量n的值修改为10。命令如下:

b bubble_sort
commands 1
  set var n=10
end

设置完之后,用run命令开始运行程序。结果如下:

图片

bubble_sort()处的断点被触发后,程序暂停,用p(print)命令查看变量n的值,已经被修改成了正确的值:10。

可见,我们的设置是有效的。

断点触发后,让程序自动恢复执行

bubble_sort()处断点被触发,程序停了下来,修改完变量n的值后,怎么自动恢复执行呢?

很简单,只需要在预设的命令中添加一个continue命令就可以了。为了证明我们的设置确实是生效的,在修改变量n的前后,各添加一个格式化打印语句,把变量n的值打印出来:

b bubble_sort
commands 1
  printf "The original value of n is %d\n",n
  set var n=10
  printf "Current value of n is %d\n",n
  continue
end

结果如下图:

图片

从运行结果可以看出,断点被触发后,我们预设的语句被正确执行,变量n的值被修改为10,然后程序自动恢复执行。虽然最终程序不会发生segfault了,但打印出来的排序结果仍然是错的!不着急,还有两个BUG没解决呢!

到此,第一个BUG已经解决了。

解决第二个BUG

下面,开始解决第7行的数组访问越界BUG:数组的元素个数是n,但是bubble_sort()中第一个for循环的终止条件是i<=n,明显会造成访问越界,正确的条件应该是i<n。

要解决这个BUG也很简单,只需要在执行第8行代码之前,判断如果i的值等于n,就跳出循环。对于这个简单的程序,我们直接从bubble_sort()函数return就可以了。

命令如下:

b 8 if i==n
command 2
  printf "Current i = %d, n = %d\n",i,n
  return
  continue
end

在第8行设置条件断点,当i==n时断点被触发,然后自动把i和n的值打印出来,再行return命令,从bubble_sort()返回,然后continue命令自动恢复程序执行。

执行结果如下图:

图片

解决第三个BUG

下面,解决最后一个BUG,第23行数组访问越界错误:数组arr的长度应该是10,不是sizeof(arr)。

解决思路与第二个BUG类似,在第24行设置条件断点,当i==10时触发断点,然后用jump命令跳出循环,让程序跳转到第26行继续执行。命令如下:

b 24 if i==10
commands 3
  printf "i=%d, exit from for loop!\n",i
  jump 26
  continue
end

执行结果如下图所示:

图片

从图中可以看出,三个断点全部被触发,并且预设的命令都正常执行。最终程序正常结束,我们终于得到了正确的执行结果!

虽然,现在程序可以正常执行了,但每次都要手动输入这么多命令,想想都觉得麻烦!我之前文章介绍过,GDB支持调试脚本,可以从脚本中加载并执行调试命令。

下面,利用GDB脚本,来制作我们的“热补丁”文件。

制作"热补丁"脚本

把上文中用来解决三个BUG的命令保存在一个脚本文件中:

vi bubble.fix

脚本内容如下图:

图片

bubble.fix脚本中的命令,与上文在GDB中直接输入的命令有几个区别:

  • • 删除了格式化打印信息。

  • • 删除了commands后面的断点ID。上文讲过,commands后面的断点ID可以省略,表示对最近一次设置的断点有效。为了让脚本更加通用,每个commands都紧跟在break命令之后,因此直接省略了断点ID。

GDB的脚本可以通过两种方式执行:

  • • 启动GDB时,用-x参数指定要执行的脚本文件。

  • • 启动GDB后,执行source命令执行指定的脚本。

下面,我们用第二种方式演示一下,如下图所示:

图片

使用source命令加载并执行bubble.fix,然后用run命令执行程序,三个断点均被触发,且预设的命令全部被正确执行,最后程序运行正常,得到期望的结果!

我们现在可以利用我们制作的"热补丁"脚本,在不修改代码、不重新编译和部署的前提下,成功修复程序中的BUG!是不是很有趣呢?

不过,做到这种程度,还是有点瑕疵。虽然得到了正确的结果,但程序执行时,总是会打印断点信息,造成视觉干扰,作为典型的"伪完美主义者",这怎么能忍!

最后,我们来解决这个问题,让我们的"热补丁"更加完美!

优化"热补丁"脚本,隐藏断点信息

在预设的命令中,如果第一条命令是silent,断点被触发的打印信息会被屏蔽掉。

我们把bubble.fix做些修改,把silent命令加进去,如下图所示:

图片

此外,在最后面加了一个run命令,这样就不用每次手动执行了。

然后,我们换一种方式来执行:

图片

这样,看起来,清爽多了!

到此,我们终于实现了最终的目标:一边debug,一边修复BUG,并验证解决方案,避免反复修改代码、重新编译和部署、提高调试效率!

Bug(已解决,不是代码问题):调试停在objcore.cpp 一启动项目就遇到以下错误: 阅读详情

相关推荐

【Unity开发效率神器】Hot Reload 修改代码不用重新编译!

摘要: Unity开发中,传统代码修改需经历耗时编译流程,严重影响效率。Hot Reload插件通过IL代码补丁、方法重定向等技术,实现运行时直接修改C#代码并即时生效,无需重新编译或退出Play Mode。它支持方法逻辑、字段定义等修改,保留游戏状态,显著提升调试效率。核心原理包括动态替换方法体、加载补丁程序集、绕过Domain Reload等,但不支持IL2CPP平台。该工具可节省大量开发时间,尤其适用于参数调整、逻辑调试等场景,是重塑Unity工作流的高效解决方案。

2403_88403568的博客 493

GDB高级技巧:边Debug边修复BUG,无需修改代码,无需重新编译

友情提醒:本文转自公众号“一口LInux”,可关注该公众号获取更多知识。本文介绍的调试技巧非常实用,但为了讲解清楚,篇幅较长,请耐心看完,我保证你定会有收获! 引言 程序调试时,你是否遇到过下面几种情况: 1、经过定位,终于找到了程序中的一个BUG,满心欢喜地以为找到了root cause,便迫不及待地修改源码,然后重新编译,重新部署。但验证时却发现,真正的问题并没有解决,代码中还隐藏着更多的问题。 2、调试时,我们找到代码中一个可疑的地方,但是不能100%确定这真的就是个BUG。要想确定,只能修

咸鱼弟的博客 1638

告别混乱!用Cadence Capture CIS高效管理你的原理图库:以单逻辑器件K9F2G08U0M为例

本文以单逻辑器件K9F2G08U0M为例,详细介绍了如何使用Cadence Capture CIS高效管理原理图库。通过三级分类体系、工程化命名规范和智能检索技术,帮助工程师实现从混乱到有序的转变,提升团队协作效率和设计准确性。

aizhi0169的博客 403

gdb 条件断点_GDB高级技巧:同一个Bug,5种解决方案,不修改源码,不重新编译

1、引言在《GDB高级技巧:边Debug边修复BUG,无需修改代码,无需重新编译》一文中,介绍了使用GDB breakpoint command lists的功能,可以在不修改源码、不重新编译的前提下,修复掉被调试程序中的BUG,从而避免反复修改代码和编译构建的过程,大大提高程序调试的效率。文中提到了解决一个问题的几种不同的思路,但限于篇幅,只重点讲解了其中的一种。有童鞋希望能够把其它几种也讲解一...

weixin_39713833的博客 936

YOLOv8 TensorRT C++部署实战详解:从XMake构建到推理流水线

本项目实现了一个基于YOLOv8和TensorRT的高性能火灾烟雾检测系统,具有以下核心特点: 技术架构:采用TensorRT 8.6+进行GPU加速推理,结合CUDA 12.1和OpenCV 4.8+,实现高效计算机视觉处理。系统使用XMake替代CMake构建,配置更简洁高效。

hscing的博客 1066

手机安装Kali Linux

在数字化时代,信息安全和隐私保护显得尤为重要。,作为一款专业的和工具,因其强大的功能和丰富的资源库而受到广大安全研究者和爱好者的青睐。然而,我们通常只能在传统的电脑设备上安装和使用Kali Linux。那么,有没有可能将也变成一款随身携带的Kali Linux设备呢?答案是肯定的。本文将介绍如何在手机上安装Kali Linux,使你的手机变成一款强大的移动安全工具。,我们需要明确一点,手机安装Kali Linux并非简单的操作,需要对手机进行一定的改造和配置。

2301_80124151的博客 6972

gdb x命令_GDB高级技巧:边Debug边修复BUG,无需修改代码,无需重新编译

友情提醒:本文介绍的调试技巧非常实用,但为了讲解清楚,篇幅较长,请耐心看完,我保证你定会有收获!引言程序调试时,你是否遇到过下面几种情况:1、经过定位,终于找到了程序中的一个BUG,满心欢喜地以为找到了root cause,便迫不及待地修改源码,然后重新编译,重新部署。但验证时却发现,真正的问题并没有解决,代码中还隐藏着更多的问题。2、调试时,我们找到代码中一个可疑的地方,但是不能100%确定这真...

weixin_39539563的博客 653

Qoder + ADB Supabase :5分钟GET超火AI手办生图APP

本文介绍如何利用Qoder、阿里云 ADB Supabase 和通义千问图像编辑模型,快速构建AI手办生图Flutter应用。无需传统后端,通过AI生成代码、BaaS服务与边缘函数调用,实现从原型到上线的敏捷开发,全流程轻量高效,助力Vibe Coding落地实践。

数据库技术 931

4修改初始值_GDB高级技巧:同一个Bug,5种解决方案,不修改源码,不重新编译...

1、引言在《GDB高级技巧:边Debug边修复BUG,无需修改代码,无需重新编译》一文中,介绍了使用GDB breakpoint command lists的功能,可以在不修改源码、不重新编译的前提下,修复掉被调试程序中的BUG,从而避免反复修改代码和编译构建的过程,大大提高程序调试的效率。文中提到了解决一个问题的几种不同的思路,但限于篇幅,只重点讲解了其中的一种。有童鞋希望能够把其它几种也讲解一...

weixin_35799569的博客 249

从零到一:STM32 CubeMX与WS2812B的DMA驱动艺术

本文详细介绍了如何使用STM32 CubeMX和DMA技术驱动WS2812B RGB LED灯条,从硬件设计到软件实现的全过程。通过CubeMX配置定时器和DMA,实现高效的PWM波形生成与数据传输,解决传统GPIO控制资源占用高的问题。文章还提供了颜色编码、动画效果实现及常见问题解决方案,助力开发者快速掌握智能LED控制技术。

weixin_28278377的博客 979

LabVIew串口收发的一些问题

LabView串口收发实验总结

weixin_45508826的博客 2066

关于修改web app项目java代码后Eclipse没有重新编译的问题

描述:有时候修改java web app项目代码后,Eclipse没有重新编译,导致修改后的代码不起作用。 原因:怀疑是eclipse内部有Bug,即使tomcat设成调试模式也不行,间断性抽风,不过几率较低。 解决:redeploy/reload也无补于事,我的方案是先把项目remove,然后重新发布。

davidin的专栏 3033

ANFIS实现数据非线性回归附Matlab代码

在本文中,我们将介绍如何使用Matlab实现基于MSE的ANFIS非线性回归,并提供相应的源代码。ANFIS是一种融合了模糊逻辑和神经网络的方法,它可以通过学习数据集中的模式来构建一个模糊推理系统。ANFIS的训练过程基于最小化MSE,以提高模型的拟合能力。通过上述代码示例,我们可以使用基于MSE的ANFIS模型对数据进行非线性回归,并获得相应的预测结果。在示例中,我们定义了一个包含两个样本的新输入数据集 X_new,然后使用训练好的ANFIS模型进行预测,并将结果存储在变量 Y_pred 中。

2301_79326588的博客 474

dump文件 修复iat_GDB高级技巧:边Debug边修复BUG,无需修改代码,无需重新编译...

友情提醒:本文介绍的调试技巧非常实用,但为了讲解清楚,篇幅较长,请耐心看完,我保证你定会有收获!引言程序调试时,你是否遇到过下面几种情况:1、经过定位,终于找到了程序中的一个BUG,满心欢喜地以为找到了root cause,便迫不及待地修改源码,然后重新编译,重新部署。但验证时却发现,真正的问题并没有解决,代码中还隐藏着更多的问题。2、调试时,我们找到代码中一个可疑的地方,但是不能100%确定这真...

weixin_39840635的博客 189

educoder实训——计算几何形状的表面积与体积

educoder python实训

qq_41234663的博客 1230

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

gdb 打印字符串长度_神奇的GDB:给程序制作“热补丁”,原来修复BUG不需要修改代码...

友情提醒:本文介绍的调试技巧非常实用,但为了讲解清楚,篇幅较长,请耐心看完,我保证你定会有收获!引言程序调试时,你是否遇到过下面几种情况:1、经过定位,终于找到了程序中的一个BUG,满心欢喜地以为找到了root cause,便迫不及待地修改源码,然后重新编译,重新部署。但验证时却发现,真正的问题并没有解决,代码中还隐藏着更多的问题。2、调试时,我们找到代码中一个可疑的地方,但是不能100%确定这真...

weixin_33037089的博客 217

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

反编译jar包,修改后重新编译为jar包

使用开源jar包或者供应商jar包时,会发现一些bug或者已有功能无法满足我们要求,需要对jar中的.class文件进行修改。处理步骤如下所示: 1.使用反编译工具将jar包反编译为源文件 反编译工具请参考XJAD2.2版下载地址:http://www.blogjava.net/Files/96sd2/XJad2.2.rar 如果只是少量的几个class需要修改,可以直接用IDEA或者jd-gui工具打开,将.class文件中的内容直接复制并创建同名的.java文件(需要根据jar包中建立所有的上层目

lgq2016的博客 8213

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

修改Delphi源代码并生成可以发布的无BUG BPL

    Delphi虽然是一个相当棒的RAD工具,但是难免也有小的瑕疵。遇到这种情况我们往往会找到Delphi/Source中的源代码进行修改,把这个源代码加入我们的Application,重新编译一下可执行程序就解决了这些小虫子。     但是如果我们要发布的是应用程序是“Build with runtime packages”(就是说,要连同该应用用到的BPL一起发布才能正常运行应用程序),我

LovEnder的专栏 2605

如何安全的修改原有代码---android mms player调试总结

<br /><br />如何去安全的在别人写的现有代码基础上修改或者添加功能, 尽可能少的引入bug?<br />修改或添加代码的效果,都必须基于对现有代码的理解程度,理解得到位,改起来就顺利一些, 而如果理解不到位,就容易产生疏漏。<br /> <br />当然对于一个比较大的系统,比如android, 想看懂其中哪一个模块都是不容易的。很难一下子就保证完全理解正确, 那是不是要等确定完全理解正确才下手呢?<br /> <br />从《代码大全》中认为,很多问题都是“险恶”的,你必须首先把这个问题“解决”

wswupeng的专栏 1632

不只是看代码:用Reflexil插件实战修改.NET DLL,解决微软官方未修复的Bug

本文详细介绍了如何使用Reflexil插件修改.NET DLL,解决微软官方未修复的Bug。通过Reflector和Reflexil工具组合,开发者可以直接编辑IL代码,实现精准的程序集调整。文章涵盖环境准备、实战案例、高级技巧及风险评估,帮助开发者在紧急情况下主动解决问题。

weixin_30920513的博客 379

【亲测免费】 解决LWIP死机问题:高效修复TCP死循环bug

解决LWIP死机问题:高效修复TCP死循环bug 【下载地址】LWIP死机问题解决办法 本资源文件提供了针对LWIP(轻量级IP协议栈)在客户端或服务端集合使用时可能出现的死循环bug的解决办法。具体问题出现在以下代码段:```cfor(pcb = tcp_active_pcbs; pcb != NULL; pcb = ...

gitblog_06659的博客 1607

gdb打印指针内容_神奇的GDB:给程序制作“热补丁”,原来修复BUG不需要修改代码

友情提醒:本文介绍的调试技巧非常实用,但为了讲解清楚,篇幅较长,请耐心看完,我保证你定会有收获!引言程序调试时,你是否遇到过下面几种情况:1、经过定位,终于找到了程序中的一个BUG,满心欢喜地以为找到了root cause,便迫不及待地修改源码,然后重新编译,重新部署。但验证时却发现,真正的问题并没有解决,代码中还隐藏着更多的问题。2、调试时,我们找到代码中一个可疑的地方,但是不能100%确定这真...

weixin_39901558的博客 1402

遗留代码动不得,Bug修不完!25年老程序员:我受够了,选择离职

至于具体的应用名称,这名老程序员并未透露,只表示,此款应用在过去增长很快,广告投得也多,销售团队也很能「打」,但在最近几个月,公司却被大量客户流失和业务缩水搞得焦头烂额。我甚至提出过,把项目分叉,尝试不同的设计方案和取舍,看看能不能解决问题。该初创公司现有的研发团队自己也很崩溃,因为这个项目用的是 React Native 技术,依赖一大堆 NPM 包,其中不少早就没人维护,稍微一碰就崩。多年下来,这自然导致了难以解决的技术债务,以及我们依法必须维护的各类仓库分支——无论是自己开发的,还是第三方的。

优快云资讯 2万+

关于如何反编译jar包,修改之后再重新编译打包出错的解决办法

遇到另一个头疼的问题困扰了半天,我一个android的项目引用了一个指纹厂商提供的jar包,但是厂商没有提供源码,有一个bug需要修改他的源码,苦于没办法,自己下了个反编译的工具,反编译了整个jar的工程,修改了代码之后,再重新用eclipse导出为jar包,但是问题出来了,反编译之后全是java文件,但是eclipse在导出为jar的时候没有给我生成.class文件,勾选了生成class文件也不

xusen112的专栏 3025

java重新编译代码,java-更改第三方jar的版本是否需要重新编译我的代码?

这个问题已经在这里有了答案:>Do I have to recompile my application when I upgrade a third party jar?5个这似乎是一个愚蠢的问题,但是在我去团队会议上大开眼界之前,我想绝对确定自己正确理解了一切.我有一个...

weixin_28762271的博客 1099
上一篇: 屎一样的代码命名,心态崩了。。。
下一篇: 20行经典C代码,很多人看不明白,你来试一下?
IT界那些事儿
博客等级 码龄6年 2万+粉丝 22原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值