Linux调试技巧总结_2020.03.07

 

1.程序的编译和链接

1.1 VS2017跨平台常规配置

"常规"配置

配置主程序: 

本地输出目录:"$(ProjectDir)bin\$(Platform)\$(Configuration)\"修改为"$(ProjectDir)..\bin\$(Platform)\$(Configuration)\",是为了将所有项目输出文件放到同一个目录中,方便相互引用。

目标文件扩展名:".out"修改为"",是为了不生成文件后缀,一般的Linux可执行程序是没有扩展名称的,可修改也可不修改。

远程生成根目录:"~/projects"修改为"/root/projects/$(SolutionName)","~"和"/root"是等价的,但是运行时动态库搜索目录不支持~路径,添加“$(SolutionName)”是为了区分不同的解决方案下相同名称的项目。

远程生成项目目录:"~/projects"修改为"/root/projects/$(SolutionName)","~"和"/root"是等价的,但是运行时动态库搜索目录不支持~路径,添加“$(SolutionName)”是为了区分不同的解决方案下相同名称的项目。

 配置动态库:"$(RemoteRootDir)/$(ProjectName)"

本地输出目录:"$(ProjectDir)bin\$(Platform)\$(Configuration)\"修改为"$(ProjectDir)..\bin\$(Platform)\$(Configuration)\"

目标文件扩展名:".out"修改为".so"

远程生成根目录:"~/projects"修改为"/root/projects/$(SolutionName)"

配置类型:"应用程序(.out)"修改为"动态库(.so)"

配置静态库:

本地输出目录:"$(ProjectDir)bin\$(Platform)\$(Configuration)\"修改为"$(ProjectDir)..\bin\$(Platform)\$(Configuration)\"

目标文件扩展名:".out"修改为".a"

远程生成根目录:"~/projects"修改为"/root/projects/$(SolutionName)"

配置类型:"应用程序(.out)"修改为"动态库(.a)"

调试配置

修改工作目录为执行程序同级目录

 

1.2 编译参数

编译器配置

一般默认即可

静态库时需要指定:

作为库的时候需要指定:

 

 

上面的参数配置最终组合生成了命令行,也可以在下面“其他选项”补充

 

1.3 链接参数

链接器配置

附加库目录:在"%(AdditionalLibraryDirectories)"前面添加"$(RemoteRootDir)/bin/$(Platform)/$(Configuration);",这个是远程主机CentOS上的路径,相当于gcc编译时指定"-L[路径]"选项,用于指定引用动态库和静态库的目录;

库依赖项:添加"dynamic_library;static_library",相当于gcc设置"-l[库名称]"选项,用于指定链接时所需要的动态库和静态库名称,如果找不到依赖的库文件,链接时会错误,显示"无法解析的符号"。

其他选项:添加"-Wl,-rpath=$(RemoteRootDir)/bin/$(Platform)/$(Configuration) ",指定程序运行时搜索动态库的路径。

 

1.4 跨平台程序编写

跨平台头文件定义参考

 

/*********************************************************************************
* Copyright 2019 Huawei Technologies Co.,Ltd.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License.  You may obtain a copy of the
* License at
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software distributed
* under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations under the License.
**********************************************************************************
*/
#ifndef ESDKOBS_H
#define ESDKOBS_H

#include <stdint.h>
#if defined __GNUC__ || defined LINUX
#include <sys/select.h>
#else 
#include <winsock2.h>
#endif 

#ifdef WIN32
#ifdef OBS_EXPORTS
#define eSDK_OBS_API __declspec(dllexport)
#else
#define eSDK_OBS_API __declspec(dllimport)
#endif
#else
#define eSDK_OBS_API __attribute__((__visibility__("default")))
#endif

