乐于分享
好东西不私藏

跨平台系统信息获取库libsigar源码分析系列(七)

跨平台系统信息获取库libsigar源码分析系列(七)

官网:http://securitytech.cc

源码分析mettle后门工具学习 所使用的依赖库

官网:http://securitytech.cc

libsigar Solaris 平台源码分析

概述

libsigar 在 Solaris 平台的实现主要通过 kstat 库、 MIB2 流接口和 libproc 库获取系统信息。Solaris 是 Sun Microsystems 开发的 UNIX 操作系统,以其强大的性能和可靠性著称。

核心架构

sigart 结构体 (sigaros.h:131-185)

  1. structsigar_t{
  2.     SIGAR_T_BASE;
  3. int solaris_version;// Solaris 版本号
  4. int use_ucb_ps;// 是否使用 UCB ps
  5. int joyent;// Joyent SmartOS 标志
  6. zoneid_t zoneid;// Zone ID
  7. char*zonenm;// Zone 名称
  8. char*zonenm_short;// Zone 短名称
  9. uint64_t cpu_prev_time, cpu_total;
  10. kstat_ctl_t*kc;// kstat 控制句柄
  11. // kstat 查找结果
  12. struct{
  13. kstat_t**cpu;// CPU kstat 数组
  14. kstat_t**cpu_info;// CPU 信息 kstat 数组
  15. processorid_t*cpuid;// CPU ID 数组
  16. unsignedint lcpu;// CPU 数组大小
  17. kstat_t*system;// 系统 kstat
  18. kstat_t*syspages;// 系统页面 kstat
  19. kstat_t*mempages;// 内存页面 kstat
  20. } ks;
  21. // kstat 偏移量
  22. struct{
  23. int system[KSTAT_SYSTEM_MAX];
  24. int mempages[KSTAT_MEMPAGES_MAX];
  25. int syspages[KSTAT_SYSPAGES_MAX];
  26. } koffsets;
  27. int pagesize;
  28. time_t last_getprocs;
  29. sigar_pid_t last_pid;
  30. psinfo_t*pinfo;// 进程信息缓冲区
  31. sigar_cpu_list_t cpulist;
  32. // libproc.so 接口
  33. void*plib;
  34. proc_grab_func_t pgrab;
  35. proc_free_func_t pfree;
  36. proc_create_agent_func_t pcreate_agent;
  37. proc_destroy_agent_func_t pdestroy_agent;
  38. proc_objname_func_t pobjname;
  39. proc_dirname_func_t pdirname;
  40. proc_exename_func_t pexename;
  41. proc_fstat64_func_t pfstat64;
  42. proc_getsockopt_func_t pgetsockopt;
  43. proc_getsockname_func_t pgetsockname;
  44. sigar_cache_t*pargs;
  45. solaris_mib2_t mib2;// MIB2 数据
  46. };

kstat 偏移量枚举

  1. // 系统统计
  2. typedefenum{
  3.     KSTAT_SYSTEM_BOOT_TIME,
  4.     KSTAT_SYSTEM_LOADAVG_1,
  5.     KSTAT_SYSTEM_LOADAVG_2,
  6.     KSTAT_SYSTEM_LOADAVG_3,
  7.     KSTAT_SYSTEM_MAX
  8. } kstat_system_off_e;
  9. // 内存页面统计
  10. typedefenum{
  11.     KSTAT_MEMPAGES_ANON,
  12.     KSTAT_MEMPAGES_EXEC,
  13.     KSTAT_MEMPAGES_VNODE,
  14.     KSTAT_MEMPAGES_MAX
  15. } kstat_mempages_off_e;
  16. // 系统页面统计
  17. typedefenum{
  18.     KSTAT_SYSPAGES_FREE,
  19.     KSTAT_SYSPAGES_MAX
  20. } kstat_syspages_off_e;

kstat 访问宏 (sigar_os.h:193-222)

  1. #define kSTAT_exists(v, type) \
  2. (sigar->koffsets.type[v]!=-2)
  3. #define kSTAT_ptr(v, type) \
  4. ((kstat_named_t*)ksp->ks_data + sigar->koffsets.type[v])
  5. #define kSTAT_uint(v, type) \
  6. (kSTAT_exists(v, type)? kSTAT_ptr(v, type)->value.KSTAT_UINT :0)
  7. #define kSYSTEM(v) kSTAT_ui32(v, system)
  8. #define kMEMPAGES(v) kSTAT_uint(v, mempages)
  9. #define kSYSPAGES(v) kSTAT_uint(v, syspages)

