Btrace 原理分析

动态追踪技术之BTrace 作为 Java的动态追踪技术,站在比较底层的角度上来说,底层无非就是基ASM、Java Attach API、Instrument开发的创建。Arthas 都是针前面这些技术的一个封装而已。 阅读详情

http://www.iteye.com/topic/1005918

概述

Btrace是由sundararajan在2009年6月开发的一个开源项目,是一种动态跟踪分析一个运行中的Java应用程序的工具。
BTrace是一个为Java平台开发的安全、动态的追踪工具。BTrace动态地向目标应用程序的字节码注入追踪代码(字节码追踪),这些追踪字节码追踪代码使用Java语言表达,也就是BTrace的脚本。

组件说明

说明:
1. BtraceClient : 为我们使用的btrace的本地api,一般我们使用的bin/btrace会在本地启动一个btrace jvm,其内部使用了Java Complier Api, JVMTI技术,以及创建了一个socket。

Java Complier Api:动态的将我们传递的监控的java源文件动态编译成.class文件
JVMTI: 主要是利用了java 1.6之后的VirtaulMachine技术,动态的attach到一个已启动的jvm上,为他去启动一个BtraceAgent。该Agent会为BtraceClient启动一个server socket进行通讯。(多进程之间的通讯)
本地socket: BtraceClient和BtraceAgent之间的数据通讯,比如生成的.class发送到BtraceAgent,还有一些Event事件等等。BtraceAgent同样可以将服务端print()的数据通过socket的方式回传给BtraceClient进行打印
2. BtraceAgent:为我们在目标jvm上植入的btrace agent实现。主要是Instrumentation技术, asm字节码处理技术。
BtraceAgent的启动可以有两种方式: BtaceClient动态attach后进行启动, 另一种就是在目标jvm启动之前添加agent参数进行启动。
BtraceAgent会启动一个server socket,与BtraceClient客户端进行交互,客户端可以将监控的.class文件通过socket发送,同样也可以在jvm启动时直接指定对应本地的.class做为监控脚本。
BtraceAgent在接受到监控指令后,会遍历当前所有已经加载的class类,挨个进行匹配检查,并生成相应的监控字节码(监控方法)。

btrace的包结构:
btrace-client.jar
btrace-boot.jar
btrace-agent.jar

btrace-client:
一般我们通常直接使用的命令,比如:

bin/btrace $pid Btrace.java

都是直接调用了btrace-client包中的代码。

核心类介绍:

com.sun.btrace.client.Main (btrace的启动入口)

调用Client进行complier(Java Compiler Api)和attach(JVM TI)处理

com.sun.btrace.client.Client

compiler方法 : 调用了Java Complier Api进行动态编译你的Btrace.java文件

Verifier btraceVerifier = new Verifier(this.unsafe); //指定了btrace特定的语法校验器 

attach方法: 调用VirtualMachine.attach(pid); vm.loadAgent(agentPath, agentArgs);动态加载btrace-agent.jar包

动态加载agent可传递给agent程序的几个参数:
debug=true
unsafe=true
dumpClass=true
dumpDir=xx
trackRetransforms=true ##是否记录instrument行为
bootClassPath= xx ##agent.jar使用
systemClassPath =xx ##agent.jar使用
probeDescPath=xx

submit方法: 调用提交对应的instrument指令,并传递对应code的byte[]

this.sock = new Socket("localhost", this.port);    
this.oos = new ObjectOutputStream(this.sock.getOutputStream());    
WireIO.write(this.oos, new InstrumentCommand(code, args));    

几点说明:
* 在调用了attach方法后,会通过btrace-agent.jar中的com.sun.btrace.agent.Main启动一个ServerSocket


int port = 2020;    
String p = (String)argMap.get("port");    
ServerSocket ss;    
  try {    
     (isDebug()) debugPrint(new StringBuilder().append("starting server at ").append(port).toString());    
     System.setProperty("btrace.port", String.valueOf(port));    
     if ((scriptOutputFile != null) && (scriptOutputFile.length() > 0)) {    
          System.setProperty("btrace.output", scriptOutputFile);    
     }    
     ss = new ServerSocket(port);    
   } catch(Exception e) ....    


