IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++中记录并解析函数调用栈callstack.txt -> 正文阅读

[C++知识库]C++中记录并解析函数调用栈callstack.txt

glibc中提供了backtrace()和backtrace_symbols()两个函数来输出和解析程序的call stack,

输出程序运行时调用栈信息

可以通过命令man backtrace查看具体帮忙信息。

#include <execinfo.h>

int backtrace(void **buffer, int size);

char **backtrace_symbols(void *const *buffer, int size);
  1. 使用backtrace()函数获取调用栈,返回获取到的调用栈个数,结果放到传入的指针数组buffer里面;
  2. 调用backtrace_symbols()把获取的指针数组和数组中调用栈个数传递给该函数,
    会返回一个新的指针数组,里面是已经转换成符号表的调用栈信息;
    用完后记得需要free返回的指针变量指向的内存空间;
  3. 使用这两个函数需要包含execinfo.h头文件;

下面把这个过程封装成了一个宏函数方便以后使用;

#ifndef __DEBUG_HPP__
#define __DEBUG_HPP__
#include <iostream>
#include <execinfo.h>

/* include <execinfo.h> to use this macro */
#define DBG_ASSERT(x) do { \
    if (x) { break; } \
    std::cout << "\r\n------ file:" <<  __FILE__ << " line:" <<  __LINE__ << " ------" << std::endl;\
    void *pptrace_raw[32] = {0}; \
    char **pptrace_str = NULL; \
    int  trace_num = 0, iloop = 0; \
    trace_num = backtrace(pptrace_raw, 32); \
    pptrace_str = (char **)backtrace_symbols(pptrace_raw, trace_num); \
    for (iloop=0; iloop<trace_num; iloop++) { std::cout << pptrace_str[iloop] << std::endl; }\
    if (pptrace_str) { delete pptrace_str; } \
} while (0);

#endif

当应用出现异常的时候可以获取一次调用栈,然后再退出,方便问题分析和定位;
例如当出现段错误时往往会收到SIGSEGV信号,这时可以提前注册接收SIGSEGV信号并打印调用栈,然后再恢复默认信号处理;

/* 1. register handler of signal SIGSEGV */
signal(SIGSEGV, sigsegvhandle);

/* 2. define signal handler and reset signal handler to default at then end of handler function */
void sigsegvhandle(int signo) {
    std::cout << "sigsegvhandle received signal: " << signo << std::endl;
    /* output callstack */
    DBG_ASSERT(0);
    /* reset signal handle to default */
    signal(signo, SIG_DFL);
    /* will receive SIGSEGV again and exit app */
}

应用示例

/*****************************************
 * Copyright (C) 2018 * Ltd. All rights reserved.
 * Created date: 2018-08-02 19:18:13
 *******************************************/
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <execinfo.h>

/* include <execinfo.h> to use this macro */
#define DBG_ASSERT(x) do { \
    if (x) { break; } \
    std::cout << "###### file:" <<  __FILE__ << " line:" <<  __LINE__ << " ######" << std::endl;\
    void *pptrace_raw[32] = {0}; \
    char **pptrace_str = NULL; \
    int  trace_num = 0, iloop = 0; \
    trace_num = backtrace(pptrace_raw, 32); \
    pptrace_str = (char **)backtrace_symbols(pptrace_raw, trace_num); \
    for (iloop=0; iloop<trace_num; iloop++) { std::cout << pptrace_str[iloop] << std::endl; } \
    if (pptrace_str) { delete pptrace_str; } \
} while (0);

void sigsegv_test()
{
    std::cout << __func__ << " begin" << std::endl;
    char *buff = NULL;
    buff[1] = buff[1]; /* will crash here */
    std::cout << __func__ << " end" << std::endl;
}

void sigsegvhandle(int signo) {
    std::cout << "sigsegvhandle received signal: " << signo << std::endl;
    /* output callstack */
    DBG_ASSERT(0);
    /* reset signal handle to default */
    signal(signo, SIG_DFL);
    /* will receive SIGSEGV again and exit app */
}

int main() {
    /* register handler of signal SIGSEGV */
    signal(SIGSEGV, sigsegvhandle);
    sleep(5);
    sigsegv_test();
    return 0;
}

编译执行

$ g++ vec.cpp -o vec
$ ./vec

后会有以下输出

sigsegv_test begin
sigsegvhandle received signal: 11
###### file:backtrace.cpp line:63 ######
./vec(+0xca4) [0x55e418312ca4]
/lib/x86_64-linux-gnu/libc.so.6(+0x3f040) [0x7f48cf8df040]
./vec(+0xb5d) [0x55e418312b5d]
./vec(+0xd95) [0x55e418312d95]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f48cf8c1bf7]
./vec(+0xa2a) [0x55e418312a2a]
Segmentation fault (core dumped)