核心技术

1. kstat 库

kstat 是 Solaris 提供的内核统计数据访问接口。

初始化 (solaris_sigar.c:69-164)

  1. int sigar_os_open(sigar_t**sigar)
  2. {
  3. kstat_ctl_t*kc;
  4. kstat_t*ksp;
  5. struct utsname name;
  6. char*ptr;
  7. char zonenm[256];
  8. // 1. 打开 kstat
  9. if((kc = kstat_open())== NULL){
  10. *sig = NULL;
  11. return errno;
  12. }
  13. // 2. 分配并初始化 sigar 结构
  14. if((*sig = sigar = calloc(1,sizeof(*sigar)))== NULL){
  15. return ENOMEM;
  16. }
  17. // 3. 获取 Zone 信息
  18.     sigar->zoneid = getzoneid();
  19. if(sigar->zoneid <0){
  20.         sigar->zoneid =0;
  21.         sigar->zonenm = strdup("unknown");
  22.         sigar->zonenm_short = strdup("unknown");
  23. }else{
  24.         getzonenamebyid(sigar->zoneid, zonenm,sizeof(zonenm));
  25.         sigar->zonenm = strdup(zonenm);
  26.         ptr = strchr(zonenm,'-');
  27. if(ptr)*ptr =0;
  28.         ptr = strchr(zonenm,':');
  29. if(ptr)*ptr =0;
  30.         sigar->zonenm_short = strdup(zonenm);
  31. }
  32. // 4. 获取 Solaris 版本
  33.     uname(&name);
  34. if((ptr = strchr(name.release,'.'))){
  35.         sigar->solaris_version = atoi(ptr +1);
  36. }
  37. else{
  38.         sigar->solaris_version =6;
  39. }
  40. // 5. 检测 Joyent SmartOS
  41.     sigar->joyent =!strncmp(name.version,"joyent",6);
  42. // 6. 检测 UCB ps
  43. if((ptr = getenv("SIGAR_USE_UCB_PS"))){
  44.         sigar->use_ucb_ps = strEQ(ptr,"true");
  45. }
  46. if(sigar->use_ucb_ps){
  47. if(access(SIGAR_USR_UCB_PS, X_OK)==-1){
  48.             sigar->use_ucb_ps =0;
  49. }
  50. else{
  51.             sigar->use_ucb_ps =1;
  52. }
  53. }
  54. // 7. 计算页大小位移
  55.     sigar->pagesize =0;
  56.     i = sysconf(_SC_PAGESIZE);
  57. while((>>=1)>0){
  58.         sigar->pagesize++;
  59. }
  60.     sigar->ticks = sysconf(_SC_CLK_TCK);
  61.     sigar->kc = kc;
  62. // 8. 获取 kstat
  63. if((status = sigar_get_kstats(sigar))!= SIGAR_OK){
  64.         fprintf(stderr,"status=%d\n", status);
  65. }
  66. // 9. 初始化偏移量并获取启动时间
  67. if((ksp = sigar->ks.system)&&
  68. (kstat_read(kc, ksp, NULL)>=0))
  69. {
  70.         sigar_koffsets_init_system(sigar, ksp);
  71.         sigar->boot_time = kSYSTEM(KSTAT_SYSTEM_BOOT_TIME);
  72. }
  73. return SIGAR_OK;
  74. }

kstat 查找 (solaris_sigar.c:51-67)

  1. statickstat_t*
  2. kstat_next(kstat_t*ksp,char*ks_module,int ks_instance,char*ks_name)
  3. {
  4. if(ksp){
  5.         ksp = ksp->ks_next;
  6. }
  7. for(; ksp; ksp = ksp->ks_next){
  8. if((ks_module == NULL ||
  9.              strcmp(ksp->ks_module, ks_module)==0)&&
  10. (ks_instance ==-1|| ksp->ks_instance == ks_instance)&&
  11. (ks_name == NULL || strcmp(ksp->ks_name, ks_name)==0))
  12. return ksp;
  13. }
  14.     errno = ENOENT;
  15. return NULL;
  16. }

2. MIB2 流接口

