MachOView源码(LoadCommands.mm)

MachOView源码(LoadCommands.mm)
加载命令
MachOView源码(LoadCommands.mm)
MachOView源码(LoadCommands.mm)
这里只要分析一个段和段中的一节就好其它都是同样的解析方式,MachO与 ELF 在段与节的分布(这里说的是内存分布,如何加载另说)上没有什么区别。都是文件头后面就是段。段可能包含很多节。只是它增加了一项加载命令。而 ELF 分俩种视图。
// LoadCommands.h

/*
 *  LoadCommands.h
 *  MachOView
 *
 *  Created by psaghelyi on 20/07/2010.
 *
 */

#import "MachOLayout.h"

//类扩展
@interface MachOLayout (LoadCommands)
//获取加载命令的名称
- (NSString *)getNameForCommand:(uint32_t)cmd;
//创建一个用于窗口显示的加载命令项
-(MVNode *)createLoadCommandNode:(MVNode *)parent
                         caption:(NSString *)caption
                        location:(uint32_t)location
                          length:(uint32_t)length
                         command:(uint32_t)command;

@end

// LoadCommands.mm

/*
 *  LoadCommands.mm
 *  MachOView
 *
 *  Created by psaghelyi on 20/07/2010.
 *
 */

#include <string>
#include <vector>
#include <set>
#include <map>

#import "Common.h"
#import "LoadCommands.h"
#import "ReadWrite.h"
#import "DataController.h"

using namespace std;

//============================================================================
@implementation MachOLayout (LoadCommands)

//-----------------------------------------------------------------------------
//获取加载命令名称
- (NSString *)getNameForCommand:(uint32_t)cmd
{
  switch(cmd)
  {
    default:                      return @"???";
    case LC_SEGMENT:              return @"LC_SEGMENT";             
    case LC_SYMTAB:               return @"LC_SYMTAB";               
    case LC_SYMSEG:               return @"LC_SYMSEG";              
    case LC_THREAD:               return @"LC_THREAD";              
    case LC_UNIXTHREAD:           return @"LC_UNIXTHREAD";          
    case LC_LOADFVMLIB:           return @"LC_LOADFVMLIB";          
    case LC_IDFVMLIB:             return @"LC_IDFVMLIB";            
    case LC_IDENT:                return @"LC_IDENT";               
    case LC_FVMFILE:              return @"LC_FVMFILE";             
    case LC_PREPAGE:              return @"LC_PREPAGE";             
    case LC_DYSYMTAB:             return @"LC_DYSYMTAB";            
    case LC_LOAD_DYLIB:           return @"LC_LOAD_DYLIB";          
    case LC_ID_DYLIB:             return @"LC_ID_DYLIB";            
    case LC_LOAD_DYLINKER:        return @"LC_LOAD_DYLINKER";       
    case LC_ID_DYLINKER:          return @"LC_ID_DYLINKER";         
    case LC_PREBOUND_DYLIB:       return @"LC_PREBOUND_DYLIB";      
    case LC_ROUTINES:             return @"LC_ROUTINES";            
    case LC_SUB_FRAMEWORK:        return @"LC_SUB_FRAMEWORK";       
    case LC_SUB_UMBRELLA:         return @"LC_SUB_UMBRELLA";        
    case LC_SUB_CLIENT:           return @"LC_SUB_CLIENT";          
    case LC_SUB_LIBRARY:          return @"LC_SUB_LIBRARY";         
    case LC_TWOLEVEL_HINTS:       return @"LC_TWOLEVEL_HINTS";      
    case LC_PREBIND_CKSUM:        return @"LC_PREBIND_CKSUM";       
    case LC_LOAD_WEAK_DYLIB:      return @"LC_LOAD_WEAK_DYLIB";     
    case LC_SEGMENT_64:           return @"LC_SEGMENT_64";          
    case LC_ROUTINES_64:          return @"LC_ROUTINES_64";         
    case LC_UUID:                 return @"LC_UUID";                
    case LC_RPATH:                return @"LC_RPATH";               
    case LC_CODE_SIGNATURE:       return @"LC_CODE_SIGNATURE";      
    case LC_SEGMENT_SPLIT_INFO:   return @"LC_SEGMENT_SPLIT_INFO";  
    case LC_REEXPORT_DYLIB:       return @"LC_REEXPORT_DYLIB";      
    case LC_LAZY_LOAD_DYLIB:      return @"LC_LAZY_LOAD_DYLIB";     
    case LC_ENCRYPTION_INFO:      return @"LC_ENCRYPTION_INFO";     
    case LC_ENCRYPTION_INFO_64:   return @"LC_ENCRYPTION_INFO_64";
    case LC_DYLD_INFO:            return @"LC_DYLD_INFO";           
    case LC_DYLD_INFO_ONLY:       return @"LC_DYLD_INFO_ONLY";      
    case LC_LOAD_UPWARD_DYLIB:    return @"LC_LOAD_UPWARD_DYLIB";
    case LC_VERSION_MIN_MACOSX:   return @"LC_VERSION_MIN_MACOSX";
    case LC_VERSION_MIN_IPHONEOS: return @"LC_VERSION_MIN_IPHONEOS";
    case LC_FUNCTION_STARTS:      return @"LC_FUNCTION_STARTS";
    case LC_DYLD_ENVIRONMENT:     return @"LC_DYLD_ENVIRONMENT";
    case LC_MAIN:                 return @"LC_MAIN";
    case LC_DATA_IN_CODE:         return @"LC_DATA_IN_CODE";
    case LC_SOURCE_VERSION:       return @"LC_SOURCE_VERSION";
    case LC_DYLIB_CODE_SIGN_DRS:  return @"LC_DYLIB_CODE_SIGN_DRS";
    case LC_LINKER_OPTION:        return @"LC_LINKER_OPTION";
    case LC_LINKER_OPTIMIZATION_HINT: return @"LC_LINKER_OPTIMIZATION_HINT";
  }
}