根据以上调用栈并结合程序的符号表就可以查出具体的调用信息;

解析函数调用栈

首先使用nm命令导出编译符号表

注意nm命令对应编译时使用的gcc/g++工具链;

比如以上程序编译为vec

$ nm -n vec > vec.symbol.txt
$ cat vec.symbol.txt
                 U backtrace@@GLIBC_2.2.5
                 U backtrace_symbols@@GLIBC_2.2.5
                 U __cxa_atexit@@GLIBC_2.2.5
                 w __cxa_finalize@@GLIBC_2.2.5
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U __libc_start_main@@GLIBC_2.2.5
                 U signal@@GLIBC_2.2.5
                 U sleep@@GLIBC_2.2.5
                 U __stack_chk_fail@@GLIBC_2.4
                 U _ZdlPvm@@CXXABI_1.3.9
                 U _ZNSolsEi@@GLIBCXX_3.4
                 U _ZNSolsEPFRSoS_E@@GLIBCXX_3.4
                 U _ZNSt8ios_base4InitC1Ev@@GLIBCXX_3.4
                 U _ZNSt8ios_base4InitD1Ev@@GLIBCXX_3.4
                 U _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@@GLIBCXX_3.4
                 U _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@@GLIBCXX_3.4
0000000000000918 T _init
0000000000000a00 T _start
0000000000000a30 t deregister_tm_clones
0000000000000a70 t register_tm_clones
0000000000000ac0 t __do_global_dtors_aux
0000000000000b00 t frame_dummy
0000000000000b0a T _Z12sigsegv_testv
0000000000000b9d T _Z13sigsegvhandlei
0000000000000d71 T main
0000000000000d9c t _Z41__static_initialization_and_destruction_0ii
0000000000000de5 t _GLOBAL__sub_I__Z12sigsegv_testv
0000000000000e00 T __libc_csu_init
0000000000000e70 T __libc_csu_fini
0000000000000e74 T _fini
0000000000000e80 R _IO_stdin_used
0000000000000e88 r _ZStL19piecewise_construct
0000000000000ee8 r _ZZ12sigsegv_testvE8__func__
0000000000000ef8 r __GNU_EH_FRAME_HDR
00000000000010dc r __FRAME_END__
0000000000201d40 t __frame_dummy_init_array_entry
0000000000201d40 t __init_array_start
0000000000201d50 t __do_global_dtors_aux_fini_array_entry
0000000000201d50 t __init_array_end
0000000000201d58 d _DYNAMIC
0000000000201f58 d _GLOBAL_OFFSET_TABLE_
0000000000202000 D __data_start
0000000000202000 W data_start
0000000000202008 D __dso_handle
0000000000202010 B __bss_start
0000000000202010 D _edata
0000000000202010 D __TMC_END__
0000000000202020 B _ZSt4cout@@GLIBCXX_3.4
0000000000202130 b completed.7698
0000000000202131 b _ZStL8__ioinit
0000000000202138 B _end

由于C++有符号修饰(Name Decoration)或符号改编(Name Mangling)的机制;
可以使用-C选项来导出更易读的符号表

$ nm -nC vec > vec.symbol.txt
$ cat vec.symbol.txt
                 U backtrace@@GLIBC_2.2.5
                 U backtrace_symbols@@GLIBC_2.2.5
                 U __cxa_atexit@@GLIBC_2.2.5
                 w __cxa_finalize@@GLIBC_2.2.5
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U __libc_start_main@@GLIBC_2.2.5
                 U signal@@GLIBC_2.2.5
                 U sleep@@GLIBC_2.2.5
                 U __stack_chk_fail@@GLIBC_2.4
                 U operator delete(void*, unsigned long)@@CXXABI_1.3.9
                 U std::ostream::operator<<(int)@@GLIBCXX_3.4
                 U std::ostream::operator<<(std::ostream& (*)(std::ostream&))@@GLIBCXX_3.4
                 U std::ios_base::Init::Init()@@GLIBCXX_3.4
                 U std::ios_base::Init::~Init()@@GLIBCXX_3.4
                 U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)@@GLIBCXX_3.4
                 U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@@GLIBCXX_3.4