Solaris 使用 MIB2 (Management Information Base 2) 流接口获取网络统计信息。

MIB2 访问 (get_mib2.c:106-262)

  1. int
  2. get_mib2(solaris_mib2_t*mib2,
  3. struct opthdr **opt,
  4. char**data,
  5. int*datalen)
  6. {
  7. struct strbuf d;
  8. int err;
  9. int f;
  10. int rc;
  11. // 如果 MIB2 访问未打开,打开它
  12. if(mib2->sd <0){
  13. if((err = open_mib2(mib2))){
  14. return(err);
  15. }
  16. // 设置消息请求和选项
  17.         mib2->req =(struct T_optmgmt_req *)mib2->smb;
  18.         mib2->op =(struct opthdr *)&mib2->smb[sizeof(struct T_optmgmt_req)];
  19.         mib2->req->PRIM_type = T_OPTMGMT_REQ;
  20.         mib2->req->OPT_offset =sizeof(struct T_optmgmt_req);
  21.         mib2->req->OPT_length =sizeof(struct opthdr);
  22.         mib2->req->MGMT_flags = MI_T_CURRENT;
  23.         mib2->op->level = MIB2_IP;
  24.         mib2->op->name = mib2->op->len =0;
  25. // 发送消息
  26. if(putmsg(mib2->sd,&mib2->ctlbuf,(struct strbuf *)NULL,0)==-1){
  27. return(GET_MIB2_ERR_PUTMSG);
  28. }
  29. }
  30. // 获取下一个回复消息
  31.     f =0;
  32. if((rc = getmsg(mib2->sd,&mib2->ctlbuf, NULL,&f))<0){
  33. return(GET_MIB2_ERR_GETMSGR);
  34. }
  35. // 检查数据结束
  36. if(rc ==0
  37. && mib2->ctlbuf.len >=sizeof(struct T_optmgmt_ack)
  38. && mib2->op_ack->PRIM_type == T_OPTMGMT_ACK
  39. && mib2->op_ack->MGMT_flags == T_SUCCESS
  40. && mib2->op->len ==0)
  41. {
  42.         err = close_mib2(mib2);
  43. if(err){
  44. return(err);
  45. }
  46. return(GET_MIB2_EOD);
  47. }
  48. // 分配数据缓冲区
  49. if(mib2->op->len >= mib2->db_len){
  50.         mib2->db_len = mib2->op->len;
  51. if(mib2->db == NULL){
  52.             mib2->db =(char*)malloc(mib2->db_len);
  53. }
  54. else{
  55.             mib2->db =(char*)realloc(mib2->db, mib2->db_len);
  56. }
  57. }
  58. // 获取数据部分
  59.     d.maxlen = mib2->op->len;
  60.     d.buf = mib2->db;
  61.     d.len =0;
  62.     f =0;
  63. if((rc = getmsg(mib2->sd, NULL,&d,&f))<0){
  64. return(GET_MIB2_ERR_GETMSGD);
  65. }
  66. *opt = mib2->op;
  67. *data = mib2->db;
  68. *datalen = d.len;
  69. return(GET_MIB2_OK);
  70. }

MIB2 打开 (get_mib2.c:275-321)

  1. int
  2. open_mib2(solaris_mib2_t*mib2)
  3. {
  4. if(mib2->sd >=0){
  5. return(GET_MIB2_ERR_OPEN);
  6. }
  7. // 打开 ARP 流设备,推送 TCP 和 UDP
  8. if((mib2->sd = open(GET_MIB2_ARPDEV, O_RDWR,0600))<0){
  9. return(GET_MIB2_ERR_ARPOPEN);
  10. }
  11. if(ioctl(mib2->sd, I_PUSH, GET_MIB2_TCPSTREAM)==-1){
  12. return(GET_MIB2_ERR_TCPPUSH);
  13. }
  14. if(ioctl(mib2->sd, I_PUSH, GET_MIB2_UDPSTREAM)==-1){
  15. return(GET_MIB2_ERR_UDPPUSH);
  16. }
  17. // 分配流消息缓冲区
  18.     mib2->smb_len =sizeof(struct opthdr)+sizeof(struct T_optmgmt_req);
  19. if(mib2->smb_len <(sizeof(struct opthdr)+sizeof(struct T_optmgmt_ack))){
  20.         mib2->smb_len =sizeof(struct opthdr)+sizeof(struct T_optmgmt_ack);
  21. }
  22. if(mib2->smb_len <sizeof(struct T_error_ack)){
  23.         mib2->smb_len =sizeof(struct T_error_ack);
  24. }
  25. if((mib2->smb =(char*)malloc(mib2->smb_len))== NULL){
  26. return(GET_MIB2_ERR_NOSPC);
  27. }
  28. return(GET_MIB2_OK);
  29. }