while (true)    
{    
   if (!isDebug()) continue; debugPrint("waiting for clients");    
   Socket sock = ss.accept();    
   if (!isDebug()) continue; debugPrint(new StringBuilder().append("client accepted ").append(sock).toString());    
   Client client = new RemoteClient(inst, sock);    
   handleNewClient(client);    
   continue;    
}    
  • 所以在submit中,会通过一个本地socket进行连接server,并提交相应的Btrace.java中的监控代码(这时应该是编译后的字节码).

com.sun.btrace.compiler.Verifier btrace自定义的语法校验器

Boolean value = this.unsafe ? Boolean.TRUE : (Boolean)ct.accept(new
VerifierVisitor(this), null); // 注意下unsafe的判断

###com.sun.btrace.compiler.VerifierVisitor (具体的一些检查规则)
visitBinary String字符串的+限制
visitClass class的检查,不允许有父类,不允许有接口类,不允许非static变量,必须有Btrace @标签
visitDoWhileLoop 不允许do while循环
visitForLoop 不允许for循环
visitMethod 必须为static public ,不允许出现synchronized标记
visitNewArray 不允许出现new Array
visitNewClass 不允许出现new 对象
visitReturn 不允许有返回值
visitSynchronized 不允许有同步快
visitThrow visitTry 不允许有try catch的动作
visitOther 除上面允许之外的,不允许有其他的
说明:
* 看完Verifier和VerifierVisitor后,相信大家都应该明白了Btrace所谓的诸多限制,只是针对.java需要动态编译。如果我们预先生成.class文件,Btrace在1.2版本中并不会作类型合法性检查。(在将code发送给btrace-agent后,会在目标的jvm内部进行一次简单的Btrace语法检查,具体见后面Btrace-agent介绍)

  1. com.sun.btrace.comm.XXX Btrace的各种command指令

btrace-agent

大致了解了Client类中的attach和submit方法后,相信也能猜到对应agent的一些设计。简单的看一下:
com.sun.btrace.agent.Main 为attach上之后agent的总入口,会调用agentmain()方法
main方法: 首先解析参数,然后会启动一个agentThread(Daemon线程)
parseArgs : 对应的参数解析,客户端在attach时,提交给agent后的一些参数

bootClassPath=xx.jar //需要动态增加的jar systemClassPath=xx.jar
noServer=true/false //是否启动server socket debug=true/false
unsafe=true/false dumpClasses=true/false dumpDir=路径
trackRetransforms=true/false probeDescPath=路径
stdout=true/false script=文件 scriptdir=路径
scriptOutputFile=文件路径特别注意下相比于Btrace-client提交的参数中,多了几个script,scriptdir等参数,允许在Client调用服务端一个指定的Btrace
script文件进行处理

loadBTraceScript : 装载指定的script(注意是script和scriptDir中指定的script),必须是.class文件,会调用FileClient进行处理,最后调用handleNewClient进行统一处理,最后调用handleNewClient进行统一instrument处理。
startServer : main启动的agentThread会调用该方法,这里会启动一个serversocket,和btrace-client的客户端socket进行通讯,使用RemoteClient,最后调用handleNewClient进行统一instrument处理。
handleNewClient : 启动一个异步线程进行class Transformer,根据提交的byte[] code进行类文件重写

说明:
* 目前instrument进行字节码重写时,会重新load所有的class进行处理。(Btrace可以使用正则,父类的方式进行匹配,只能是挨个Class进行处理,看下是否有匹配的OnMethod)

Btrace总结

监控方式

本地jvm监控:目前大多数都是用的是btrace和监控的目标java是在同一机器上
远程jvm监控:需要在远程服务器启动时添加btrace-agent.jar,需要重写btrace客户端,完成和serversocket建立通讯,完成btrace script发送监控。
1. VirtualMachine动态attach不支持远程操作,所以无法动态的进行agent添加。
2. btrace支持的jdk版本
java 1.4以及之前 : 不支持,Instrument在jdk 1.5之后才出现。
java 1.5 : 必须手动在jvm启动时添加btrace-agent.jar,因为VirtualMachine是在jdk 1.6之后才出现。
java 1.6 : 推荐使用