0000000000000918 T _init
0000000000000a00 T _start
0000000000000a30 t deregister_tm_clones
0000000000000a70 t register_tm_clones
0000000000000ac0 t __do_global_dtors_aux
0000000000000b00 t frame_dummy
0000000000000b0a T sigsegv_test()
0000000000000b9d T sigsegvhandle(int)
0000000000000d71 T main
0000000000000d9c t __static_initialization_and_destruction_0(int, int)
0000000000000de5 t _GLOBAL__sub_I__Z12sigsegv_testv
0000000000000e00 T __libc_csu_init
0000000000000e70 T __libc_csu_fini
0000000000000e74 T _fini
0000000000000e80 R _IO_stdin_used
0000000000000e88 r std::piecewise_construct
0000000000000ee8 r sigsegv_test()::__func__
0000000000000ef8 r __GNU_EH_FRAME_HDR
00000000000010dc r __FRAME_END__
0000000000201d40 t __frame_dummy_init_array_entry
0000000000201d40 t __init_array_start
0000000000201d50 t __do_global_dtors_aux_fini_array_entry
0000000000201d50 t __init_array_end
0000000000201d58 d _DYNAMIC
0000000000201f58 d _GLOBAL_OFFSET_TABLE_
0000000000202000 D __data_start
0000000000202000 W data_start
0000000000202008 D __dso_handle
0000000000202010 B __bss_start
0000000000202010 D _edata
0000000000202010 D __TMC_END__
0000000000202020 B std::cout@@GLIBCXX_3.4
0000000000202130 b completed.7698
0000000000202131 b std::__ioinit
0000000000202138 B _end

注意:调用栈的地址是实际运行时映射的虚拟地址;

获取运行时进程内存映射关系

可以使用 cat /proc/(程序运行时的PID号)/maps 获取运行时内存映射关系;
每个调用栈地址解析的时候需要减去对应段映射地址的首地址,然后再到导出的符号表中查找;

如下为运行时的maps信息

$ ps -ef | pgrep vec |xargs -i cat /proc/{}/maps 
55e418312000-55e418314000 r-xp 00000000 08:71 1048919                    /mnt/home_tmp/tmp/vec
55e418513000-55e418514000 r--p 00001000 08:71 1048919                    /mnt/home_tmp/tmp/vec
55e418514000-55e418515000 rw-p 00002000 08:71 1048919                    /mnt/home_tmp/tmp/vec
55e419d3e000-55e419d5f000 rw-p 00000000 00:00 0                          [heap]
7f48cf2ea000-7f48cf301000 r-xp 00000000 08:01 21495879                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7f48cf301000-7f48cf500000 ---p 00017000 08:01 21495879                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7f48cf500000-7f48cf501000 r--p 00016000 08:01 21495879                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7f48cf501000-7f48cf502000 rw-p 00017000 08:01 21495879                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7f48cf502000-7f48cf69f000 r-xp 00000000 08:01 21496080                   /lib/x86_64-linux-gnu/libm-2.27.so
7f48cf69f000-7f48cf89e000 ---p 0019d000 08:01 21496080                   /lib/x86_64-linux-gnu/libm-2.27.so
7f48cf89e000-7f48cf89f000 r--p 0019c000 08:01 21496080                   /lib/x86_64-linux-gnu/libm-2.27.so
7f48cf89f000-7f48cf8a0000 rw-p 0019d000 08:01 21496080                   /lib/x86_64-linux-gnu/libm-2.27.so
7f48cf8a0000-7f48cfa87000 r-xp 00000000 08:01 21496033                   /lib/x86_64-linux-gnu/libc-2.27.so
7f48cfa87000-7f48cfc87000 ---p 001e7000 08:01 21496033                   /lib/x86_64-linux-gnu/libc-2.27.so
7f48cfc87000-7f48cfc8b000 r--p 001e7000 08:01 21496033                   /lib/x86_64-linux-gnu/libc-2.27.so
7f48cfc8b000-7f48cfc8d000 rw-p 001eb000 08:01 21496033                   /lib/x86_64-linux-gnu/libc-2.27.so
7f48cfc8d000-7f48cfc91000 rw-p 00000000 00:00 0 
7f48cfc91000-7f48cfe0a000 r-xp 00000000 08:01 31458490                   /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f48cfe0a000-7f48d000a000 ---p 00179000 08:01 31458490                   /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f48d000a000-7f48d0014000 r--p 00179000 08:01 31458490                   /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f48d0014000-7f48d0016000 rw-p 00183000 08:01 31458490                   /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7f48d0016000-7f48d001a000 rw-p 00000000 00:00 0 
7f48d001a000-7f48d0043000 r-xp 00000000 08:01 21495852                   /lib/x86_64-linux-gnu/ld-2.27.so
7f48d01f7000-7f48d01fd000 rw-p 00000000 00:00 0 
7f48d0243000-7f48d0244000 r--p 00029000 08:01 21495852                   /lib/x86_64-linux-gnu/ld-2.27.so
7f48d0244000-7f48d0245000 rw-p 0002a000 08:01 21495852                   /lib/x86_64-linux-gnu/ld-2.27.so
7f48d0245000-7f48d0246000 rw-p 00000000 00:00 0 
7fff0e3a6000-7fff0e3c7000 rw-p 00000000 00:00 0                          [stack]
7fff0e3d8000-7fff0e3db000 r--p 00000000 00:00 0                          [vvar]
7fff0e3db000-7fff0e3dd000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