3. libproc 库

Solaris 的 libproc 库提供了进程查询和控制功能。

动态加载 libproc

  1. sigar->plib = dlopen("libproc.so", RTLD_LAZY);
  2. // 加载函数指针
  3. sigar->pgrab = dlsym(sigar->plib,"Pgrab");
  4. sigar->pfree = dlsym(sigar->plib,"Pfree");
  5. sigar->pcreate_agent = dlsym(sigar->plib,"Pcreate_agent");
  6. sigar->pdestroy_agent = dlsym(sigar->plib,"Pdestroy_agent");
  7. // ... 其他函数

4. Zone 支持

Solaris Zones 是操作系统级虚拟化技术。

  1. // 获取 Zone ID
  2. sigar->zoneid = getzoneid();
  3. // 获取 Zone 名称
  4. getzonenamebyid(sigar->zoneid, zonenm,sizeof(zonenm));

核心功能模块

1. 内存监控

内存信息获取 (solaris_sigar.c:272-332)

  1. int sigar_mem_get(sigar_t*sigar,sigar_mem_t*mem)
  2. {
  3. kstat_ctl_t*kc = sigar->kc;
  4. kstat_t*ksp;
  5. sigar_uint64_t kern =0;
  6.     SIGAR_ZERO(mem);
  7. // 获取物理内存总量
  8.     mem->total = sysconf(_SC_PHYS_PAGES);
  9.     mem->total <<= sigar->pagesize;
  10. if(sigar_kstat_update(sigar)==-1){
  11. return errno;
  12. }
  13. // Zone 感知处理
  14. if(getzoneid()!= GLOBAL_ZONEID){
  15. return zone_mem_get(sigar, mem);
  16. }
  17. // 获取空闲内存
  18. if((ksp = sigar->ks.syspages)&& kstat_read(kc, ksp, NULL)>=0){
  19.         sigar_koffsets_init_syspages(sigar, ksp);
  20.         mem->free = kSYSPAGES(KSTAT_SYSPAGES_FREE);
  21.         mem->free <<= sigar->pagesize;
  22.         mem->used = mem->total - mem->free;
  23. }
  24. // 获取内存页面统计
  25. if((ksp = sigar->ks.mempages)&& kstat_read(kc, ksp, NULL)>=0){
  26.         sigar_koffsets_init_mempages(sigar, ksp);
  27. }
  28. // ZFS ARC 缓存
  29. if((ksp = kstat_lookup(sigar->kc,"zfs",0,"arcstats"))&&
  30. (kstat_read(sigar->kc, ksp, NULL)!=-1))
  31. {
  32. kstat_named_t*kn;
  33. if((kn =(kstat_named_t*)kstat_data_lookup(ksp,"size"))){
  34.             kern = kn->value.i64;
  35. }
  36. if((kn =(kstat_named_t*)kstat_data_lookup(ksp,"c_min"))){
  37. // c_min 不能回收
  38. if(kern > kn->value.i64){
  39.                 kern -= kn->value.i64;
  40. }
  41. }
  42. }
  43.     mem->actual_free = mem->free + kern;
  44.     mem->actual_used = mem->used - kern;
  45.     sigar_mem_calc_ram(sigar, mem);
  46. return SIGAR_OK;
  47. }

