这里用来记录 博客 , 用来记录一些并不是很系统的知识.
这里的文章都是按照时间倒叙排列.
这里用来记录 博客 , 用来记录一些并不是很系统的知识.
这里的文章都是按照时间倒叙排列.
#include <iostream>
class A
{
public:
A() : c(0)
{
std::cout << "A::A()" << std::endl;
func2();
};
void func2() { std::cout <<"A::func2()" << std::endl; };
private:
int c;
};
int main(void)
{
A a1;
A a2;
return 0;
}
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./a.out...
(gdb) b 22
Breakpoint 1 at 0x11fc: file basic.cpp, line 22.
(gdb) run
Starting program: /mnt/work/cs-note-src/src/cpp/memory/a.out
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
A::A()
A::func2()
A::A()
A::func2()
Breakpoint 1, main () at basic.cpp:22
22 return 0;
(gdb) info vtbl a1
This object does not have a virtual function table
(gdb)
This object does not have a virtual function table
(gdb) p a
$1 = {i = {0, 1045149306}, x = 1.2904777690891933e-08, d = 1.2904777690891933e-08}
(gdb) p a1
$2 = {c = 0}
(gdb) p a2
$3 = {c = 0}
(gdb) p &a1
$4 = (A *) 0x7fffffffe250
(gdb) p &a2
$5 = (A *) 0x7fffffffe254
(gdb) p typeid(a1)
could not find typeinfo symbol for 'A'
(gdb) info address A::func2
Symbol "A::func2()" is a function at address 0x5555555552da.
(gdb) info symbo A::func2
A::func2() in section .text of /mnt/work/cs-note-src/src/cpp/memory/a.out
(gdb)
```sh
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./virtual...
(gdb) b 24
Breakpoint 1 at 0x11fc: file virtual.cpp, line 24.
(gdb) run
Starting program: /mnt/work/cs-note-src/src/cpp/memory/virtual
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
A::A()
A::func2()
A::A()
A::func2()
Breakpoint 1, main () at virtual.cpp:24
24 return 0;
(gdb) p a1
$1 = {_vptr.A = 0x555555557d68 <vtable for A+16>, c = 0}
(gdb) p &a1
$2 = (A *) 0x7fffffffe230
(gdb) x /2xg 0x7fffffffe230
0x7fffffffe230: 0x0000555555557d68 0x0000000000000000
(gdb) info vtbl a1
vtable for 'A' @ 0x555555557d68 (subobject @ 0x7fffffffe230):
[0]: 0x5555555552ea <A::virtual_func1()>
[1]: 0x555555555366 <A::virtual_func3()>
(gdb) p typeid(a1)
$3 = {_vptr.type_info = 0x7ffff7fa2fa0 <vtable for __cxxabiv1::__class_type_info+16>, __name = 0x55555555603c <typeinfo name for A> "1A"}
(gdb) p sizeof(typeid(a1))
$4 = 16
(gdb) p &typeid(a1)
$5 = (gdb_gnu_v3_type_info *) 0x555555557d78 <typeinfo for A>
(gdb) x /4xg 0x555555557d68
0x555555557d68 <_ZTV1A+16>: 0x00005555555552ea 0x0000555555555366
0x555555557d78 <_ZTI1A>: 0x00007ffff7fa2fa0 0x000055555555603c
0x555555557d88: 0x0000000000000001 0x0000000000000169
0x555555557d98: 0x0000000000000001 0x0000000000000178
0x555555557d80 0x000055555555603c
0x555555557d78 0x00007ffff7fa2fa0
0x555555557d70 0x0000555555555366
0x555555557d68 0x00005555555552ea
#include <iostream>
class A
{
public:
A() : c(0)
{
std::cout << "A::A()" << std::endl;
func2();
};
virtual void virtual_func1() { std::cout << "A::virtual_func1()" << std::endl;};
void func2() { std::cout <<"A::func2()" << std::endl; };
virtual void virtual_func3() { std::cout << "A::virtual_func3()" << std::endl;};
private:
int c;
};
class B : public A
{
public:
B() : a(0), b(0)
{
std::cout << "B::B()" << std::endl;
func2();
}
B(int a, int b) : a(a), b(b)
{
std::cout << "B::B(int a, int b)" << std::endl;
func2();
}
~B() { std::cout << "B::~B()" << std::endl; };
virtual void virtual_fun1() { std::cout <<"B::virtual_fun1()" << std::endl; };
void func2() { std::cout <<"B::func2()" << std::endl; };
virtual void virtual_func3() { std::cout << "B::virtual_func3()" << std::endl;};
private:
int a;
int b;
};
int main(void)
{
A a1;
A a2;
B b1;
B b2;
return 0;
}
(gdb) b 48
Breakpoint 1 at 0x1235: file ./inheritance.cpp, line 48.
(gdb) run
Starting program: /mnt/work/cs-note-src/src/cpp/memory/inheritance
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
A::A()
A::func2()
A::A()
A::func2()
A::A()
A::func2()
B::B()
B::func2()
A::A()
A::func2()
B::B()
B::func2()
Breakpoint 1, main () at ./inheritance.cpp:48
48 return 0;
(gdb) p b1
$1 = {<A> = {_vptr.A = 0x555555557d10 <vtable for B+16>, c = 0}, a = 0, b = 0}
(gdb) p b2
$2 = {<A> = {_vptr.A = 0x555555557d10 <vtable for B+16>, c = 0}, a = 0, b = 0}
(gdb) p &b1
$3 = (B *) 0x7fffffffe210
(gdb) p &b2
$4 = (B *) 0x7fffffffe230
(gdb) p typeid(b1)
$5 = {_vptr.type_info = 0x7ffff7fa3c30 <vtable for __cxxabiv1::__si_class_type_info+16>, __name = 0x55555555607b <typeinfo name for B> "1B"}
(gdb) p typeid(b2)
$6 = {_vptr.type_info = 0x7ffff7fa3c30 <vtable for __cxxabiv1::__si_class_type_info+16>, __name = 0x55555555607b <typeinfo name for B> "1B"}
(gdb) info vtbr b1
Undefined info command: "vtbr b1". Try "help info".
(gdb) info vtbl b1
vtable for 'B' @ 0x555555557d10 (subobject @ 0x7fffffffe210):
[0]: 0x555555555362 <A::virtual_func1()>
[1]: 0x55555555555e <B::virtual_func3()>
[2]: 0x5555555554e2 <B::virtual_fun1()>
(gdb) p &typeid(b1)
$7 = (gdb_gnu_v3_type_info *) 0x555555557d48 <typeinfo for B>
(gdb) x /16xg 0x555555557d10
0x555555557d10 <_ZTV1B+16>: 0x0000555555555362 0x000055555555555e
0x555555557d20 <_ZTV1B+32>: 0x00005555555554e2 0x0000000000000000
0x555555557d30 <_ZTV1A+8>: 0x0000555555557d60 0x0000555555555362
0x555555557d40 <_ZTV1A+24>: 0x00005555555553de 0x00007ffff7fa3c30
0x555555557d50 <_ZTI1B+8>: 0x000055555555607b 0x0000555555557d60
0x555555557d60 <_ZTI1A>: 0x00007ffff7fa2fa0 0x000055555555607e
0x555555557d70: 0x0000000000000001 0x00000000000001b6
0x555555557d80: 0x0000000000000001 0x00000000000001c5
0x555555557d68 0x000055555555607e --> typeid(a) __name = 1A
0x555555557d60 0x00007ffff7fa2fa0 --> typeid(a) _vptr.type_info
0x555555557d58 0x0000555555557d60
0x555555557d50 0x000055555555607b --> typeid(b) __name = 1B
0x555555557d48 0x00007ffff7fa3c30 --> typeid(b) _vptr.type_info
0x555555557d40 0x00005555555553de --> A::virtual_func3()
0x555555557d38 0x0000555555555362 --> A::virtual_func1()
0x555555557d30 0x0000555555557d60
0x555555557d28 0x0000000000000000
0x555555557d20 0x00005555555554e2 --> B::virtual_fun1()
0x555555557d18 0x000055555555555e --> B::virtual_func3()
0x555555557d10 0x0000555555555362 --> A::virtual_func1()
| 显示类命令 | 缩写 | 命令说明 |
|---|---|---|
| info | i | 查看断点 / 线程等信息 |
| p | 打印变量或寄存器值 | |
| display | display | 自动显示命令 |
| whatis | whatis | 查看变量类型 |
| ptype | ptype | 查看变量类型 |
| list | l | 显示源码 |
| disassemble | dis | 查看汇编代码 |
| backtrace | bt | 查看当前线程的调用堆栈 |
| help | help | 帮助命令 |
| 控制类型的命令 | 缩写 | 命令说明 |
|---|---|---|
| run | r | 运行一个待调试的程序 |
| continue | c | 让暂停的程序继续运行 |
| next | n | 运行到下一行 |
| step | s | 单步执行,遇到函数会进入 |
| until | u | 运行到指定行停下来 |
| finish | fi | 结束当前调用函数,回到上一层调用函数处 |
| return | return | 结束当前调用函数并返回指定值,到上一层函数调用处 |
| jump | j | 将当前程序执行流跳转到指定行或地址 |
| 断点监视点 | 缩写 | 命令说明 |
|---|---|---|
| break | b | 添加断点 |
| tbreak | tb | 添加临时断点 |
| delete | d | 删除断点 |
| enable | enable | 启用某个断点 |
| disable | disable | 禁用某个断点 |
| watch | watch | 监视某一个变量或内存地址的值是否发生变化 |
| 名称 | 缩写 | 命令说明 |
|---|---|---|
| frame | f | 切换到当前调用线程的指定堆栈 |
| thread | thread | 切换到指定线程 |
| set args | set args | 设置程序启动命令行参数 |
| show args | show args | 查看设置的命令行参数 |
调试目标程序
gdb ./hello_world
调试一个正在运行的程序
# 启动时直接attach到一个进程
gdb -p PID
# 在gdb环境中 attach一个进程
(gdb) attach PID
调试 core 文件:
gdb ./program_name corename
退出命令:q(quit)或者 Ctr + d 启动程序:r (run) 继续运行:c (continue) 查看调用栈:bt (backtrace) 进入调用栈:f (frame)堆栈编号 单步执行:n(next),遇到函数直接跳过,不进入函数内部 单步执行:s(step),会进入函数内部 退出当前函数:finish,继续执行完当前函数,正常退出 返回当前函数:return,从当前位置,从当前函数退出,剩下的代码不执行了,可以指定函数的返回值; 指定位置停下来:until,和 break 类似 查看某段代码的汇编指令:disassemble 帮助命令:help
程序运行到某一行
b test.cpp 100
程序运行到某一个函数
b namespace::func
其他文件设置
b a.cpp:441
其他文件的某一个函数
b a.cpp:func
设置条件断点
b if i = 8
info b
删除第几个断点
delete N
删除第几行的断点
clear N
使能第 N 个断点
enable N
不使能第 N 个断点
disable N
单个变量
watch i
指针变量,监视的是指针变量本身,
watch p
指针所指向的变量,监视指针所值的内容:
watch *p
数组,可以监视整个数组的内容
watch a
注意:当监视的变量变化时会自动停下来 当监视的变量脱离了作用域,也就失效了;
调用函数
p func()
调试过程中修改变量的值
print var=value
计算表达式并打印
print a+b+c
输出当前对象的各成员变量的值
print *this
查看变量类型
whatis var
查看变量类型,可以查看复合数据类型,会打印出该类型的成员变量
ptype val
打印 std::string
打印内容
p str
p str.c_str()
打印长度
p str.length()
p str.size()
字符串太长
set print elements 0
list 命令输出上一次list命令显示的代码后面的代码,如果是第一次执行list命令,则会显示当前正在执行代码位置附近的代码;
list
上一次 list 命令显示的代码前面的代码
list -
显示当前代码文件第 Linenumber 行附近的代码;
list Linenumber
显示 FileName 文件第 LineNo 行附近的代码
list FileName:Linenumber
显示当前文件的 FunctionName 函数附近的代码
list FunctionName
显示 FileName 文件的 FunctionName 函数附件的代码
list FileName:FunctionName
其中from和to是具体的代码位置,显示这之间的代码
list from,to
jump 命令跳转到第几行
jump line_number
跳转往下 10行
jump + 10
跳转到具体的内存地址
jump *0X12345678
基本用法
display expression
跟踪变量值: 如果您正在调试一个C程序,并且想观察变量foo的值随程序执行的变化,可以这样设置:
(gdb) display foo
显示内存地址内容: 若要跟踪特定内存地址(如0x12345678)的内容:
(gdb) display *0x12345678
跟踪表达式结果: 如果您关心的是某个计算结果或复杂表达式的值,比如变量a与b之和:
(gdb) display a + b
格式化输出: 可以使用/fmt选项来指定显示值的格式,类似于printf函数的格式化字符串。例如,以十六进制显示整数:
(gdb) display/x foo
或以浮点数的科学记数法显示:
(gdb) display/g foo
查看GDB文档或使用help format命令获取更多关于格式化选项的信息。
查看已设置的display项: 如果您设置了多个display命令,可以使用info display命令列出所有当前生效的自动显示项及其编号:
(gdb) info display
取消自动显示: 要取消之前设置的某个display项,使用undisplay命令并指定其编号:
(gdb) undisplay 1
上述命令将取消编号为1的display设置。
临时禁用所有自动显示: 如果想暂时关闭所有自动显示,而不取消它们的设置,可以使用disable display命令:
(gdb) disable display
后续可以通过enable display命令重新启用所有自动显示。
查看即将执行的汇编指令: 在调试汇编程序时,使用display/i $pc命令可以显示每次程序暂停时即将执行的汇编指令:
(gdb) display/i $pc
$pc是GDB的内部变量,代表当前程序计数器的值,即下一条要执行的指令地址。
gdb ./program core
bt
where
f 3
查看所有线程信息
info thread
切换线程
thread thread_number
查看所有线程的栈信息
thread apply all bt
在特定线程编号上打断点
break location thread thread_number
通用的命令, 可以用 all,作用禹全部线程
thread apply thread_number1 thread_number2 ... command
non-stop 模式
gdb -exec-run-mode non-stop your_program
记录模式
gdb -record record_file_name your_program
回放模式
gdb -replay record_file_name
默认的调试模式:一个线程暂停运行,其他线程也随即暂停;一个线程启动运行,其他线程也随即启动
但在一些场景中,我们希望只让特定线程运行,其他线程都维持在暂停状态,即要防止线程切换。
和 shell 一样,有一些简便操作:
Reference:
公司的守护进程的优雅关闭,是通过捕获 SIGTERM 实现的:
SIGTERMpthread_exit 退出出现的问题是:优雅关闭时,进程又收到了 SIGABRT ,导致进程又被异常杀死了,最后进程并没有优雅的关闭。
当然在公司定位这个问题的过程中,都是通过日志和 gdb 工具来进行的。
现在我们模拟一下解决这个问题的过程,为了方便起见,就使用打印和 gdb 来进行debug
模拟的问题代码如下:
#include <iostream>
#include <pthread.h>
#include <signal.h>
void printSigInfo(int signo, siginfo_t* info, void* context)
{
std::cout << "Signal Info:" << std::endl;
std::cout << " si_signo: " << info->si_signo << " - Signal number" << std::endl;
std::cout << " si_code: " << info->si_code << " - Signal code" << std::endl;
std::cout << " si_errno: " << info->si_errno << " - Errno value associated with signal" << std::endl;
if (info->si_code > 0 && info->si_code < NSIG) { // 可能需要针对具体情况进行检查
std::cout << " si_pid: " << info->si_pid << " - Sending process ID" << std::endl;
std::cout << " si_uid: " << info->si_uid << " - Real user ID of sending process" << std::endl;
if (info->si_addr) {
std::cout << " si_addr: " << static_cast<void*>(info->si_addr) << " - Faulting address" << std::endl;
}
}
}
void LinuxSigHandler(int signo, siginfo_t* info, void* context)
{
switch (signo) {
case SIGABRT:
std::cout << "Caught signal SIGABRT (" << signo << ")." << std::endl;
printSigInfo (signo, info, context);
break;
case SIGTERM:
std::cout << "Caught signal SIGTERM (" << signo << ")." << std::endl;
printSigInfo (signo, info, context);
pthread_exit(0);
break;
default:
std::cout << "Caught signal " << signo << std::endl;
break;
}
// 重新引发信号
raise(signo);
}
void InitSignalHandlers(void)
{
struct sigaction action;
action.sa_handler = NULL;
action.sa_sigaction = LinuxSigHandler;
sigemptyset (&action.sa_mask);
action.sa_flags = (typeof(action.sa_flags))(SA_RESTART | SA_SIGINFO | SA_RESETHAND);
sigaction (SIGSEGV, &action, NULL);
sigaction (SIGILL, &action, NULL);
sigaction (SIGFPE, &action, NULL);
sigaction (SIGBUS, &action, NULL);
sigaction (SIGABRT, &action, NULL);
sigaction (SIGTERM, &action, NULL);
/* Ignored signals. */
struct sigaction action_ignore;
action_ignore.sa_handler = SIG_IGN;
action_ignore.sa_flags = 0;
sigemptyset (&action_ignore.sa_mask);
sigaction (SIGPIPE, &action_ignore, NULL);
}
void main_thread()
{
for (;;)
{
std::cout << "main_thread is running:" << std::endl;
sleep(10);
}
}
int main()
{
InitSignalHandlers();
try {
main_thread();
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
catch (...) {
std::cerr << "Caught unknown exception" << std::endl;
}
std::cerr << "process close" << std::endl;
return 0;
}
我们先来观察一段这段代码,它有如下特点:
sigaction 函数修改了与指定信号的相关联的处理动作SIGTERM: 收到这个信号,做打印并调用了 pthread_exitSIGABRT: 收到这个信号,做打印;SIGTERM 时,它应该有打印,并优雅的退出,不应该再有其他的打印。我们如何测试呢?
kill -SIGTERM 实现。公司的系统优雅的关闭就是通过 systemd 发送 SIGTERM 实现的。现在我们来做一个测试吧!
我们来以这段代码做个测试,编译这段代码得到可执行文件 abi
g++ abi.cpp -o abi
在窗口 1 运行它
./abi
main_thread is running:
main_thread is running:
在窗口 2 里面执行:
ps -ef | grep abi
tlj 980622 976483 0 08:36 pts/0 00:00:00 ./abi
$ kill -SIGTERM 980622
观察窗口 1:
./abi
main_thread is running:
main_thread is running:
Caught signal SIGTERM (15).
Signal Info:
si_signo: 15 - Signal number
si_code: 0 - Signal code
si_errno: 0 - Errno value associated with signal
Caught unknown exception
FATAL: exception not rethrown
Caught signal SIGABRT (6).
Signal Info:
si_signo: 6 - Signal number
si_code: -6 - Signal code
si_errno: 0 - Errno value associated with signal
Aborted (core dumped)
??? 怎么又收到了一个 SIGABRT ,还异常关闭了?
从打印可以看到
Caught unknown exception
这里捕获了一个异常,那么按照正常的逻辑,它应该继续往下执行 std::cerr << "process close" << std::endl; 才对,结果它没有往下执行,到这里就收到了 SIGABRT.
十分怪异!
那我们先来看一下 core dump 看看发生了什么?
先启用coredump
ulimit -c unlimited
echo "/var/crash/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
用 gdb 看一下 core 的情况:
root@ubuntu:/mnt/work/cs-note-src/src/cpp/debug# gdb ./abi core_abi_2262_1712044154
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./abi...
[New LWP 2262]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `./abi'.
Program terminated with signal SIGABRT, Aborted.
#0 __pthread_kill_implementation (no_tid=0, signo=6, threadid=139802159551424) at ./nptl/pthread_kill.c:44
44 ./nptl/pthread_kill.c: No such file or directory.
(gdb)
(gdb) bt
#0 __pthread_kill_implementation (no_tid=0, signo=6, threadid=139802159551424) at ./nptl/pthread_kill.c:44
#1 __pthread_kill_internal (signo=6, threadid=139802159551424) at ./nptl/pthread_kill.c:78
#2 __GI___pthread_kill (threadid=139802159551424, signo=signo@entry=6) at ./nptl/pthread_kill.c:89
#3 0x00007f263a21b476 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4 0x00007f263a2017f3 in __GI_abort () at ./stdlib/abort.c:79
#5 0x00007f263a2623dc in __libc_message (action=do_abort, fmt=0x7f263a3b479c "%s", fmt=0x7f263a3b479c "%s", action=do_abort)
at ../sysdeps/posix/libc_fatal.c:155
#6 0x00007f263a2626f0 in __GI___libc_fatal (message=<optimized out>) at ../sysdeps/posix/libc_fatal.c:164
#7 0x00007f263a2763f6 in unwind_cleanup (reason=<optimized out>, exc=<optimized out>) at ./nptl/unwind.c:114
#8 0x0000562cea54094f in main () at new_abi.cpp:85
(gdb)
从这里看出来似乎就是从 main 函数发生了错误,剩下的调用栈都是库函数了。
到这里就没有思路了,main 哪里写的有问题?
在 catch(...) 的时候出错了,我们去掉,重新测试,还真没有了异常!
root@ubuntu:/mnt/work/cs-note-src/src/cpp/debug# ./abi
main_thread is running:
Caught signal SIGTERM (15).
Signal Info:
si_signo: 15 - Signal number
si_code: 0 - Signal code
si_errno: 0 - Errno value associated with signal
那我们基本就知道了,是 catch(...) 带来的这个问题。那么究竟是怎么带来的呢?
那我们就要了解 C++ stack unwinding
堆栈展开是在运行时从函数调用堆栈中删除函数条目的过程。局部对象以与构建它们的相反顺序被销毁。
堆栈展开通常与异常处理有关。在 C++ 中,当发生异常时,将线性搜索函数调用堆栈以查找异常处理程序,并且从函数调用堆栈中删除具有异常处理程序的函数之前的所有条目。因此,如果在同一函数(抛出异常的位置)中未处理异常,则异常处理涉及堆栈展开。基本上,堆栈展开是为运行时构造的所有自动对象调用析构函数(每当引发异常时)的过程。
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class A
{
public:
A (int a) : a(a) { cout << "A::A(): " << a << endl; }
virtual ~A() { cout << "A::~A(): " << a << endl; }
void disp() { cout <<"A disp" << endl; }
private:
int a;
};
class B : public A
{
public:
B (int a, const string &b) : A(a), b(b) { cout << "B::B(): " << b << endl; }
~B() { cout << "B::~B(): " << b << endl; }
void disp() { cout <<"B disp" << endl; }
private:
int a;
string b;
};
void f1()
{
cout << "f1() Start " << endl;
B b1(1, "stack object");
A *P = new B(2, "heap object");
shared_ptr<A> p2 = make_shared<B>(3, "shared ptr object");
throw 100;
cout << "f1() End " << endl;
}
void f2()
{
cout << "f2() Start " << endl;
f1();
cout << "f2() End " << endl;
}
void f3()
{
cout << "f3() Start " << endl;
try {
f2();
}
catch (int i) {
cout << "Caught Exception: " << i << endl;
}
cout << "f3() End" << endl;
}
int main()
{
f3();
return 0;
}
f3() Start
f2() Start
f1() Start
A::A(): 1
B::B(): stack object
A::A(): 2
B::B(): heap object
A::A(): 3
B::B(): shared ptr object
B::~B(): shared ptr object
A::~A(): 3
B::~B(): stack object
A::~A(): 1
Caught Exception: 100
f3() End
当异常发生的时候,如果没有异常处理程序,这些局部变量一个一个去销毁的。需要注意的是 p2 ,当异常发生时,没有发生析构,这说明,这里会有资源泄露,所以当在实际项目中,需要注意到这一点。
现在我们知道了异常处理的过程,那么我们的测试程序究竟捕获了什么异常呢?为什么又导致了 SIGABRT 呢?
从调用栈,我们看到 main 函数调用的是 nptl/unwind.c 中的 unwind_cleanup 函数,咱们先看看这个函数到底是怎么回事?
// https://github.com/lattera/glibc/blob/master/nptl/unwind.c
static void
unwind_cleanup (_Unwind_Reason_Code reason, struct _Unwind_Exception *exc)
{
/* When we get here a C++ catch block didn't rethrow the object. We
cannot handle this case and therefore abort. */
__libc_fatal ("FATAL: exception not rethrown\n");
}
看这里的注释
“当程序运行到这里,那就是 C++ catch 块没有重新抛出对象。我们无法处理这种情况,所以触发 abort”
那么就可以明白了,我们 catch 了一个异常,但是没有重新抛出,所以 glibc 出发了 abort。
那么我们 catch 的这个异常到底是什么呢?从我们的程序可以看出,我们只是调用了 pthread_exit, pthread 本身是应该只是 C 库,并不是 C++库,它不应该抛出异常啊。
这和 glibc 和 gcc 的实现相关!
pthread_exit 或者 pthread_cancel 都会抛出一个 ___forced_unwind 异常,用于在线程退出期间展开堆栈 (stack unwinding). 当程序捕获了这个异常,需要务必重新抛出它,以便系统可以做完整的 stack unwinding。
如果捕获了这个异常,那么栈展开就没办法继续进行下去了,所以 glibc 就会调用系统调用 abort 函数来进行 SIGABRT 异常处理。
知道了真正原因,那么修改我们原有的程序,让它优雅的关闭吧
int main()
{
InitSignalHandlers();
try {
main_thread();
} catch (const std::exception& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
catch (abi::_forced_unwind&) {
std::cerr << "Caught forced_unwid error and retrow it" << std::endl;
throw;
}
catch (...) {
std::cerr << "Caught unknown exception" << std::endl;
}
std::cerr << "process close" << std::endl;
return 0;
}
测试一下,成功了,程序可以优雅关闭了!
main_thread is running:
main_thread is running:
Caught signal SIGTERM (15).
Signal Info:
si_signo: 15 - Signal number
si_code: 0 - Signal code
si_errno: 0 - Errno value associated with signal
Caught forced_unwid error and retrow it
Reference:
| abbr | english | chinese |
|---|---|---|
| DP | Debug Port | 调试端口 |
| MMU | Memory Management Unit | 内存管理单元 |
| EXI | External Interface | 外部接口 |
| XIF | Crossbar Interface | 交叉开关接口 |
| TM | Traffic Manager | 流量管理器 |
| TM OQ | Traffic Manager Output Queue | 流量管理器输出队列 |
| TM POL | Traffic Manager Policy | 流量管理器策略 |
| TM SCH | Traffic Manager Scheduler | 流量管理器调度器 |
| PSTAT | Port Status | 端口状态 |
| ETH | Ethernet | 以太网 |
| ETH MAC | Ethernet Media Access Control | 以太网介质访问控制 |
| ETH PCS | Ethernet Physical Coding Sublayer | 以太网物理编码子层 |
| ETH ADAPT | Ethernet Adaptation | 以太网适配 |
| RND | Random | |
| intr | interrupt | 中断 |
| ECC | Error Correcting Code | 纠错码 |
| SRAM | Static Random Access Memory | 静态随机存取存储器 |
| DRAM | Dynamic Random Access Memory | 动态随机存取存储器 |
int ioctl(int fd, unsigned long op, ...); /* glibc, BSD */