一、异常终止
在C++中,如果有一个异常没有被任何人捕捉,此时默认的处理是将进程终止掉,终止的时候使用的信号是sigabrt。好在内核对于这种信号的默认处理是会生成一个coredump文件,对于一些服务器来说,通过core文件可以知道当时的进程信息,如果附带了调试信息,那么调用的堆栈信息清晰可见。
理想总是丰满的,但是现实还是骨干的。有些时候这些文件的coredump文件对应的源文件已经不存在,或者即使存在,此时运行的版本和当前我们看到的版本已经不同,如果此时再继续使用源文件来分析这个问题,那就是刻舟求剑的最好注脚了。
之前遇到的一个情况是出现异常的进程可执行文件的源代码大体结构还在,但是中间毕竟进行了多次调整,在异常调用链中throw异常的函数中,使用非常工整的排比式throw,也就是if xxx throw Exception(args) else if yyy throw Exception,这些异常的类型相同,但是结构的成员中包含了我们在最为关心的字符提示以及一些错误码提示问题。
二、gcc对异常结构的基本接口及流程
gcc对异常的处理分为两个步骤,一个是为抛出的异常对象之前建立一个内部使用的__cxa_exception结构,然后将这个结构和抛出的对象对象拼接成一个g++内部使用的结构,传递给__cxa_throw函数,从g++的throw函数实现来看,g++对于通过内部throw接口
extern "C" void
__cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo,
void (*dest) (void *))
抛出的异常,第一个参数必须是一个结构指针,这个指针指向的是抛出的对象实例,而在指针的连续的低地址位置,其中包含着一个异常处理头结构,也就是对我们透明的,但是底层实现中约定的统一接口__cxa_exception结构。这里可以推导出一个问题,throw的所有变量都是在堆栈上分配独立的地址空间,然后执行构造函数,之后再抛出异常;不论抛出的是全局变量,还是临时变量,新throw类型的空间都在堆栈上单独分配,分配了之后再执行构造函数,然后抛送给__cxa_throw函数执行。
而这个通用的__cxa_exception结构则由调用_cxa_throw函数之前由编译器通过__cxa_allocate_exception申请,这个函数接受的是throw结构大小,然后它在throw的基础上加上exception的大小,偏移指针之后将地址返回给用户。这种方法是不是似曾相识呢?因为几乎所有的动态内存分配都是通过这种方法实现的,所有的malloc分配的空间都会在结构的开始加上结构的长度信息。
再看一下throw函数的原型
extern "C" void
__cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo,
void (*dest) (void *))
第一个参数即throw的类型,第二个表示throw类型的动态类型识别信息,第三个表示该对象的析构函数地址,如果没有析构函数,最后一个参数可以为空。
三、如何还原
有了这些信息,可以从throw函数的参数中找到所有的信息,还原异常结构。也就是首先通过第二个参数找到对应的类型信息,然后将第一个参数转换为该类型的指针显示内存结构即可。
1、演示代码
[root@Harry throwtype]# cat throwtype.cpp
#include <stdio.h>
#include <typeinfo>
struct base
{
int m_holder;
int m_holder2;
base():m_holder(0x12345678){}
};
struct derive : public base
{
virtual int vfun(){}
};
struct nvdctor: public base
{
~nvdctor(){}
};
nvdctor gnvdctor;
int baz()
{
throw gnvdctor;
}
int bar()
{
throw nvdctor();
}
int foo()
{
throw(derive());
}
int main()
{
baz();
throw(base());
}
2、触发异常
[root@Harry throwtype]# g++ throwtype.cpp -g
[root@Harry throwtype]# ./a.out
terminate called after throwing an instance of 'nvdctor'
Aborted (core dumped)
[root@Harry throwtype]# gdb -c core_20199_6_0 a.out
GNU gdb (GDB) Fedora (7.0-3.fc12)
Copyright (C) 2009 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 "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/tsecer/CodeTest/throwtype/a.out...done.
Reading symbols from /usr/lib/libstdc++.so.6...(no debugging symbols found)...done.
Loaded symbols for /usr/lib/libstdc++.so.6
Reading symbols from /lib/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libm.so.6
Reading symbols from /lib/libgcc_s.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/libgcc_s.so.1
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 6, Aborted.
#0 0x00a30424 in __kernel_vsyscall ()
Missing separate debuginfos, use: debuginfo-install glibc-2.11.2-3.i686 libgcc-4.4.2-7.fc12.i686 libstdc++-4.4.2-7.fc12.i686
(gdb) bt
#0 0x00a30424 in __kernel_vsyscall ()
#1 0x00234b91 in raise () from /lib/libc.so.6
#2 0x0023646a in abort () from /lib/libc.so.6
#3 0x054269ff in __gnu_cxx::__verbose_terminate_handler() ()
from /usr/lib/libstdc++.so.6
#4 0x054246f6 in ?? () from /usr/lib/libstdc++.so.6
#5 0x05424733 in std::terminate() () from /usr/lib/libstdc++.so.6
#6 0x05424872 in __cxa_throw () from /usr/lib/libstdc++.so.6
#7 0x08048623 in baz () at throwtype.cpp:22
#8 0x080486c1 in main () at throwtype.cpp:35
(gdb) frame 6
#6 0x05424872 in __cxa_throw () from /usr/lib/libstdc++.so.6
(gdb) x/10x $ebp
0xbfb7af28: 0xbfb7af48 0x08048623 0x0830b068 0x080488a0
0xbfb7af38: 0x08048770 0x080484bc 0xbfb7af88 0x08049c60
0xbfb7af48: 0xbfb7af78 0x080486c1
(gdb) x/10x 0x080488a0
0x80488a0 <_ZTI7nvdctor>: 0x08049ce8 0x08048894 0x0804888c 0x72656436
0x80488b0 <_ZTS6derive+4>: 0x00657669 0x08049d28 0x080488ac 0x00000000
0x80488c0 <_ZTI6derive+12>: 0x00000001 0x0804888c
(gdb) shell c++filt _ZTI7nvdctor
typeinfo for nvdctor
(gdb) p *(nvdctor*) 0x0830b068
$1 = {<base> = {m_holder = 305419896, m_holder2 = 0}, <No data fields>}
(gdb)
3、对应反汇编代码
有兴趣的同学可以看一下,所有需要的东西应该都有了,也省得大家自己写测试代码验证
[root@Harry throwtype]# objdump -dr throwtype.o | c++filt
throwtype.o: file format elf32-i386
Disassembly of section .text:
00000000 <baz()>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 18 sub $0x18,%esp
6: c7 04 24 08 00 00 00 movl $0x8,(%esp)
d: e8 fc ff ff ff call e <baz()+0xe>
e: R_386_PC32 __cxa_allocate_exception
12: 89 c2 mov %eax,%edx
14: 89 d0 mov %edx,%eax
16: 8b 0d 00 00 00 00 mov 0x0,%ecx
18: R_386_32 gnvdctor
1c: 89 08 mov %ecx,(%eax)
1e: 8b 0d 04 00 00 00 mov 0x4,%ecx
20: R_386_32 gnvdctor
24: 89 48 04 mov %ecx,0x4(%eax)
27: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
2e: 00
2b: R_386_32 nvdctor::~nvdctor()
2f: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
36: 00
33: R_386_32 typeinfo for nvdctor
37: 89 14 24 mov %edx,(%esp)
3a: e8 fc ff ff ff call 3b <baz()+0x3b>
3b: R_386_PC32 __cxa_throw
0000003f <bar()>:
3f: 55 push %ebp
40: 89 e5 mov %esp,%ebp
42: 53 push %ebx
43: 83 ec 14 sub $0x14,%esp
46: c7 04 24 08 00 00 00 movl $0x8,(%esp)
4d: e8 fc ff ff ff call 4e <bar()+0xf>
4e: R_386_PC32 __cxa_allocate_exception
52: 89 c3 mov %eax,%ebx
54: 89 d8 mov %ebx,%eax
56: c7 00 00 00 00 00 movl $0x0,(%eax)
5c: c7 40 04 00 00 00 00 movl $0x0,0x4(%eax)
63: 89 04 24 mov %eax,(%esp)
66: e8 fc ff ff ff call 67 <bar()+0x28>
67: R_386_PC32 nvdctor::nvdctor()
6b: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
72: 00
6f: R_386_32 nvdctor::~nvdctor()
73: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
7a: 00
77: R_386_32 typeinfo for nvdctor
7b: 89 1c 24 mov %ebx,(%esp)
7e: e8 fc ff ff ff call 7f <bar()+0x40>
7f: R_386_PC32 __cxa_throw
00000083 <foo()>:
83: 55 push %ebp
84: 89 e5 mov %esp,%ebp
86: 53 push %ebx
87: 83 ec 14 sub $0x14,%esp
8a: c7 04 24 0c 00 00 00 movl $0xc,(%esp)
91: e8 fc ff ff ff call 92 <foo()+0xf>
92: R_386_PC32 __cxa_allocate_exception
96: 89 c3 mov %eax,%ebx
98: 89 d8 mov %ebx,%eax
9a: c7 00 00 00 00 00 movl $0x0,(%eax)
a0: c7 40 04 00 00 00 00 movl $0x0,0x4(%eax)
a7: c7 40 08 00 00 00 00 movl $0x0,0x8(%eax)
ae: 89 04 24 mov %eax,(%esp)
b1: e8 fc ff ff ff call b2 <foo()+0x2f>
b2: R_386_PC32 derive::derive()
b6: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
bd: 00
be: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
c5: 00
c2: R_386_32 typeinfo for derive
c6: 89 1c 24 mov %ebx,(%esp)
c9: e8 fc ff ff ff call ca <foo()+0x47>
ca: R_386_PC32 __cxa_throw
000000ce <main>:
ce: 55 push %ebp
cf: 89 e5 mov %esp,%ebp
d1: 83 e4 f0 and $0xfffffff0,%esp
d4: 53 push %ebx
d5: 83 ec 1c sub $0x1c,%esp
d8: e8 fc ff ff ff call d9 <main+0xb>
d9: R_386_PC32 baz()
dd: c7 04 24 08 00 00 00 movl $0x8,(%esp)
e4: e8 fc ff ff ff call e5 <main+0x17>
e5: R_386_PC32 __cxa_allocate_exception
e9: 89 c3 mov %eax,%ebx
eb: 89 d8 mov %ebx,%eax
ed: 89 04 24 mov %eax,(%esp)
f0: e8 fc ff ff ff call f1 <main+0x23>
f1: R_386_PC32 base::base()
f5: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
fc: 00
fd: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
104: 00
101: R_386_32 typeinfo for base
105: 89 1c 24 mov %ebx,(%esp)
108: e8 fc ff ff ff call 109 <main+0x3b>
109: R_386_PC32 __cxa_throw
0000010d <__static_initialization_and_destruction_0(int, int)>:
10d: 55 push %ebp
10e: 89 e5 mov %esp,%ebp
110: 83 ec 18 sub $0x18,%esp
113: 83 7d 08 01 cmpl $0x1,0x8(%ebp)
117: 75 32 jne 14b <__static_initialization_and_destruction_0(int, int)+0x3e>
119: 81 7d 0c ff ff 00 00 cmpl $0xffff,0xc(%ebp)
120: 75 29 jne 14b <__static_initialization_and_destruction_0(int, int)+0x3e>
122: c7 04 24 00 00 00 00 movl $0x0,(%esp)
125: R_386_32 gnvdctor
129: e8 fc ff ff ff call 12a <__static_initialization_and_destruction_0(int, int)+0x1d>
12a: R_386_PC32 nvdctor::nvdctor()
12e: b8 00 00 00 00 mov $0x0,%eax
12f: R_386_32 nvdctor::~nvdctor()
133: c7 44 24 08 00 00 00 movl $0x0,0x8(%esp)
13a: 00
137: R_386_32 __dso_handle
13b: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp)
142: 00
13f: R_386_32 gnvdctor
143: 89 04 24 mov %eax,(%esp)
146: e8 fc ff ff ff call 147 <__static_initialization_and_destruction_0(int, int)+0x3a>
147: R_386_PC32 __cxa_atexit
14b: c9 leave
14c: c3 ret
0000014d <global constructors keyed to gnvdctor>:
14d: 55 push %ebp
14e: 89 e5 mov %esp,%ebp
150: 83 ec 18 sub $0x18,%esp
153: c7 44 24 04 ff ff 00 movl $0xffff,0x4(%esp)
15a: 00
15b: c7 04 24 01 00 00 00 movl $0x1,(%esp)
162: e8 a6 ff ff ff call 10d <__static_initialization_and_destruction_0(int, int)>
167: c9 leave
168: c3 ret
Disassembly of section .text._ZN4baseC2Ev:
00000000 <base::base()>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 8b 45 08 mov 0x8(%ebp),%eax
6: c7 00 78 56 34 12 movl $0x12345678,(%eax)
c: 5d pop %ebp
d: c3 ret
Disassembly of section .text._ZN4baseC1Ev:
00000000 <base::base()>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 8b 45 08 mov 0x8(%ebp),%eax
6: c7 00 78 56 34 12 movl $0x12345678,(%eax)
c: 5d pop %ebp
d: c3 ret
Disassembly of section .text._ZN6derive4vfunEv:
00000000 <derive::vfun()>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 5d pop %ebp
4: c3 ret
Disassembly of section .text._ZN7nvdctorD1Ev:
00000000 <nvdctor::~nvdctor()>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 5d pop %ebp
4: c3 ret
Disassembly of section .text._ZN6deriveC1Ev:
00000000 <derive::derive()>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 18 sub $0x18,%esp
6: 8b 45 08 mov 0x8(%ebp),%eax
9: 83 c0 04 add $0x4,%eax
c: 89 04 24 mov %eax,(%esp)
f: e8 fc ff ff ff call 10 <derive::derive()+0x10>
10: R_386_PC32 base::base()
14: 8b 45 08 mov 0x8(%ebp),%eax
17: c7 00 08 00 00 00 movl $0x8,(%eax)
19: R_386_32 vtable for derive
1d: c9 leave
1e: c3 ret
Disassembly of section .text._ZN7nvdctorC1Ev:
00000000 <nvdctor::nvdctor()>:
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 18 sub $0x18,%esp
6: 8b 45 08 mov 0x8(%ebp),%eax
9: 89 04 24 mov %eax,(%esp)
c: e8 fc ff ff ff call d <nvdctor::nvdctor()+0xd>
d: R_386_PC32 base::base()
11: c9 leave
12: c3 ret
[root@Harry throwtype]#