btrace的支持的script方式有多种。

1.client上的.java文件
会进行动态编译,会有比较多的语法限制,btrace一堆的你不能做的事
2.client上的.class文件
没什么好讲的,自己写Btrace script时导入btrace-client.jar,写好后生成一个.class文件,再通过btrace pid Btrace.class进行启动。
3.remote上的.class文件
修改btrace-client中的Client类,支持script和scriptDir的一些参数提交。
在remote机器上放置对应的btrace.class文件

btrace的使用是否会对java进程造成影响?(影响是肯定的,不过影响不大)

1.装载时的影响:
btrace每次使用,都会重新load所有的class。当然如果OnMethod不匹配,是不会被重新装载。所以跟你的OnMethod的匹配规则很有关系,如果使用**+**java.lang.Object。那就死定了。
2.退出后的影响:
btrace监控每次退出后,原先所有的class都不会被恢复,你的所有的监控代码依然一直在运行 。

抓取了下btrace改写过后的类(也可以通过jvisualvm的btrace插件dump出修改类):
public InstrumentServer(String ip, String port)  
  {  
    $btrace$com$agapple$btrace$Instrumentor$InstrumentTracer$bufferMonitor(this);  
    this.ip = ip;  
    this.port = port;  
  }  

private static void $btrace$com$agapple$btrace$Instrumentor$InstrumentTracer$bufferMonitor(@Self Object arg0)  
  {  
    if (!BTraceRuntime.enter(InstrumentTracer.runtime)) return; try { Field ipField = BTraceUtils.field("com.agapple.btrace.Instrumentor.InstrumentServer", "ip");  
      Field portField = BTraceUtils.field("com.agapple.btrace.Instrumentor.InstrumentServer", "port");  

      String ip = (String)BTraceUtils.get(ipField, self);  
      String port = (String)BTraceUtils.get(portField, self);  
      BTraceUtils.println(BTraceUtils.strcat(BTraceUtils.strcat(BTraceUtils.strcat("ip : ", BTraceUtils.str(ip)), " port : "), BTraceUtils.str(port)));  
      BTraceRuntime.leave(); return; } catch (Throwable localThrowable) { BTraceRuntime.handleException(localThrowable);  
    }  
  }  
注意其中的if (!BTraceRuntime.enter(InstrumentTracer.runtime)) return; 
再看一下BTraceRuntime中对应方法的实现:
private volatile boolean disabled;  

public static boolean enter(BTraceRuntime current)  
  {  
    if (current.disabled) return false;  
    return map.enter(current);  
  }  
每次执行你的监控代码之前会先进行一个判断,判断当前是否处于监控中。你的客户端发起了exit指令后,该方法判断false,直接return。
所以btrace使用退出后会让你的代码多走了一个方法调用+一个对象属性判断,所以说影响还是非常的少。

btrace诸多的使用限制(设置unsafe=true可突破限制):

can not create new objects.
can not create new arrays.
can not throw exceptions.
can not catch exceptions.
can not make arbitrary instance or static method calls - only the public static methods of com.sun.btrace.BTraceUtils class or methods declared in the same program may be called from a BTrace program.
(pre 1.2) can not have instance fields and methods. Only static public void returning methods are allowed for a BTrace class. And all fields have to be static.
can not assign to static or instance fields of target program’s classes and objects. But, BTrace class can assign to it’s own static fields (“trace state” can be mutated).
can not have outer, inner, nested or local classes.
can not have synchronized blocks or synchronized methods.
can not have loops (for, while, do..while)
can not extend arbitrary class (super class has to be java.lang.Object)
can not implement interfaces.
can not contains assert statements.
can not use class literals.

说明:

在btrace-client和btrace-agent分别都有对诸多限制的检查。
btrace-client代码类: http://kenai.com/projects/btrace/sources/hg/content/src/share/classes/com/sun/btrace/compiler/VerifierVisitor.java
btrace-agent代码类: http://kenai.com/projects/btrace/sources/hg/content/src/share/classes/com/sun/btrace/runtime/MethodInstrumentor.java
补充说明:

正因为btrace有这诸多的限制,才可以让我们的监控代码可以更加的放心,这也正是btrace能普及的一个很重要的原因。
不得不说的一个点:对String的”+”限制使用,让我们使用起来很不爽,不过还好在btrace 1.2之后,作者提供了一个StringBuilder,相比于strcat已经好用多了。
btrace可突破对应的限制。不是非常建议,因为总结4中提出即使btrace client退出后,服务端一直会运行btrace script。所以一旦有写的动作,会是一个长期持续的过程。

btrace的相关源码:

官网上找到一个,不过是web版本: http://kenai.com/projects/btrace/sources/hg/show/src/share/classes/com/sun/btrace
BTrace是GPLv2开源的。源码用Mercurial管理着。
要源码的话这样就好了: hg clone https://hg.kenai.com/hg/btrace~hg

Windows 游戏 EXCEPTION_ACCESS_VIOLATION 修复:3类硬件问题与5步软件排查法 本文提供Windows游戏EXCEPTION_ACCESS_VIOLATION错误的终极修复指南,涵盖3类硬件问题(内存故障、显卡问题、超频不稳定)和5步软件排查法(文件验证、驱动更新、DEP调整等),帮助玩家彻底解决0xFFFFFFFFFFFFFFFF内存访问冲突问题。 阅读详情

相关推荐

手把手教你用STM32CubeIDE移植Vector CCP驱动,实现与INCA的标定通信(附避坑指南)

本文详细介绍了如何在STM32CubeIDE环境下移植Vector CCP驱动,实现与INCA的标定通信。从开发环境准备、CAN外设配置到CCP协议栈移植和A2L文件生成,提供了完整的操作流程和常见问题解决方案,帮助工程师快速掌握基于CAN总线的标定技术。

weixin_28685287的博客 138

字节跳动开源神器:btrace 2.0 技术原理大揭秘

在一年多前,我们对外正式开源了 btrace(AKA RheaTrace),它是基于 Systrace 的高性能 Trace 工具,目前字节跳动已经有接近 10+ 产品团队使用 btrace 做日常性能优化工作。使用体验:Windows 有着大量用户群体,但 btrace 1.0 未支持;桌面脚本依赖 Systrace 和 Python 2.7 环境,导致环境搭建十分复杂,此外手机端还依赖外部存储访问权限,在初次使用时很容易导致打断。同时产物体积庞大,网页打开速度很慢。性能体验。

chuyouyinghe的专栏 5319

【Unity学习笔记】事件系统EventSystem

1.EventSystem物体 第一次在Unity中创建UI元素时,总会自动生成一个EventSystem物体,它包含三个组件:Transform、EventSystem、Standalone Input Module。如果我们把它删除了,会发现UI上的事件(比如点击、拖拽)将不会被检测到。没错,它就是用来处理UI相关的事件的。 1.1 EventSystem组件 EventSystem基于输入给物体发送事件,它在整个事件系统中扮演的主要角色是: 1.管理被选中的物体 2.管理正在使用的Input Modu

一白梦人的博客 1万+

BTrace快速入门

概述 btrace是sun的一款java动态诊断工具。可以在运行中的java类中动态的注入trace代码,这样我们就可以在不停机的情况下获取方法的入参,返回值,等信息,从而达到监控调试的目的。但是使用btrace可能导致jvm崩溃,所以才生产环境使用btrace必须要在本地验证脚本的正确性。 配置 安装:去https://github.com/btraceio/btrace下载btra...

Shuai.Zhang 4684

【阿里云大模型高级工程师ACP习题集】2.3 优化提示词改善答疑机器人回答质量

【摘要】这是我精心整理出对应的习题集,里面足足有 30 道题目,从基础概念到深度拓展,全方位覆盖章节重点、难点。不管你是想检验自己的学习成果,还是想针对薄弱环节进行强化训练,它都能完美适配!

davidkorhenn的博客 1629

ads设计Doherty功放(1)

Doherty功放的设计

qq_40995480的博客 2万+

btrace 开源!基于 Systrace 高性能 Trace 工具

介绍btrace(又名 RheaTrace) 是抖音基础技术团队自研的一款高性能 Android Trace 工具,它基于 Systrace 实现,并针对 Systrace 不足之处加以改...