计算偏移地址并到符号表中查找

然后根据调用栈的地址到符号表地址中查找,
注意符号表中的地址表示某个符号(函数)开始的位置;
如对于 ./vec(+0xca4) [0x55e418312ca4] 地址对应的符号应该是
0000000000000b9d T sigsegvhandle(int)
因为 ./vec(+0xca4) [0x55e418312ca4] 实际对应的符号表地址应该是 0x55e418312ca4-0x55e418312000 即 0xca4 地址落在这两个
0000000000000b9d T sigsegvhandle(int)
0000000000000d71 T main
之间,说明0xca4是属于 sigsegvhandle(int) 函数内的;

以此类推可以得到调用关系,和我们代码的实际调用关系是一致的(由于C++编译时符号表函数名会被修改添加一些参数类型信息);

sigsegv_test begin
sigsegvhandle received signal: 11
###### file:backtrace.cpp line:63 ######
./vec(+0xca4) [0x55e418312ca4]    sigsegvhandle(int)
/lib/x86_64-linux-gnu/libc.so.6(+0x3f040) [0x7f48cf8df040]
./vec(+0xb5d) [0x55e418312b5d]    sigsegv_test()
./vec(+0xd95) [0x55e418312d95]    main
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f48cf8c1bf7]
./vec(+0xa2a) [0x55e418312a2a]    _start
Segmentation fault (core dumped)

解析库文件的符号表

如果需要解析库文件中的符号,也可以使用nm命令获取库文件对应的符号表;
也需要有程序运行时的内存映射关系;

如果需要解析 /lib/x86_64-linux-gnu/libc.so.6(+0x3f040) [0x7f48cf8df040]
则需要首先减去对应的偏移地址即 0x7f48cf8df040 - 0x7f48cf8a0000 然后再到 libc.so.6 对应的符号表中查询;
由于 libc.so.6 不是我们自己编译的库,因此在这里不再继续解析了;
如果是自己编译的库文件则可以继续;

自动获取进程的maps信息

下面增加一个函数 getmapsinfo(),用于自动获取运行时的 maps 信息
需要包含以下头文件

#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

用于获取pid及读文件操作;

/*****************************************
 * Copyright (C) 2018 * Ltd. All rights reserved.
 * Created date: 2018-08-02 19:18:13
 *******************************************/
#include <iostream>
#include <signal.h>
#include <execinfo.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

/* include <execinfo.h> to use this macro */
#define DBG_ASSERT(x) do { \
    if (x) { break; } \
    std::cout << "###### file:" <<  __FILE__ << " line:" <<  __LINE__ << " ######" << std::endl;\
    void *pptrace_raw[32] = {0}; \
    char **pptrace_str = NULL; \
    int  trace_num = 0, iloop = 0; \
    trace_num = backtrace(pptrace_raw, 32); \
    pptrace_str = (char **)backtrace_symbols(pptrace_raw, trace_num); \
    for (iloop=0; iloop<trace_num; iloop++) { std::cout << pptrace_str[iloop] << std::endl; } \
    if (pptrace_str) { delete pptrace_str; } \
} while (0);

void sigsegv_test()
{
    std::cout << __func__ << " begin" << std::endl;
    char *buff = NULL;
    buff[1] = buff[1]; /* will crash here */
    std::cout << __func__ << " end" << std::endl;
}

void getmapsinfo()
{
    pid_t pid = getpid();
    std::string mapsfile = "/proc/";
    mapsfile.append(std::to_string(pid));
    mapsfile.append("/maps");
    std::cout << "##############################################" << std::endl;
    std::cout << "pid: " << pid << "maps: " << mapsfile << std::endl;
    int fd = open(mapsfile.c_str(), O_RDONLY);
    if (fd == -1) {
        std::cout << "open error: " << strerror(errno) << std::endl;
        return;
    }
    int len = 0;
    char buff[200] = {0};
    while ((len = read(fd, buff, sizeof(buff)-1)) > 0) {
        buff[len] = '\0';
        std::cout << buff;
    }
    std::cout << "##############################################" << std::endl;
}

void sigsegvhandle(int signo) {
    std::cout << "sigsegvhandle received signal: " << signo << std::endl;
    /* output callstack */
    DBG_ASSERT(0);
    getmapsinfo();
    /* reset signal handle to default */
    signal(signo, SIG_DFL);
    /* will receive SIGSEGV again and exit app */
}