Zone 内存 (solaris_sigar.c:223-270)

  1. staticint zone_mem_get(sigar_t*sigar,sigar_mem_t*mem)
  2. {
  3. kstat_t*ksp;
  4. sigar_vmusage64_t result;
  5. size_t nres =1;
  6. sigar_uint64_t used =0, cap =0;
  7. int ret;
  8. // 尝试使用 memory_cap kstat (SmartOS 特有)
  9. if((ksp = kstat_lookup(sigar->kc,"memory_cap",-1, NULL))&&
  10. (kstat_read(sigar->kc, ksp, NULL)!=-1))
  11. {
  12. kstat_named_t*kn;
  13. if((kn =(kstat_named_t*)kstat_data_lookup(ksp,"rss"))){
  14.             used = kn->value.i64;
  15. }
  16. if((kn =(kstat_named_t*)kstat_data_lookup(ksp,"physcap"))){
  17.             cap = kn->value.i64;
  18. }
  19. }
  20. else{
  21. // 回退到 getvmusage()
  22.         ret = sysinfo(SI_ARCHITECTURE_64, path,sizeof(path));
  23. if(ret <0){
  24. return-1;
  25. }
  26. if(getvmusage(VMUSAGE_ZONE, VMUSAGE_INTERVAL,(vmusage_t*)&result,
  27. &nres)!=0|| nres !=1){
  28. return-1;
  29. }
  30.         used = result.vmu_rss_all;
  31.         cap = mem->total;
  32. }
  33.     mem->actual_free = mem->free = cap - used;
  34.     mem->actual_used = mem->used = used;
  35.     sigar_mem_calc_ram(sigar, mem);
  36. return SIGAR_OK;
  37. }

2. 交换空间监控 (solaris_sigar.c:334-402)

  1. int sigar_swap_get(sigar_t*sigar,sigar_swap_t*swap)
  2. {
  3. kstat_t*ksp;
  4. kstat_named_t*kn;
  5. swaptbl_t*stab;
  6. int num, i;
  7. char path[PATH_MAX+1];
  8. // 获取交换空间数量
  9. if((num = swapctl(SC_GETNSWP, NULL))==-1){
  10. return errno;
  11. }
  12.     stab = malloc(num *sizeof(stab->swt_ent[0])+sizeof(*stab));
  13.     stab->swt_n = num;
  14. for(i=0; i<num; i++){
  15.         stab->swt_ent[i].ste_path = path;
  16. }
  17. // 获取交换空间列表
  18. if((num = swapctl(SC_LIST, stab))==-1){
  19.         free(stab);
  20. return errno;
  21. }
  22.     num = num < stab->swt_n ? num : stab->swt_n;
  23.     swap->total = swap->free =0;
  24. for(i=0; i<num; i++){
  25. if(stab->swt_ent[i].ste_flags & ST_INDEL){
  26. continue;// 交换文件正在删除
  27. }
  28.         swap->total += stab->swt_ent[i].ste_pages;
  29.         swap->free += stab->swt_ent[i].ste_free;
  30. }
  31.     free(stab);
  32.     swap->total <<= sigar->pagesize;
  33.     swap->free <<= sigar->pagesize;
  34.     swap->used = swap->total - swap->free;
  35. // 获取分页统计
  36. if(sigar_kstat_update(sigar)==-1){
  37. return errno;
  38. }
  39. if(!(ksp = kstat_lookup(sigar->kc,"cpu",-1,"vm"))){
  40.         swap->page_in = swap->page_out = SIGAR_FIELD_NOTIMPL;
  41. return SIGAR_OK;
  42. }
  43.     swap->page_in = swap->page_out =0;
  44. do{
  45. if(kstat_read(sigar->kc, ksp, NULL)<0){
  46. break;
  47. }
  48. if((kn =(kstat_named_t*)kstat_data_lookup(ksp,"pgin"))){
  49.             swap->page_in += kn->value.i64;
  50. }
  51. if((kn =(kstat_named_t*)kstat_data_lookup(ksp,"pgout"))){
  52.             swap->page_out += kn->value.i64;
  53. }
  54. }while((ksp = kstat_next(ksp,"cpu",-1,"vm")));
  55. return SIGAR_OK;
  56. }

3. CPU 监控 (solaris_sigar.c:476-500+)

  1. int sigar_cpu_get(sigar_t*sigar,sigar_cpu_t*cpu)
  2. {
  3. int status, i;
  4.     status = sigar_cpu_list_get(sigar,&sigar->cpulist);
  5. if(status != SIGAR_OK){
  6. return status;
  7. }
  8.     SIGAR_ZERO(cpu);
  9. for(i=0; i<sigar->cpulist.number; i++){
  10. sigar_cpu_t*xcpu =&sigar->cpulist.data[i];
  11.         cpu->user  += xcpu->user;
  12.         cpu->sys   += xcpu->sys;
  13.         cpu->idle  += xcpu->idle;
  14.         cpu->wait  += xcpu->wait;
  15.         cpu->total += xcpu->total;
  16. }
  17. return SIGAR_OK;
  18. }