字节跳动技术团队官方博客 5887

AUTOSAR进阶图解==>AUTOSAR_SWS_CANNetworkManagement

本文介绍了AUTOSAR架构中的CAN网络管理(CanNm)模块,主要内容包括: 模块概述:CanNm是协调CAN网络节点状态转换的基础组件,采用去中心化设计,通过周期性发送网络管理PDU保持网络活跃状态,支持网络唤醒、睡眠和部分网络功能。 架构设计:CanNm位于AUTOSAR中间层,连接上层通信管理器(ComM)和底层CAN接口(CanIf),包含状态管理、PDU处理和定时器管理等子组件。 状态机设计:定义了三种主要模式(总线睡眠、准备睡眠和网络模式)及其转换逻辑,网络模式又包含重复消息、正常操作和准备

一名留德华的,康德的信徒的,专注于汽车电子领域的博客 2040

Btrace使用入门

目录 1.什么是BTrace 2.BTrace使用场景 BTrace可以做什么? 3.使用限制 4.使用方法及样例 4.1使用方法 4.2使用样例 2.查看哪些方法调用了 System.gc() ,调用栈是怎样的 3.打印某个类中某一方法的入参和返回值 5.拦截方法定义 定位方法 拦截时机 参考资料 1.什么是BTrace BTrace是sun公司推出的一款Java 动态、安全追踪(监控)工具,可以在不用重启的情况下监控系统运行情况,方便的获取程序运行时的数据信息,如方法参数、

772

【异常】Cursor IDE 扩展主机超时错误处理文档The agent execution provider did not respond within 4 seconds.

该错误通常是 Cursor IDE 的临时性问题,不影响项目代码。大多数情况下,重新加载窗口或重启 IDE 即可解决。如果问题持续,可按上述进阶方案排查。重新加载窗口(最快)重启 IDE检查系统资源清理缓存检查扩展更多关于 AI+教育领域的干货,可以关注我的微信公众号第一时间获取哦~

奶爸的编程之路,也就一周冷个三天 2700

QGis二次开发 —— 5、基于qgis开发多种在线地图预览,并加入经纬度坐标显示等(附:源码)