int main() {
    /* register handler of signal SIGSEGV */
    signal(SIGSEGV, sigsegvhandle);
    sleep(5);
    sigsegv_test();
    return 0;
}

新的符号表如下

$ nm -nC vec > vec.symbol.txt
$ cat vec.symbol.txt
                 U backtrace@@GLIBC_2.2.5
                 U backtrace_symbols@@GLIBC_2.2.5
                 U __cxa_atexit@@GLIBC_2.2.5
                 U __cxa_begin_catch@@CXXABI_1.3
                 U __cxa_end_catch@@CXXABI_1.3
                 w __cxa_finalize@@GLIBC_2.2.5
                 U __cxa_rethrow@@CXXABI_1.3
                 U __errno_location@@GLIBC_2.2.5
                 U getpid@@GLIBC_2.2.5
                 w __gmon_start__
                 U __gxx_personality_v0@@CXXABI_1.3
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
                 U __libc_start_main@@GLIBC_2.2.5
                 U open@@GLIBC_2.2.5
                 U read@@GLIBC_2.2.5
                 U signal@@GLIBC_2.2.5
                 U sleep@@GLIBC_2.2.5
                 U __stack_chk_fail@@GLIBC_2.4
                 U strerror@@GLIBC_2.2.5
                 U _Unwind_Resume@@GCC_3.0
                 U vsnprintf@@GLIBC_2.2.5
                 U operator delete(void*, unsigned long)@@CXXABI_1.3.9
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::c_str() const@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_data() const@@GLIBCXX_3.4.21
                 U std::allocator<char>::allocator()@@GLIBCXX_3.4
                 U std::allocator<char>::~allocator()@@GLIBCXX_3.4
                 U std::allocator<char>::~allocator()@@GLIBCXX_3.4
                 U std::ostream::operator<<(int)@@GLIBCXX_3.4
                 U std::ostream::operator<<(std::ostream& (*)(std::ostream&))@@GLIBCXX_3.4
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_dispose()@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_capacity(unsigned long)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider::_Alloc_hider(char*, std::allocator<char> const&)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_local_data()@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_set_length(unsigned long)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_S_copy_chars(char*, char*, char*)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::append(char const*)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::append(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_data(char*)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)@@GLIBCXX_3.4.21
                 U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()@@GLIBCXX_3.4.21
                 U std::ios_base::Init::Init()@@GLIBCXX_3.4
                 U std::ios_base::Init::~Init()@@GLIBCXX_3.4
                 U std::__throw_logic_error(char const*)@@GLIBCXX_3.4
                 U std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)@@GLIBCXX_3.4
                 U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <char, std::char_traits<char>, std::allocator<char> >(std::basic_ostream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@@GLIBCXX_3.4.21
                 U std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)@@GLIBCXX_3.4
00000000000015a0 T _init
0000000000001850 T _start
0000000000001880 t deregister_tm_clones
00000000000018c0 t register_tm_clones
0000000000001910 t __do_global_dtors_aux
0000000000001950 t frame_dummy
000000000000195a T sigsegv_test()
00000000000019ed T getmapsinfo()
0000000000001ccf T sigsegvhandle(int)
0000000000001ea8 T main
0000000000001ed3 t __static_initialization_and_destruction_0(int, int)
0000000000001f1c t _GLOBAL__sub_I__Z12sigsegv_testv
0000000000001f31 W std::__cxx11::to_string(int)
0000000000001f93 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > __gnu_cxx::__to_xstring<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, char>(int (*)(char*, unsigned long, char const*, __va_list_tag*), unsigned long, char const*, ...)
0000000000002134 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider::~_Alloc_hider()
0000000000002134 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider::~_Alloc_hider()
0000000000002150 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<char*, void>(char*, char*, std::allocator<char> const&)
0000000000002150 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string<char*, void>(char*, char*, std::allocator<char> const&)
00000000000021c8 W void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*>(char*, char*)
0000000000002224 W void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct_aux<char*>(char*, char*, std::__false_type)
0000000000002280 W void std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct<char*>(char*, char*, std::forward_iterator_tag)
00000000000023b7 W bool __gnu_cxx::__is_null_pointer<char>(char*)
00000000000023c9 W std::iterator_traits<char*>::iterator_category std::__iterator_category<char*>(char* const&)
00000000000023d3 W std::iterator_traits<char*>::difference_type std::distance<char*>(char*, char*)
0000000000002435 W std::iterator_traits<char*>::difference_type std::__distance<char*>(char*, char*, std::random_access_iterator_tag)
0000000000002460 T __libc_csu_init
00000000000024d0 T __libc_csu_fini
00000000000024d4 T _fini
00000000000024e0 R _IO_stdin_used
00000000000024e8 r std::piecewise_construct
00000000000025d8 r sigsegv_test()::__func__
00000000000025e8 r __GNU_EH_FRAME_HDR
00000000000029f4 r __FRAME_END__
0000000000202c48 t __frame_dummy_init_array_entry
0000000000202c48 t __init_array_start
0000000000202c58 t __do_global_dtors_aux_fini_array_entry
0000000000202c58 t __init_array_end
0000000000202c60 d _DYNAMIC
0000000000202e70 d _GLOBAL_OFFSET_TABLE_
0000000000203000 D __data_start
0000000000203000 W data_start
0000000000203008 D __dso_handle
0000000000203010 V DW.ref.__gxx_personality_v0
0000000000203018 B __bss_start
0000000000203018 D _edata
0000000000203018 D __TMC_END__
0000000000203020 B std::cout@@GLIBCXX_3.4
0000000000203130 b completed.7698
0000000000203131 b std::__ioinit
0000000000203138 B _end