4. CPU 信息

CPU 品牌 (solaris_sigar.c:409-453)

  1. staticint get_chip_brand(sigar_t*sigar,int processor,
  2. sigar_cpu_info_t*info)
  3. {
  4. kstat_t*ksp = sigar->ks.cpu_info[processor];
  5. kstat_named_t*brand;
  6. if(sigar->solaris_version <10){
  7. return0;// Solaris 9 不支持
  8. }
  9. if(ksp &&
  10. (kstat_read(sigar->kc, ksp, NULL)!=-1)&&
  11. (brand =(kstat_named_t*)kstat_data_lookup(ksp,"brand")))
  12. {
  13. char*name = KSTAT_NAMED_STR_PTR(brand);
  14. char*vendor ="Sun";
  15. char*vendors[]={"Intel","AMD", NULL};
  16. int i;
  17. if(!name){
  18. return0;
  19. }
  20. // 检测供应商
  21. for(i=0; vendors[i]; i++){
  22. if(strstr(name, vendors[i])){
  23.                 vendor = vendors[i];
  24. break;
  25. }
  26. }
  27.         SIGAR_SSTRCPY(info->vendor, vendor);
  28. return1;
  29. }
  30. else{
  31. return0;
  32. }
  33. }

性能优化

1. kstat 缓存

缓存 kstat 查找结果,避免重复查找。

2. 批量更新

使用 sigar_kstat_update 批量更新 kstat 数据。

3. Zone 感知

区分全局 Zone 和非全局 Zone 的处理。

特殊技术

1. Solaris 11 MIB2 兼容性 (solaris_sigar.c:48)

  1. /*
  2.  * Hack to correctly walk the MIB2 TCP Connection structures
  3.  * for binaries compiled on Sparc 10 running on Sparc 11
  4.  */
  5. #define SOLARIS_11_MIB2_TCP_CONN_SIZE 80

2. ZFS ARC 统计

  1. if((ksp = kstat_lookup(sigar->kc,"zfs",0,"arcstats"))&&
  2. (kstat_read(sigar->kc, ksp, NULL)!=-1))
  3. {
  4. kstat_named_t*kn;
  5. if((kn =(kstat_named_t*)kstat_data_lookup(ksp,"size"))){
  6.         kern = kn->value.i64;
  7. }
  8. if((kn =(kstat_named_t*)kstat_data_lookup(ksp,"c_min"))){
  9. if(kern > kn->value.i64){
  10.             kern -= kn->value.i64;
  11. }
  12. }
  13. }

3. Joyent SmartOS 支持

  1. sigar->joyent =!strncmp(name.version,"joyent",6);

4. libproc 动态加载

  1. sigar->plib = dlopen("libproc.so", RTLD_LAZY);

技术亮点

1. kstat 统一接口

kstat 提供了统一的内核统计数据访问接口。

2. MIB2 流接口

使用流接口获取网络统计信息。

3. Zone 虚拟化

支持 Solaris Zones 的资源监控。

4. ZFS 支持

集成 ZFS 文件系统的 ARC 缓存统计。

5. SmartOS 兼容性

支持 Joyent SmartOS 平台。

限制和挑战

1. 版本兼容性

不同 Solaris 版本之间 kstat 结构有差异。

2. MIB2 复杂性

MIB2 流接口编程复杂,容易出错。

3. libproc 版本

不同 Solaris 版本 libproc 接口有变化。

4. Zone 权限

非全局 Zone 某些信息可能不可用。

总结

libsigar Solaris 实现充分利用了 Solaris 的 kstat、 MIB2 流接口和 libproc 库,提供了全面的系统监控功能。其设计特点包括:

  1. kstat 接口: 统一的内核统计数据访问
  2. MIB2 流: 复杂但功能强大的网络统计接口
  3. Zone 支持: 完整的虚拟化资源监控
  4. ZFS 集成: 支持现代文件系统统计
  5. 动态加载: libproc 等库的动态加载

这种实现方式展示了 Solaris 系统架构的特点,强调了高性能和可靠性。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 跨平台系统信息获取库libsigar源码分析系列(七)

猜你喜欢

  • 暂无文章