#ifdef __cplusplus
extern "C" {
#endif

#define OBS_INIT_WINSOCK         1
#define OBS_INIT_ALL                        (OBS_INIT_WINSOCK)
#define OBS_MAX_DELETE_OBJECT_NUMBER  1000
#define OBS_MAX_DELETE_OBJECT_DOC 1024000

eSDK_OBS_API void compute_md5(const char *buffer, int64_t buffer_size, char *outbuffer);

eSDK_OBS_API int set_obs_log_path(const char *log_path);

eSDK_OBS_API void set_openssl_callback(obs_openssl_switch switch_flag);


#ifdef __cplusplus
}
#endif

#endif /* LIBOBS_H */


 

ini文件解析器

ini跨平台库,iniparser-3.1

 

 

m_bReSendTovms = iniparser_getint(RecordIni, "LiveSetting:ReSendToVMS", 1);
strRecPath = iniparser_getstring(RecordIni, "RecordServer:RecordPath", "/mnt");

 

需要注意:最后一个字段需要有换行符,不然读取可能失败。

ini文件需要设定格式为UTF-8;与Windows下的文件格式不同,使用GetPrivateProfileInt读取ini文件,格式必须为ANSI;

windows和linux下long类型长度

其中long类型和指针类型需要特别注意,编写跨平台的软件时尽量不要使用long类型,或者需要对long类型做特殊处理。GUID的定义需要注意

2. 程序的运行和部署

2.1 动态库链接

指定运行时动态库位置的三种方式

在Linux 中,动态库的搜索路径除了默认的搜索路径外,还可通过三种方法来指定:方法一:在配置文件/etc/ld.so.conf中指定动态库搜索路径;方法二:通过环境变量LD_LIBRARY_PATH指定动态库搜索路径;方法三:在编译目标代码时指定该程序的动态库搜索路径。

众所周知,Linux动态库的默认搜索路径是/lib和/usr/lib。动态库被创建后,一般都复制到这两个目录中。当程序执行时需要某动态库,并且该动 态库还未加载到内存中,则系统会自动到这两个默认搜索路径中去查找相应的动态库文件,然后加载该文件到内存中,这样程序就可以使用该动态库中的函数,以及该动态库的其它资源了。在Linux 中,动态库的搜索路径除了默认的搜索路径外,还可以通过以下三种方法来指定。

 

通过gcc参数指定

-Wl,-rpath=${LD_PATH}  #-Wl,-rpath=<动态库所在路径>
g++ ${RPATH} ./opencv_knn.cpp -o knn

通过gcc参数制定之后就不需要配置环境变量和配置文件了。

通过设置环境变量

export LD_LIBRARY_PATH=/root/code/opencv/opencv-3.2.0/build/build/lib
#LD_LIBRARY_PATH=<动态库所在位置>

只对当前shell有效,关闭shell或者退出当前用户则环境变量配置及无效了。

export LD_LIBRARY_PATH=/root/code/opencv/opencv-3.2.0/build/build/lib

通过配置文件