运行信息如下

sigsegv_test begin
sigsegvhandle received signal: 11
###### file:backtrace.cpp line:63 ######
./vec(+0x1dd6) [0x55c4c441add6]
/lib/x86_64-linux-gnu/libc.so.6(+0x3f040) [0x7fdfa940c040]
./vec(+0x19ad) [0x55c4c441a9ad]
./vec(+0x1ecc) [0x55c4c441aecc]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7fdfa93eebf7]
./vec(+0x187a) [0x55c4c441a87a]

##############################################
pid: 6784maps: /proc/6784/maps
55c4c4419000-55c4c441c000 r-xp 00000000 08:71 1048638                    /mnt/home_tmp/tmp/vec
55c4c461b000-55c4c461c000 r--p 00002000 08:71 1048638                    /mnt/home_tmp/tmp/vec
55c4c461c000-55c4c461d000 rw-p 00003000 08:71 1048638                    /mnt/home_tmp/tmp/vec
55c4c5650000-55c4c5671000 rw-p 00000000 00:00 0                          [heap]
7fdfa902f000-7fdfa91cc000 r-xp 00000000 08:01 21496080                   /lib/x86_64-linux-gnu/libm-2.27.so
7fdfa91cc000-7fdfa93cb000 ---p 0019d000 08:01 21496080                   /lib/x86_64-linux-gnu/libm-2.27.so
7fdfa93cb000-7fdfa93cc000 r--p 0019c000 08:01 21496080                   /lib/x86_64-linux-gnu/libm-2.27.so
7fdfa93cc000-7fdfa93cd000 rw-p 0019d000 08:01 21496080                   /lib/x86_64-linux-gnu/libm-2.27.so
7fdfa93cd000-7fdfa95b4000 r-xp 00000000 08:01 21496033                   /lib/x86_64-linux-gnu/libc-2.27.so
7fdfa95b4000-7fdfa97b4000 ---p 001e7000 08:01 21496033                   /lib/x86_64-linux-gnu/libc-2.27.so
7fdfa97b4000-7fdfa97b8000 r--p 001e7000 08:01 21496033                   /lib/x86_64-linux-gnu/libc-2.27.so
7fdfa97b8000-7fdfa97ba000 rw-p 001eb000 08:01 21496033                   /lib/x86_64-linux-gnu/libc-2.27.so
7fdfa97ba000-7fdfa97be000 rw-p 00000000 00:00 0 
7fdfa97be000-7fdfa97d5000 r-xp 00000000 08:01 21495879                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7fdfa97d5000-7fdfa99d4000 ---p 00017000 08:01 21495879                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7fdfa99d4000-7fdfa99d5000 r--p 00016000 08:01 21495879                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7fdfa99d5000-7fdfa99d6000 rw-p 00017000 08:01 21495879                   /lib/x86_64-linux-gnu/libgcc_s.so.1
7fdfa99d6000-7fdfa9b4f000 r-xp 00000000 08:01 31458490                   /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7fdfa9b4f000-7fdfa9d4f000 ---p 00179000 08:01 31458490                   /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7fdfa9d4f000-7fdfa9d59000 r--p 00179000 08:01 31458490                   /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7fdfa9d59000-7fdfa9d5b000 rw-p 00183000 08:01 31458490                   /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.25
7fdfa9d5b000-7fdfa9d5f000 rw-p 00000000 00:00 0 
7fdfa9d5f000-7fdfa9d88000 r-xp 00000000 08:01 21495852                   /lib/x86_64-linux-gnu/ld-2.27.so
7fdfa9f3c000-7fdfa9f42000 rw-p 00000000 00:00 0 
7fdfa9f88000-7fdfa9f89000 r--p 00029000 08:01 21495852                   /lib/x86_64-linux-gnu/ld-2.27.so
7fdfa9f89000-7fdfa9f8a000 rw-p 0002a000 08:01 21495852                   /lib/x86_64-linux-gnu/ld-2.27.so
7fdfa9f8a000-7fdfa9f8b000 rw-p 00000000 00:00 0 
7ffec92d2000-7ffec92f3000 rw-p 00000000 00:00 0                          [stack]
7ffec9389000-7ffec938c000 r--p 00000000 00:00 0                          [vvar]
7ffec938c000-7ffec938e000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
##############################################
Segmentation fault (core dumped)