QGis二次开发 —— 5、基于qgis开发多种在线地图预览,并加入经纬度坐标显示等(附:源码

369

java动态跟踪分析工具BTrace实现原理

http://kenwublog.com/btrace-theory-analysis 今天,Team Leader推荐了一个非常棒的动态跟踪分析工具 – BTrace。由于对它的实现原理非常感兴趣,于是花了点时间研究了一下,顺便写点心得。 什么是BTrace? BTrace是SUN Kenai云计算开发平台下的一个开源项目。旨在为java提供安全可靠的动态跟踪分析工具。

Java&&大数据专栏 2485

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

BTrace实现原理的分析

什么是BTrace?BTrace是SUN Kenai云计算开发平台下的一个开源项目。旨在为java提供安全可靠的动态跟踪分析工具。 Btrace基于动态字节码修改技术(Hotswap)来实现运行时java程序的跟踪和替换。(还记得javarebel不?)Btrace的脚本是用纯java编写的,基于一套官方提供的annotation,使跟踪逻辑实现起来异常简单。 实现原理 用一个简单的公式...

soledede 188

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Btrace的基本原理

写在前面的话:Btrace系列是我将平时里学习和使用Btrace的一些经验的总结,拿出来和大家一起交流一下,希望在这个过程中能找寻出自己理解或使用上的错误之处。   一、Btrace的简介:     Btrace是由Kenai 开发的一个开源项目,是一种动态跟踪分析JAVA源代码的工具。它可以用来帮我们做运行时的JAVA程序分析,监控等等操作,当然,它也不是万能的,BTrace也

caomiao2006的专栏 912

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

BTrace原理概述

概述 Btrace是一种安全、动态跟踪分析一个运行中的Java应用程序的工具。 BTrace动态地向目标应用程序的字节码注入追踪代码(字节码追踪),这些追踪字节码追踪代码使用Java语言表达,也就是BTrace的脚本。 组件说明 1. BtraceClient : 为我们使用的b...

chuiliang9247的博客 278

Btrace系列之一:Btrace的基本原理

  写在前面的话:Btrace系列是我将平时里学习和使用Btrace的一些经验的总结,拿出来和大家一起交流一下,希望在这个过程中能找寻出自己理解或使用上的错误之处。   一、Btrace的简介:     Btrace是由Kenai 开发的一个开源项目,是一种动态跟踪分析JAVA源代码的工具。它可以用来帮我们做运行时的JAVA程序分析,监控等等操作,当然,它也不是万能的,BTrace也有一...

victorzhzh 407

初识btrace

此文已由作者易国强授权网易云社区发布。欢迎访问网易云社区,了解更多网易技术产品运营经验。1 btrace简介 BTrace是一个非常不错的java诊断工具。BTrace 中的B表示bytecode,它是在字节码层面上对代码进行trace ,通过在运行中的java类中注入trace代码, 并对运行中的目标程序进行热交换(hotswap)来达到对代码的跟踪 。BTrace应用较为广泛的原因应该是其安...

weixin_33989058的博客 134

java性能调优及问题追踪--Btrace的使用

在生产环境中经常遇到格式各样的问题,如OOM或者莫名其妙的进程死掉。一般情况下是通过修改程序,添加打印日志;然后重新发布程序来完成。然而,这不仅麻烦,而且带来很多不可控的因素。有没有一种方式,在不修改原有运行程序的情况下获取运行时的数据信息呢?如方法参数、返回值、全局变量、堆栈信息等。Btrace就是这样一个工具,它可以在不修改原有代码的情况下动态地追踪java运行程序,通过hotswap技术,动...

hellozhxy的博客 283

BTrace工具简介

BTrace

zhangming1013的专栏 1224

Java程序排查问题利器之Btrace

(一)Btrace的介绍 BTrace是Java的安全可靠的动态跟踪工具。 他的工作原理是通过 instrument + asm 来对正在运行的java程序中的class类进行动态增强,可以在不用重启的情况下监控系统运行情况,方便的获取程序运行时的数据信息,如方法参数、返回值、全局变量和堆栈信息等,并且做到最少的侵入,占用最少的系统资源。 正如上面描述的一些特性,所以btr...

三劫散仙 255

Btrace User Guide

Btrace是为Java定制的安全、动态跟踪工具。Btrace通过动态字节码技术来实现的。Btrace插入跟踪动作到运行的Java程序和类中且热插拔跟踪程序类。     下载地址:http://kenai.com/projects/btrace/downloads/download/releases/release-1.2.2/btrace-bin.zip Btrace 技术     P

Java&&大数据专栏 2176

如何在生产环境使用Btrace进行调试

背景 记得前几天有人问我:在生产环境中可能经常遇到各种问题,你们一般是如何进行调试的? 很惭愧,没有经验。因为平时碰不到生产环境的服务器,定位问题需要各种数据,所以大多数问题的解决方式都是在本地打断点进行调试,或者在测试环境利用输出日志进行调试,这种方式简单粗暴,但过程比较繁琐,需要各种重新发布,重启应用,还不能保证一次就找到问题的根源。直到最近才了解到Btrace这个工具,对于这样一个神器,我觉...

qq_38780765的博客 202

Java应用诊断工具】「BTrace」基本概念和原理的介绍(1)

Java应用诊断工具】「BTrace」基本概念和原理的介绍(1) 系列文章 「BTrace」基本概念和初步介绍(1) 「BTrace」安装介绍和使用原理(2)- 未完成 「BTrace」实战代码进行调试使用(3)- 未完成 「BTrace」运行时异常原因分析(4)- 未完成 背景说明(痛点分析) 线上遇到了问题? 服务上线出问题,想增加打印日志怎么办? 线上怀疑某个接口慢,想打印接口耗时怎么办? 线上某个接口报错,想看看调用的参数和谁调用了怎么办? 线上出错了,想看某个对象的数据怎么办? 线上出错了,

m0_58476806的博客 414
上一篇: Java 探针:Java SE 6 提供的 Attach API
下一篇: Java 字节码 工具 javassist
conquer0715
博客等级 码龄18年 73粉丝 147原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值