修改/etc/ld.so.conf文件。
 其文件内容为:
 [root@VM_24_16_centos etc]# cat /etc/ld.so.conf
 include ld.so.conf.d/*.conf
 [root@VM_24_16_centos etc]# 
 所以,可以在文件夹/etc/ld.so.conf.d/中添加自己的文件,例如:创建文件gx.conf,
 然后在文件中添加动态库路径,例如:
     /root/code/opencv/opencv-3.2.0/build/build/lib
 注意,保存退出之后需要执行命令ldconfig刷新当前缓存。

 

链接库顺序

使用ldd查看依赖项

使用附加依赖项的模式:

 

ldconfig的方式

 

gcc指定参数-WL,rpath=.

 

readelf -d xxx

2.2 简单脚本编写

安装脚本

#!/bin/bash
echo "InstallRecordServer Begin"
echo $PWD>librecordserver-x86_64.conf
sudo cp librecordserver-x86_64.conf /etc/ld.so.conf.d/
ls /etc/ld.so.conf.d/librecordserver-x86_64.conf
sudo ldconfig
chmod +x recordServer
chmod +x startRecordServer.sh
chmod +x stopRecordServer.sh
sudo systemctl disable firewalld
echo -e "* soft nofile 40960\n* hard nofile 81920" | sudo tee -a /etc/security/limits.conf
#非root用户执行失败
sudo ulimit -n 81920
echo "InstallRecordServer End"

Windows编写的脚本在Linux执行可能遇到问题:“/bin/bash^M: bad interpreter”

脚本格式需要设置为UNIX.

vi filename打开文件,执行 : set ff=unix 设置文件为unix,然后执行:wq,保存成unix格式。

vi filename打开文件,执行 : set ff,如果文件为dos格式在显示为fileformat=dos,如果是unxi则显示为fileformat=unix

启动脚本

#!/bin/bash
echo "RecordServer Begin"
./recordServer &
echo "RecordServer End"

 

停止脚本

#!/bin/bash
echo "StopRecordServer Begin"
sp_pid=`ps -ef | grep recordServer | grep -v grep | awk '{print $2}'`
if [ -z "$sp_pid" ];
then
 echo "[ not find recordServer pid ]"
else
 echo "find result: $sp_pid "
 kill $sp_pid
fi
echo "StopRecordServer End"

 

2.3 打包

tar包

rm -f recordServer
cp -r Release recordServer
tar -zcvf recordServer.tar.gz --exclude=recordServer/RecLogInfo --exclude=recordServer/recordServer.log --exclude=recordServer/libCameraRecordInfo.a recordServer

 

rpm

什么是rpm包?

rpm 相当于windows中的安装文件,它会自动处理软件包之间的依赖关系。

rpm优点: 

包管理系统简单,通过几个命令就可以实现包的安装、升级、卸载。 

安装速度比源码包快的多。

流程: 

打rpm 包需要的东西有 源码、spec文件(打rpm包的脚本)、rpmbuild工具。

1. 安装rpmbuild

$yum install rpmbuild

$yum install rpmdevtools 

$rpmdev-setuptree

 

此时rpmbuild已经安装好了,可以查看一下

rpmbuild --showrc | grep topdir

把这个包拷贝出来,发给其他机器,就可以使用rpm -ivh 安装了

 

制作一个简单的rpm包:helloworld

3. 遇到问题和解决方案

3.1 gcc版本升级导致log4cplus异常

 

3.2 巧用VS报错信息网络查找

 

静态库提示-fPIC的例子

 

编译的时候可以编译成功, 但是当调试的时候会报这个错

 

3.3 善于思考,定位问题出现在什么环节

防火墙关闭的例子

3.4 接入数量高后句柄打开失败

uname -n

 

4. 便利工具总结

4.1 Windows、Linux间文件传输

XFTP传输

sz, rz命令使用

rz,sz是Linux/Unix同Windows进行ZModem文件传输的命令行工具。优点就是不用再开一个sftp工具登录上去上传下载文件。

 

sz:将选定的文件发送(send)到本地机器

rz:运行该命令会弹出一个文件选择窗口,从本地选择文件上传到Linux服务器

安装命令:

 

yum install lrzsz

从服务端发送文件到客户端:

 

sz filename

从客户端上传文件到服务端:

 

rz

在弹出的框中选择文件,上传文件的用户和组是当前登录的用户

SecureCRT设置默认路径:

Options -> Session Options -> Terminal -> Xmodem/Zmodem ->Directories

Xshell设置默认路径:

右键会话 -> 属性 -> ZMODEM -> 接收文件夹

 

nfs挂载,mount命令

sudo mount -t cifs -o username="user",password="pwd",
uid=1001,gid=1001,dir_mode=0775,file_mode=0664,nounix,
noserverino //192.168.1.xxx/CVR ~/projects/cvr_win/

或者Windows挂载Linux

4.2 Linux下工具安装

使用yum命令( Ubuntu下使用apt)

[csvtest@ecs-395b ~]$ yum -y install gcc

# yum -y install gcc-c++
# yum install make

# gcc -v
# gcc --version

[csvtest@ecs-395b ~]$ yum search sz
Loaded plugins: fastestmirror
Determining fastest mirrors
 * base: mirrors.huaweicloud.com
 * epel: mirrors.yun-idc.com
 * extras: mirrors.huaweicloud.com
 * updates: mirrors.huaweicloud.com
======================================================================================================================== N/S matched: sz =========================================================================================================================
laszip-devel.aarch64 : The development files for laszip
lrzsz.aarch64 : The lrz and lsz modem communications programs
laszip.aarch64 : Quickly turns bulky LAS files into compant LAZ files

  Name and summary matches only, use "search all" for everything.



 

源码安装

yum源没有或者版本不够的情况,yum 默认安装的gcc版本为4.8.5

 

wget https://ftp.gnu.org/gnu/gcc/gcc-7.2.0/gcc-7.2.0.tar.gz
tar xzf gcc-7.2.0.tar.gz
cd gcc-7.2.0
./configure --with-system-zlib --disable-multilib --enable-languages=c,c++
make -j 8
make install

 

4.3 程序运行状态观测

任务管理器top/htop

top

htop

查看指定进程pid

ps -aux | grep zookeeper
top -p 10997

 

查看线程数

方法一:PS

在ps命令中,“-T”选项可以开启线程查看。下面的命令列出了由进程号为<pid>的进程创建的所有线程。

ps -T -p <pid>

 

方法二: Top

top命令可以实时显示各个线程情况。要在top输出中开启线程查看,请调用top命令的“-H”选项,该选项会列出所有Linux线程。在top运行时,你也可以通过按“H”键将线程查看模式切换为开或关。

top -H

要让top输出某个特定进程<pid>并检查该进程内运行的线程状况:

top -H -p <pid>

 

方法三: Htop

一个对用户更加友好的方式是,通过htop查看单个进程的线程,它是一个基于ncurses的交互进程查看器。该程序允许你在树状视图中监控单个独立线程。

要在htop中启用线程查看,请开启htop,然后按<F2>来进入htop的设置菜单。选择“设置”栏下面的“显示选项”,然后开启“树状视图”和“显示自定义线程名”选项。按<F10>退出设置。

 

查看进程打开的文件

ls -l /proc/184172/fd/* | wc -l
ls -l /proc/184172/fd/* |more

lsof -p 184172

 

单进程打开的最大文件数

ulimit –a
ore file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 29238
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 29238
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited


ulimit -n 2000

这个命令可以把默认的句柄数改为2000,但系统重启后会恢复默认值

这个文件在系统中的默认值配置在/etc/security/limits.conf文件中,加入以下配置:
修改了/etc/security/limits.conf ,必须要重启,才能生效。

* soft nofile 2000
* hard nofile 2000

 

关闭防火墙

下面是red hat/CentOs7关闭防火墙的命令!
1:查看防火状态
systemctl status firewalld
service  iptables status
2:暂时关闭防火墙
systemctl stop firewalld
service  iptables stop
3:永久关闭防火墙
systemctl disable firewalld
chkconfig iptables off
4:重启防火墙
systemctl enable firewalld
service iptables restart 

 

linux进程的结构内存分布

pmap [pid] 

# pmap -x 21407
21407:   /root/projects/SVDSE/VsipTcpVideoProxy/../bin/x64/Debug/recordServer
Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000      60      48       8 r-x-- recordServer
000000000060e000       4       4       4 r---- recordServer
000000000060f000       4       4       4 rw--- recordServer
0000000000610000     264     204     204 rw---   [ anon ]
00007ffe98000000   65032   64776   64776 rw---   [ anon ]
00007ffe9bf82000     504       0       0 -----   [ anon ]
00007ffe9c000000   65032    2692    2692 rw---   [ anon ]
.........
00007ffff7dc8000       8       8       8 r---- libStorage.so
00007ffff7dca000       4       4       4 rw--- libStorage.so
00007ffff7dcb000      64       4       4 rw---   [ anon ]
00007ffff7ddb000     136     116      20 r-x-- ld-2.17.so
00007ffff7dfd000    2020     432     432 rw---   [ anon ]
00007ffff7ff6000      16      16      16 rw---   [ anon ]
00007ffff7ffa000       8       8       0 r-x--   [ anon ]
00007ffff7ffc000       4       4       4 r---- ld-2.17.so
00007ffff7ffd000       4       4       4 rw--- ld-2.17.so
00007ffff7ffe000       4       4       4 rw---   [ anon ]
00007ffffffde000     132      16      16 rw---   [ stack ]
ffffffffff600000       4       0       0 r-x--   [ anon ]
---------------- ------- ------- ------- 
total kB         5578904  706776  703072


 

查看虚拟内存占用情况

如何查看栈空间大小

 

对于 x86 和 x64 计算机,默认堆栈大小为 1 MB。在 Itanium 芯片组上,默认大小为 4 MB。linux下默认的堆栈空间大小是8M或10M,不同的发行版本可能不太一样。可以使用ulimit指令查看栈空间大小,指令ulimit -s或者ulimit -a如下图:

 

ulimit -s

查看栈空间大小,Linux下默认为8192KB

 

VIRT:virtual memory usage 虚拟内存

1、进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据等

2、假如进程申请100m的内存,但实际只使用了10m,那么它会增长100m,而不是实际的使用量

RES:resident memory usage 常驻内存

1、进程当前使用的内存大小,但不包括swap out

2、包含其他进程的共享

3、如果申请100m的内存,实际使用10m,它只增长10m,与VIRT相反

4、关于库占用内存的情况,它只统计加载的库文件所占内存大小

SHR:shared memory 共享内存

1、除了自身进程的共享内存,也包括其他进程的共享内存

2、虽然进程只使用了几个共享库的函数,但它包含了整个共享库的大小

3、计算某个进程所占的物理内存大小公式:RES – SHR

4、swap out后,它将会降下来

DATA

1、数据占用的内存。如果top没有显示,按f键可以显示出来。

2、真正的该程序要求的数据空间,是真正在运行中要使用的。

top 运行中可以通过 top 的内部命令对进程的显示方式进行控制。内部命令如下:

s – 改变画面更新频率

l – 关闭或开启第一部分第一行 top 信息的表示

t – 关闭或开启第一部分第二行 Tasks 和第三行 Cpus 信息的表示

m – 关闭或开启第一部分第四行 Mem 和 第五行 Swap 信息的表示

N – 以 PID 的大小的顺序排列表示进程列表

P – 以 CPU 占用率大小的顺序排列进程列表

M – 以内存占用率大小的顺序排列进程列表

h – 显示帮助

n – 设置在进程列表所显示进程的数量

q – 退出 top

s – 改变画面更新周期

 

打包、压缩命令 tar/zip

tar语法

压缩

tar -czvf ***.tar.gz

tar -cjvf ***.tar.bz2

解压缩

tar -xzvf ***.tar.gz

tar -xjvf ***.tar.bz2

 

 tar  [主选项+辅选项] 文件或目录

主选项是必须要有的,它告诉tar要做什么事情。

辅选项是辅助使用的,可以选用。

tar常用命令:

主选项:

-x    从档案文件中释放文件。

-c    创建新的档案文件。如果用户想备份一个目录或是一些文件,就要选择这个选项。

-r     把要存档的文件追加到档案文件的末尾。例如用户已经做好备份文件,又发现还有一个目录或

         是一些文件忘 记备份了,这时可以使用该选项,将忘记的目录或文件追加到备份文件中。

-t       列出档案文件的内容,查看已经备份了哪些文件。

-u      更新文件。就是说,用新增的文件取代原备份文件,如果在备份文件中找不到要更新的文件,

          则把它追加到备份文件的最后。

 

辅助选项:

-j         代表使用‘bzip2’程序进行文件的压缩    tar.bz2

-z       用gzip来压缩/解压缩文件,加上该选项后可以将档案文件进行压缩,但还原时也一定要使用该

            选项进行解压缩。   tar.gz

-v       详细报告tar处理的文件信息。如无此选项,tar不报告文件信息。

-b     该选项是为磁带机设定的,其后跟一数字,用来说明区块的大小,系统预设值为20(20×512 bytes)。

-f       使用档案文件或设备,这个选项通常是必选的。

-k       保存已经存在的文件。例如把某个文件还原,在还原的过程中遇到相同的文件,不会进行覆盖。

-m       在还原文件时,把所有文件的修改时间设定为。

-M      创建多卷的档案文件,以便在几个磁盘中存放。

-w           每一步都要求确认。

tar包管理

1、tar包的创建

tar -cvf  file.tar file1  file2

tar -zcvf  file.tar.gz  file1  file2

tar -jcvf   file.tar.bz2  file1  file2

 

2、tar包的查看

tar -tvf  file.tar

tar -ztvf  file.tar.gz

tar -jtvf  file.tar.bz2

 

3、释放tar包

tar -xvf  file.tar

tar -zxvf  file.tar.gz

tar -jxvf  file.tar.bz2

 

 

 

补充一点   如果需要打包一个文件夹,但其中的几个文件不需要打包,命令如下

打包test文件夹      test里的  1  这个文件夹不需要打包

[root@localhost /]# cd /usr/test

[root@localhost test]# ls

1  2  3

 

返回/usr 目录

[root@localhost test]# cd /usr

[root@localhost usr]# tar -zcvf test.tar.gz --exclude=test/1 test

test/

test/3/

test/3/333.png

test/2/

test/2/222.png

 

果然没有打包test/1 文件夹

 

打包程序:tar

  • c:   创建文档

  • t: 列出存档内容

  • x:提取存档

  • f: filename 要操作的文档名

  • v:详细信息

 

  • z     用于gzip压缩:      filename.tar.gz

  • j      用于bzip压缩:      filename.tar.bz2

  • J      用于xz压缩:         filename.tar.xz

 

将tar压缩文件解压到指定的目录下的命令是:

tar -xvf  压缩文件 -C  /指定目录

例:#tar -xvf openstack_test.tar -C /tmp

说明:把根目录下的openstack_test.tar解压到/tmp下

生成core文件并用gdb调试

ulimit -c unlimited
sysctl -p /etc/sysctl.conf
kill -s SIGSEGV 13425

调试core文件
gdb recordServer
core-file core_recordServer_13425 
然后执行bt看堆栈信息

 

端口占用情况

lsof -i:26000


netstat -tunlpc
用于显示tcp,udp的端口和进程等相关情况,如下图
命令里的t,u,n,l,p均有不同含义:
-t  仅显示和tcp相关的
-u 仅显示和udp相关的
-n 不限时别名,能显示数字的全部转换为数字
-l   仅显示出于Listen(监听)状态的
-p  显示建立这些连接的程序名

 

网络抓包命令

tcpdump -i eth0 tcp port 26000

tcpdump src host ip				抓取从该ip发来的信息
tcpdump src port 5070				抓取从5070发来的消息
tcpdump host ip and port 5070 			抓取该ip&&端口
tcpdump host 192.168.1.101 and udp port 5060
tcpdump src host 192.168.1.101 and src port 5060 and udp	
tcpdump -i enp1s0  port 5070  | grep MESSAGE

 

4.4 关于ANSI,unicode与utf-8的区别

不同的国家和地区制定了不同的标准,由此产生了 GB2312、GBK、GB18030、Big5、Shift_JIS 等各自的编码标准。这些使用多个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;在繁体中文Windows操作系统中,ANSI编码代表Big5;在日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码。

不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。

ANSI编码表示英文字符时用一个字节,表示中文用两个或四个字节。

 

在中文和日文操作系统里生成的(txt和xml)文件的编码虽然都是ansi,但是,在简体中文系统下,ansi 编码代表 GB2312 编码,在日文操作系统下,ansi 编码代表 JIS 编码。不同 ansi 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ansi 编码的文本中。

结论:国际文档(txt和xml)使用unicode编码是正宗做法;操作系统和浏览器都能够“理解”unicode编码。浏览器“迫于压力”才“理解”utf-8编码。但是,操作系统有时只认unicode编码。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值