然后根据编译导出的符号表和maps信息解析即可得到对应的调用关系。

打印调用栈的时候即打印符号名

-rdynamic 是一个连接选项,它指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表(即.dynsym表)里,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。

-rdynamic选项编译程序,然后使用 readelf 命令查看:

$ g++ vec.cpp -o vec -rdynamic
$ readelf -s vec
Symbol table '.dynsym' contains 78 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __errno_location@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSaIcED2Ev@GLIBCXX_3.4 (3)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
     4: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND strerror@GLIBC_2.2.5 (2)
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_begin_catch@CXXABI_1.3 (5)
     6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNKSt7__cxx1112basic_str@GLIBCXX_3.4.21 (4)
     7: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@GLIBC_2.2.5 (2)
     8: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZSt4endlIcSt11char_trait@GLIBCXX_3.4 (3)
     9: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    10: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND vsnprintf@GLIBC_2.2.5 (2)
    11: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    12: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZSt19__throw_logic_error@GLIBCXX_3.4 (3)
    13: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND open@GLIBC_2.2.5 (2)
    14: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND sleep@GLIBC_2.2.5 (2)
    15: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    16: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    17: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_atexit@GLIBC_2.2.5 (2)
    18: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZStlsIcSt11char_traitsIc@GLIBCXX_3.4.21 (4)
    19: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZStlsISt11char_traitsIcE@GLIBCXX_3.4 (3)
    20: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZdlPvm@CXXABI_1.3.9 (6)
    21: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSolsEPFRSoS_E@GLIBCXX_3.4 (3)
    22: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    23: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSaIcED1Ev@GLIBCXX_3.4 (3)
    24: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNKSt7__cxx1112basic_str@GLIBCXX_3.4.21 (4)
    25: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (7)
    26: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND signal@GLIBC_2.2.5 (2)
    27: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    28: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    29: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND read@GLIBC_2.2.5 (2)
    30: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_rethrow@CXXABI_1.3 (5)
    31: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getpid@GLIBC_2.2.5 (2)
    32: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND backtrace@GLIBC_2.2.5 (2)
    33: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND backtrace_symbols@GLIBC_2.2.5 (2)
    34: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt8ios_base4InitC1Ev@GLIBCXX_3.4 (3)
    35: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __cxa_end_catch@CXXABI_1.3 (5)
    36: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __gxx_personality_v0@CXXABI_1.3 (5)
    37: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSolsEi@GLIBCXX_3.4 (3)
    38: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
    39: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _Unwind_Resume@GCC_3.0 (8)
    40: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSaIcEC1Ev@GLIBCXX_3.4 (3)
    41: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    42: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
    43: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    44: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
    45: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    46: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    47: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt7__cxx1112basic_stri@GLIBCXX_3.4.21 (4)
    48: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _ZNSt8ios_base4InitD1Ev@GLIBCXX_3.4 (3)
    49: 0000000000002558    43 FUNC    GLOBAL DEFAULT   14 main
    50: 0000000000002b84     0 FUNC    GLOBAL DEFAULT   15 _fini
    51: 0000000000001c58     0 FUNC    GLOBAL DEFAULT   11 _init
    52: 000000000000237f   473 FUNC    GLOBAL DEFAULT   14 _Z13sigsegvhandlei
    53: 0000000000204018     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start
    54: 0000000000002b90     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used
    55: 0000000000204138     0 NOTYPE  GLOBAL DEFAULT   25 _end
    56: 0000000000002800   120 FUNC    WEAK   DEFAULT   14 _ZNSt7__cxx1112basic_stri
    57: 00000000000027e4    27 FUNC    WEAK   DEFAULT   14 _ZNSt7__cxx1112basic_stri
    58: 0000000000204000     0 NOTYPE  GLOBAL DEFAULT   24 __data_start
    59: 0000000000002a79    10 FUNC    WEAK   DEFAULT   14 _ZSt19__iterator_category
    60: 00000000000027e4    27 FUNC    WEAK   DEFAULT   14 _ZNSt7__cxx1112basic_stri
    61: 0000000000002643   416 FUNC    WEAK   DEFAULT   14 _ZN9__gnu_cxx12__to_xstri
    62: 0000000000204018     0 NOTYPE  GLOBAL DEFAULT   24 _edata
    63: 0000000000002b80     2 FUNC    GLOBAL DEFAULT   14 __libc_csu_fini
    64: 0000000000002a67    18 FUNC    WEAK   DEFAULT   14 _ZN9__gnu_cxx17__is_null_
    65: 0000000000001f00    43 FUNC    GLOBAL DEFAULT   14 _start
    66: 0000000000002b10   101 FUNC    GLOBAL DEFAULT   14 __libc_csu_init
    67: 000000000000209d   738 FUNC    GLOBAL DEFAULT   14 _Z11getmapsinfov
    68: 00000000000025e1    98 FUNC    WEAK   DEFAULT   14 _ZNSt7__cxx119to_stringEi
    69: 0000000000002800   120 FUNC    WEAK   DEFAULT   14 _ZNSt7__cxx1112basic_stri
    70: 0000000000204000     0 NOTYPE  WEAK   DEFAULT   24 data_start
    71: 000000000000200a   147 FUNC    GLOBAL DEFAULT   14 _Z12sigsegv_testv
    72: 0000000000002ae5    28 FUNC    WEAK   DEFAULT   14 _ZSt10__distanceIPcENSt15
    73: 0000000000002878    91 FUNC    WEAK   DEFAULT   14 _ZNSt7__cxx1112basic_stri
    74: 0000000000204020   272 OBJECT  GLOBAL DEFAULT   25 _ZSt4cout@GLIBCXX_3.4 (3)
    75: 00000000000028d4    91 FUNC    WEAK   DEFAULT   14 _ZNSt7__cxx1112basic_stri
    76: 0000000000002930   311 FUNC    WEAK   DEFAULT   14 _ZNSt7__cxx1112basic_stri
    77: 0000000000002a83    98 FUNC    WEAK   DEFAULT   14 _ZSt8distanceIPcENSt15ite