//-----------------------------------------------------------------------------
//struct segment_command_64 { /* for 64-bit architectures */
//    uint32_t    cmd;             命令加载类型
//    uint32_t    cmdsize;         命令加载的大小
//    char        segname[16];     16 字节的段名
//    uint64_t    vmaddr;          段的虚拟内存地址 VA
//    uint64_t    vmsize;          段的在虚拟内存有大小
//    uint64_t    fileoff;         段在文件中的偏移
//    uint64_t    filesize;        段在文件中的大小
//    vm_prot_t    maxprot;        段页要所需要的最高内存保护(4=r,2=w,1=x)
//    vm_prot_t    initprot;       段页面初始化的内存保护
//    uint32_t    nsects;          段中包含 section 的数量
//    uint32_t    flags;           其它杂项标志位
//};
//加载命令的显示方式
- (MVNode *)createLCSegmentNode:(MVNode *)parent
                     caption:(NSString *)caption
                     //数据的地址
                     location:(uint32_t)location
                     segment_command:(struct segment_command const *)segment_command
{
    //DataController.h中的一个结构
//    struct MVNodeSaver
//    {
//        MVNodeSaver();
//        ~MVNodeSaver();
//
//        void setNode(MVNode * node) { m_node = node; }
//
//    private:
//        MVNodeSaver(MVNodeSaver const &);
//        MVNodeSaver & operator=(MVNodeSaver const &);
//
//        MVNode * __weak m_node;
//    };
  MVNodeSaver nodeSaver;
    //DataController.h中的一个类
    //初始化一个MVNode
  MVNode * node = [parent insertChildWithDetails:caption location:location length:segment_command->cmdsize saver:nodeSaver]; 
  //创建一个NSRange
  NSRange range = NSMakeRange(location,0);
  NSString * lastReadHex;
  //获取 hex 位置为range.location
    获取十六进制 lastReadHex缓冲区
//- (uint32_t)read_uint32:(NSRange &)range lastReadHex:(NSString **)lastReadHex
//{
//    //缓冲区
//  uint32_t buffer;
//    //创建一个range
//  range = NSMakeRange(NSMaxRange(range),sizeof(uint32_t));
//    //可修改的NSMutableData * fileData;
//    //获取指定范围的数据
//  [fileData getBytes:&buffer range:range];
//    //判断lastReadHex是否可以用
//  if (lastReadHex)
//      //转十六进制
//      *lastReadHex = [NSString stringWithFormat:@"%.8X",buffer];
//  //可修改的NSMutableData * realData;
//    //重新读
//  [realData getBytes:&buffer range:range];
//    //返回数据
//  return buffer;
//}

  [dataController read_uint32:range lastReadHex:&lastReadHex];
    // MVTable * details
    //offsetStr = col0;
    //dataStr = col1;
    //descriptionStr = col2;
    //valueStr = col3;
    //右边表格追加一行
  [node.details appendRow:
      //地址(文件偏移地址或者vm内存地址)
     [NSString stringWithFormat:@"%.8lX", range.location]
      //Date数据
     :lastReadHex
      //描述
     :@"Command"
      //加载命令的值(名称)
     :[self getNameForCommand:segment_command->cmd]];
   //设置加载命令的结构体(那个俩字段的结构体)颜色为greenColor
  //MVCellColorAttributeName = @"MVCellColorAttribute";
  [node.details setAttributes:MVCellColorAttributeName,[NSColor greenColor],nil];
  //重新获取 hex
  [dataController read_uint32:range lastReadHex:&lastReadHex];
   //显示Command Size字段
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Command Size"
                         :[NSString stringWithFormat:@"%u", segment_command->cmdsize]];
  //显示颜色
  [node.details setAttributes:MVCellColorAttributeName,[NSColor greenColor],
                              MVUnderlineAttributeName,@"YES",nil];
  //重新取 hex
  [dataController read_string:range fixlen:16 lastReadHex:&lastReadHex];
  //显示字段Segment Name
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Segment Name"
                         :[NSString stringWithFormat:@"%s", string(segment_command->segname,16).c_str()]];
  //VM Address
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"VM Address"
                         :[NSString stringWithFormat:@"0x%X", segment_command->vmaddr]];
  //VM Size
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"VM Size"
                         :[NSString stringWithFormat:@"%u", segment_command->vmsize]];
  //File Offset
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"File Offset"
                         :[NSString stringWithFormat:@"%u", segment_command->fileoff]];
  //File Size
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"File Size"
                         :[NSString stringWithFormat:@"%u", segment_command->filesize]];
  //Maximum VM Protection
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Maximum VM Protection"
                         :@""];
  //段页面不能超过的最高内存保护 注意这里是八进制
    //无最高保护
  if (segment_command->maxprot == VM_PROT_NONE)    [node.details appendRow:@"":@"":@"00000000":@"VM_PROT_NONE"];
    //可执行
  if (segment_command->maxprot & VM_PROT_READ)     [node.details appendRow:@"":@"":@"00000001":@"VM_PROT_READ"];
    //可写
  if (segment_command->maxprot & VM_PROT_WRITE)    [node.details appendRow:@"":@"":@"00000002":@"VM_PROT_WRITE"];
    //可读
  if (segment_command->maxprot & VM_PROT_EXECUTE)  [node.details appendRow:@"":@"":@"00000004":@"VM_PROT_EXECUTE"];
  //重新读
  [dataController read_uint32:range lastReadHex:&lastReadHex];
    //段内最初始的内存保护
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Initial VM Protection"
                         :@""];
  //0 1 2 4
  if (segment_command->initprot == VM_PROT_NONE)   [node.details appendRow:@"":@"":@"00000000":@"VM_PROT_NONE"];
  if (segment_command->initprot & VM_PROT_READ)    [node.details appendRow:@"":@"":@"00000001":@"VM_PROT_READ"];
  if (segment_command->initprot & VM_PROT_WRITE)   [node.details appendRow:@"":@"":@"00000002":@"VM_PROT_WRITE"];
  if (segment_command->initprot & VM_PROT_EXECUTE) [node.details appendRow:@"":@"":@"00000004":@"VM_PROT_EXECUTE"];
  //段中节的数量
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Number of Sections"
                         :[NSString stringWithFormat:@"%u", segment_command->nsects]];
  //其它标志
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Flags"
                         :@""];
  //该段的文件内容是VM空间的高部分,低部分为零填充(在核心文件用于堆栈)
  if (segment_command->flags & SG_HIGHVM)              [node.details appendRow:@"":@"":@"00000001":@"SG_HIGHVM"];
    //此段是由固定的VM library分配的VM,用于链接编辑器中的重叠检查
  if (segment_command->flags & SG_FVMLIB)              [node.details appendRow:@"":@"":@"00000002":@"SG_FVMLIB"];
    //无重定位
  if (segment_command->flags & SG_NORELOC)             [node.details appendRow:@"":@"":@"00000004":@"SG_NORELOC"];
    //这个部分是受保护的,如果段在文件偏移0处开始,则段的第一页不受保护,该段的所有其他页都受到保护。
  if (segment_command->flags & SG_PROTECTED_VERSION_1) [node.details appendRow:@"":@"":@"00000008":@"SG_PROTECTED_VERSION_1"];
  //返回加载命令 显示结点
  return node;
}
//struct section_64 { /* for 64-bit architectures */
//    char        sectname[16];   节名
//    char        segname[16];    节的所属段名
//    uint64_t    addr;           节的内存中的起始地址
//    uint64_t    size;           节的大小
//    uint32_t    offset;         节的文件偏移
//    uint32_t    align;          节的对齐粒度
//    uint32_t    reloff;         重定位入口的文件偏移
//    uint32_t    nreloc;         需要重定位的入口数量
//    uint32_t    flags;          其它标志 包含包含节的type和attributes
//    uint32_t    reserved1;      记录了它们在dysymtab_command结构中indirectsymoff字段所对应的起始项
//对于 symbol pointer sections 和 stubs sections 来说,reserved1 表示 indirect table 数组的 index。用来索引 section's entries
//    uint32_t    reserved2;      记录了条目的大小或者个数
//    uint32_t    reserved3;    /* reserved */
//};
//-----------------------------------------------------------------------------
//节
- (MVNode *)createSectionNode:(MVNode *)parent
                    caption:(NSString *)caption
                   location:(uint32_t)location
                    section:(struct section const *)section
{
  MVNodeSaver nodeSaver;
  MVNode * node = [parent insertChildWithDetails:caption location:location length:sizeof(struct section) saver:nodeSaver]; 

  NSRange range = NSMakeRange(location,0);
  NSString * lastReadHex;

  [dataController read_string:range fixlen:16 lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Section Name"
                         :[NSString stringWithFormat:@"%s", string(section->sectname,16).c_str()]];

  [dataController read_string:range fixlen:16 lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Segment Name"
                         :[NSString stringWithFormat:@"%s", string(section->segname,16).c_str()]];

  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Address"
                         :[NSString stringWithFormat:@"0x%X", section->addr]];

  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Size"
                         :[NSString stringWithFormat:@"%u", section->size]];

  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Offset"
                         :[NSString stringWithFormat:@"%u", section->offset]];

  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Alignment"
                         :[NSString stringWithFormat:@"%u", (1 << section->align)]];

  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Relocations Offset"
                         :[NSString stringWithFormat:@"%u", section->reloff]];

  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Number of Relocations"
                         :[NSString stringWithFormat:@"%u", section->nreloc]];

  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :@"Flags"
                         :@""];
  //节类型
  switch (section->flags & SECTION_TYPE)
  {
    case S_REGULAR:                             [node.details appendRow:@"":@"":@"00000000":@"S_REGULAR"]; break;
    case S_ZEROFILL:                            [node.details appendRow:@"":@"":@"00000001":@"S_ZEROFILL"]; break;
    case S_CSTRING_LITERALS:                    [node.details appendRow:@"":@"":@"00000002":@"S_CSTRING_LITERALS"]; break;
    case S_4BYTE_LITERALS:                      [node.details appendRow:@"":@"":@"00000003":@"S_4BYTE_LITERALS"]; break;
    case S_8BYTE_LITERALS:                      [node.details appendRow:@"":@"":@"00000004":@"S_8BYTE_LITERALS"]; break;
    case S_LITERAL_POINTERS:                    [node.details appendRow:@"":@"":@"00000005":@"S_LITERAL_POINTERS"]; break;
    case S_NON_LAZY_SYMBOL_POINTERS:            [node.details appendRow:@"":@"":@"00000006":@"S_NON_LAZY_SYMBOL_POINTERS"]; break;
    case S_LAZY_SYMBOL_POINTERS:                [node.details appendRow:@"":@"":@"00000007":@"S_LAZY_SYMBOL_POINTERS"]; break;
    case S_SYMBOL_STUBS:                        [node.details appendRow:@"":@"":@"00000008":@"S_SYMBOL_STUBS"]; break;
    case S_MOD_INIT_FUNC_POINTERS:              [node.details appendRow:@"":@"":@"00000009":@"S_MOD_INIT_FUNC_POINTERS"]; break;
    case S_MOD_TERM_FUNC_POINTERS:              [node.details appendRow:@"":@"":@"0000000A":@"S_MOD_TERM_FUNC_POINTERS"]; break;
    case S_COALESCED:                           [node.details appendRow:@"":@"":@"0000000B":@"S_COALESCED"]; break;
    case S_GB_ZEROFILL:                         [node.details appendRow:@"":@"":@"0000000C":@"S_GB_ZEROFILL"]; break;
    case S_INTERPOSING:                         [node.details appendRow:@"":@"":@"0000000D":@"S_INTERPOSING"]; break;
    case S_16BYTE_LITERALS:                     [node.details appendRow:@"":@"":@"0000000E":@"S_16BYTE_LITERALS"]; break;
    case S_DTRACE_DOF:                          [node.details appendRow:@"":@"":@"0000000F":@"S_DTRACE_DOF"]; break;
    case S_LAZY_DYLIB_SYMBOL_POINTERS:          [node.details appendRow:@"":@"":@"00000010":@"S_LAZY_DYLIB_SYMBOL_POINTERS"]; break;
    case S_THREAD_LOCAL_REGULAR:                [node.details appendRow:@"":@"":@"00000011":@"S_THREAD_LOCAL_REGULAR"]; break;
    case S_THREAD_LOCAL_ZEROFILL:               [node.details appendRow:@"":@"":@"00000012":@"S_THREAD_LOCAL_ZEROFILL"]; break;
    case S_THREAD_LOCAL_VARIABLES:              [node.details appendRow:@"":@"":@"00000013":@"S_THREAD_LOCAL_VARIABLES"]; break;
    case S_THREAD_LOCAL_VARIABLE_POINTERS:      [node.details appendRow:@"":@"":@"00000014":@"S_THREAD_LOCAL_VARIABLE_POINTERS"]; break;
    case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS: [node.details appendRow:@"":@"":@"00000015":@"S_THREAD_LOCAL_INIT_FUNCTION_POINTERS"]; break;
  }

  if (section->flags & S_ATTR_PURE_INSTRUCTIONS)   [node.details appendRow:@"":@"":@"80000000":@"S_ATTR_PURE_INSTRUCTIONS"];
  if (section->flags & S_ATTR_NO_TOC)              [node.details appendRow:@"":@"":@"40000000":@"S_ATTR_NO_TOC"];
  if (section->flags & S_ATTR_STRIP_STATIC_SYMS)   [node.details appendRow:@"":@"":@"20000000":@"S_ATTR_STRIP_STATIC_SYMS"];
  if (section->flags & S_ATTR_NO_DEAD_STRIP)       [node.details appendRow:@"":@"":@"10000000":@"S_ATTR_NO_DEAD_STRIP"];
  if (section->flags & S_ATTR_LIVE_SUPPORT)        [node.details appendRow:@"":@"":@"08000000":@"S_ATTR_LIVE_SUPPORT"];
  if (section->flags & S_ATTR_SELF_MODIFYING_CODE) [node.details appendRow:@"":@"":@"04000000":@"S_ATTR_SELF_MODIFYING_CODE"];
  if (section->flags & S_ATTR_DEBUG)               [node.details appendRow:@"":@"":@"02000000":@"S_ATTR_DEBUG"];
  if (section->flags & S_ATTR_SOME_INSTRUCTIONS)   [node.details appendRow:@"":@"":@"00000400":@"S_ATTR_SOME_INSTRUCTIONS"];
  if (section->flags & S_ATTR_EXT_RELOC)           [node.details appendRow:@"":@"":@"00000200":@"S_ATTR_EXT_RELOC"];
  if (section->flags & S_ATTR_LOC_RELOC)           [node.details appendRow:@"":@"":@"00000100":@"S_ATTR_LOC_RELOC"];
  //符号索引
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :(section->flags & SECTION_TYPE) == S_SYMBOL_STUBS ||
                          (section->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS ||
                          (section->flags & SECTION_TYPE) == S_LAZY_DYLIB_SYMBOL_POINTERS ||
                          (section->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS ? @"Indirect Sym Index" : @"Reserved1"
                         :[NSString stringWithFormat:@"%u", section->reserved1]];
  //字号大小或者数量
  [dataController read_uint32:range lastReadHex:&lastReadHex];
  [node.details appendRow:[NSString stringWithFormat:@"%.8lX", range.location]
                         :lastReadHex
                         :(section->flags & SECTION_TYPE) == S_SYMBOL_STUBS ? @"Size of Stubs" : @"Reserved2"
                         :[NSString stringWithFormat:@"%u", section->reserved2]];
  return node;
}

转载于:https://blog.51cto.com/haidragon/2142524

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值