可以看到添加 -rdynamic 选项后,.dynsym表就包含了所有的符号,不仅是已使用到的外部动态符号,还包括本程序内定义的符号等。
.dynsym表里的数据并不能被strip掉,即强制strip将导致程序无法执行.

然后重新运行,输出如下

sigsegv_test begin
sigsegvhandle received signal: 11
###### file:vec.cpp line:61 ######
./vec(_Z13sigsegvhandlei+0x107) [0x5623de873486]
/lib/x86_64-linux-gnu/libc.so.6(+0x3f040) [0x7f3658699040]
./vec(_Z12sigsegv_testv+0x53) [0x5623de87305d]
./vec(main+0x24) [0x5623de87357c]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f365867bbf7]
./vec(_start+0x2a) [0x5623de872f2a]
##############################################

可以发现backtrace打印的调用栈已经包含了对应的符号名,
./vec(_Z13sigsegvhandlei+0x107) [0x5623de873486]
但是这个符号名是C++运行时标识名与C++源码中的符号名并不一样;
这是由于符号改编(Name Mangling)的机制造成的。

demangle

将C++ ABI标识符(C++ ABI identifier)转换成C++源程序标识符(original C++ source identifier)的过程称为demangle。
更简单的说,识别C++编译以后的函数名的过程,就叫demangle。

c++filt以及nm,objdump等命令具有demangle的能力。如c++filt命令

$ c++filt _Z13sigsegvhandlei
sigsegvhandle(int)

在libstdc++里关于abi命名空间的文档中(https://gcc.gnu.org/onlinedocs/libstdc++/latest-doxygen/namespaces.html),介绍了GCC所使用的跨厂商(cross-vendor) C++ ABI,
其中暴露的一个函数 abi::__cxa_demangle 就是用于demangling。

//#include <cxxabi.h>
std::string demangle(const char* symbol)
{
    std::string result;
    char temp[128] = { 0 };
    do {
        //try to demangle a c++ name
        if (1 <= sscanf(symbol, "%*[^(]%*[^_]%127[^)+]", temp)) {
            //#include <cxxabi.h>
            size_t size;
            int status;
            char* demangled;
            demangled = abi::__cxa_demangle(temp, NULL, &size, &status);
            if (NULL != demangled) {
                result.assign(demangled);
                free(demangled);
                break;
            }   
        }   
        //try to get a regular c symbol
        if (1 <= sscanf(symbol, "%127s", temp)) {
            result.assign(temp);                                                                              
            break;
        }   

        //just return the symbol
        result.assign(symbol);
    } while (0);
    return result;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-14 22:56:44  更:2021-07-14 22:58:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/20 14:15:34-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码