Windbg定位内存泄露的一种简单方法

前两天接到一个反映进程内存占用过G的投诉。问题是必現的,一定是内存泄露,应该容易定位,一同事远程看过现场,使用gflags和windbg试图找到泄露的堆栈,同事是一步步按照这篇文章的方法来的,但在最后一步Windbg没有找到出问题的堆栈,用户给我们远程的时间很短,无法深究gflags+windbg不灵验的原因,只得另辟蹊径。

步骤如下:

0. 安装windbg, 设置symbols, 用windbg attach到发生内存泄露的进程

1. 打印出heap的使用情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0:003> !heap -s
LFH Key : 0x7ce97b7b
LFH Key : 0x7ce97b7b
Termination on corruption : ENABLED
Heap     Flags    Reserv Commit  Virt   Free   List    UCR    Virt  Lock Fast
                  (k)    (k)     (k)    (k)    length  blocks cont. heap
-----------------------------------------------------------------------------
002c0000 00000002 1024    372     1024   54    13      1      0     0    LFH
00010000 00008000 64      4       64 2   1     1       0      0
00020000 00008000 64      64      64     62    1       1      0     0
004d0000 00001002 1088    152     1088   7     4       2      0     0    LFH
007c0000 00001002 1088    188     1088   18    7       2      0     0    LFH
00880000 00001002 1280    276     1280   14    5       2      0     0    LFH
01db0000 00001002 64      12      64     2     3       1      0     0
021f0000 00001002 15488   12024   15488  144   7       5      0     0    LFH
00810000 00001002 64      12      64     2     3       1      0     0

很明显这一行:021f0000 00001002 15488 12024 15488 144 7 5 0 0 LFH是有异常的。

2. 显示异常heap的信息

1
2
3
4
5
6
7
8
0:003> !heap -stat -h 021f0000
heap @ 021f0000
heap @ 021f0000
group-by: TOTSIZE max-display: 20
  size   #blocks  total  ( %) (percent of total busy bytes)
  a45c   11d   -  b6fa6c (99.75)
  75a8   1     -  75a8   (0.25)
  20     1     -  20     (0.00)

上面11d块size为a45c的内存极有可能是泄露的内存。

3. 根据泄露内存的Size找到CallStack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
0:003> bp ntdll!RtlAllocateHeap "j(poi(@esp+c) = 0x0a45c) 'k';'gc'"
0:003> g
0:003> g
Unable to deliver callback, Unable to deliver callback, 3131

ChildEBPChildEBP RetAddrRetAddr


0021de54 1000ba7e 0021de54 1000ba7e ntdll!RtlAllocateHeapntdll!RtlAllocateHeap


WARNING: Stack unwind information not available. Following frames may be wrong.
WARNING: Stack unwind information not available. Following frames may be wrong.
0021de6c 1000bbcc 0021de6c 1000bbcc mfnspstd32mfnspstd32++0xba7e0xba7e


0021de8c 1000beb1 0021de8c 1000beb1 mfnspstd32mfnspstd32++0xbbcc0xbbcc


0021deb8 1000ea4d 0021deb8 1000ea4d mfnspstd32mfnspstd32++0xbeb10xbeb1


0021dee4 75de9986 0021dee4 75de9986 mfnspstd32!WSPStartupmfnspstd32!WSPStartup++0x9d0x9d


0021e3b8 75de975b 0021e3b8 75de975b WS2_32!DPROVIDER::InitializeWS2_32!DPROVIDER::Initialize++0x1850x185


0021e3d8 75df5a2f 0021e3d8 75df5a2f WS2_32!DCATALOG::LoadProviderWS2_32!DCATALOG::LoadProvider++0x6d0x6d


0021e678 75df5fe8 0021e678 75df5fe8 WS2_32!DCATALOG::FindIFSProviderForSocketWS2_32!DCATALOG::FindIFSProviderForSocket++0x630x63


0021e68c 75de4204 0021e68c 75de4204 WS2_32!DSOCKET::FindIFSSocketWS2_32!DSOCKET::FindIFSSocket++0x370x37


0021e6cc 00d48444 0021e6cc 00d48444 WS2_32!setsockoptWS2_32!setsockopt++0xb00xb0


0021e6ec 00d4900c 0021e6ec 00d4900c t**b!OPENSSL_Applinktadb!OPENSSL_Applink++0x7c140x7c14


0021e71c 00d3e50c 0021e71c 00d3e50c t**b!OPENSSL_Applinktadb!OPENSSL_Applink++0x87dc0x87dc


0021e738 00d4a654 0021e738 00d4a654 t**b++0x4e50c0x4e50c


0021e768 00d44f79 0021e768 00d44f79 t**b!OPENSSL_Applinktadb!OPENSSL_Applink++0x9e240x9e24


0021f8c8 00d4a6a4 0021f8c8 00d4a6a4 t**b!OPENSSL_Applinktadb!OPENSSL_Applink++0x47490x4749


0021f8d8 00cfb1f7 0021f8d8 00cfb1f7 t**b!OPENSSL_Applinktadb!OPENSSL_Applink++0x9e740x9e74


0021f91c 7700ee1c 0021f91c 7700ee1c t**b++0xb1f70xb1f7


0021f928 7731377b 0021f928 7731377b kernel32!BaseThreadInitThunkkernel32!BaseThreadInitThunk++0xe0xe


0021f968 7731374e 0021f968 7731374e ntdll!__RtlUserThreadStartntdll!__RtlUserThreadStart++0x700x70


0021f980 00000000 0021f980 00000000 ntdll!_RtlUserThreadStartntdll!_RtlUserThreadStart++0x1b0x1b

4. 最后甄别CallStack是否真正的发生内存泄露

总结

此方法适宜,泄露亦重现,且泄露的size固定的情况

C++API设计 - 笔记

C++ API设计从书名看是关于设计C++接口的书,内容却不仅仅是关于C++接口设计。书还算实用,但如果看过代码大全和Effective系列的话,可以不用在看这本书了。

笔记

P91 重构就像汽车高速行驶时更换引擎,但还不能停车

P103 API的第一个发行版本很重要

P115 LISKOV替换原则指出,如果S是T的子类,那么在行为上,S不需要修改就可以替换T类型的对象

P117 私有继承、Was-a的关系

P119 开发封闭原则是一种启发式的原则,则不是必须遵守的

P121 一个好的名字往往可以表达类的意图,如果一个类难以命名,这往往是缺乏设计的信号

P122 函数尽量使用正面的概念命名,比如IsConnect而不是IsUnConnect

P132 C API比C++ API具有更好的二进制兼容性

P140 可以使用预处理器技术模拟模板

P176 避免使用友元,这往往意味这糟糕的设计

P182 如果没有必要使用Dynamic_Cast则常见的做法是关闭运行时信息生成

P182 不要猜测性能瓶颈的位置

P183 传值可能会导致对象切割而引起莫名其妙的问题

P185 不应该前置声明STL的类型

P192 C++11中一个构造函数可以调用其他的构造函数

P192 对象越小,就越适合缓存

P197 Donald Knuth有句名言:过早优化是万恶之源

P211 Linux 的奇数版本号代表开发版本

P216 如果确实需要做二进制不兼容,则需要考虑给信库起个新名字。比如zlib.dll –> zlib1.dll

P225 弃用标记:__declspec(deprecated)

P250 时间驱动、质量驱动和功能驱动

P257 性能测试的结果是实数而不是简单的真和假

P264 驱动测试开发的好处是推动你考虑接口会被如何调用

P286 SWIG

P286 任何脚本绑定技术都是建立在适配器模式上的

P286 Boost.Python支持Boost和Python的互相交互

P265 模拟对象又可以简单称为MOCK

P339 每个DLL有相应的到入库lib文件,虽然静态库和导入库的格式都是lib但他们的文件类型是不同的

读《摩托车修理店的未来工作哲学》

<摩托车修理店的未来工作哲学>是一本大抵关于工作的书。

薪水的意义

记得刚毕业的头一年月薪虽少,但内心竟极不安:我每天干的这点事情配这些钱吗? 到现在又换过两次工作,薪水比刚毕业高了不少,起初还是惶恐自己干的活不配拿这份工资,但随着时间的推移,慢慢麻木不再深思这件事情,将原因归结为人力市场经济的供求关系。现在看到此书中观点,自己可以心安理得的拿这份薪水了:对个体而言,薪水是一种把人限制在一个地方、一定时间做自己并不是特别享受的事情的补偿。

平素里讲的对某工作感兴趣都是相对而言,如果对工作的享受可以达到玩游戏、K歌、美食的程度那一定是自欺欺人。对工作如此兴致的话,想必不要工资也会全心投入,因为平时可以给你带来如此享受的东西都是要钱的。

站在公司的角度看,用薪水吸引人,也是无奈之举。来人是冲着薪水来的,并不是工作本身。

快乐的工作

选择程序员的工作无疑是幸福的。程序员号称是当代唯一的手工艺者,互联网公司的办公环境也相对自由,不必忍受像富士康等流水线工作中十年从事一个标准动作的枯燥。

程序员可以快乐工作的条件很简单,有代码可写、在不被打扰的工作环境里思考着软件的结构和编码。回想自己这几年来的3份工作,第一份工作上班并没有太多的事情可做,或者是布置的东西太虚,导致自己内心也空虚,终日惶惶。第二份工作,流程清晰,需求明确,类似于流水线操作不大会出问题。现在的工作,不断有有新挑战,安心下来写代码会很幸福,无奈流程待拨乱反正,每天被打断N次、往返于各种会议与讨论之中。

所以,程序员快乐的编码不容易。

互联网公司“24小时待命”的陋习

24小时待命可能有些夸张,但它确实是以一种不成文的规定存在于一部分互联网公司。记得去年培训时,来自深圳总部的培训老师,自豪的说TX许多部门里员工都是24小时待命,随时应对解决问题。听一些其它公司的朋友讲,他们也有非上班时间被叫回公司的现象。所以24小时待命在一些地方可能形成了习俗。习俗很可怕,哪怕明知是错误的人们也无力反抗,比如裹脚。

有时真的需要24小时待命

在一些小而美的团队,早期面对众多的对手,必须有快速的执行力,才可以抢占先机,可以说24小时待命是小团队早期阶段不会死掉的必备条件。

还有一种情况下,相关人员必须随叫随到,就是线上产品出现了严重问题,这是必须要立即处理的,出现了这种问题,相信大家每个人都会很着急,会主动赶回去的。这主要涉及运维和相关开发。

头痛医脚的昏招

但除了这两种情况,我看不出这种随意拉员工到公司的习惯有什么好处,相反会极大的侵害员工的积极性。如果在一个非创业期的团队经常出现周末随意拉员工回去加班的情况,那一定是产品和项目管理出现了严重问题。拉员工回去对于解决问题,治标不治本,相反还让人产生反感,让人有种疲于奔命之感。

除此之外还有一种情况容易滋生24小时待命文化:老板的淫威。这是我熟识的一个朋友身上发生的事,他经常在坐了一个小时公交快到家门口的时候,被老板叫回去开临时会议,而且一开就到半夜12点了。后来朋友离开了那家公司,想必他不愿意再过那种随叫随到的生活,毕竟家里还有娇妻幼子。

我现在也有些反感,周六早上被拉进微信群讨论产品问题,为什么不周五讨论?如果是紧急问题,我会立马回去处理的,但往往事情并不是那么紧急,往往是因为老板的不经意一句话,产品人员热情的跟进了,于是我们开发人员就到了公司加班。

陋习的危害

对于这种24小时待命的习俗,是没有人敢于站出来反对的,因为稍有质疑就会被扣上不积极没有责任心的帽子,以后在公司就别想混了。所以对于这种东西,大家往往道路以目。长久下去,必会影响公司的发展,挫败团队的斗志。

仔细想一下,24小时待命和不积极没有责任心责任心并没有必然关系,岗位不同,责任不同,就我们开发而言,责任心应该体现在按时保质的完成开发任务(协助处理一些突发事故)。QA的主要责任感是按时完成测试,如果一个团队推行24小时待命,那什么东西是非得半夜过来测的?

相反我觉得员工对”24小时待命”文化的逆来顺受是一种逃避和不负责任的做法。没人去深层次的分析和解决产生临时拉人加班的原因,只会使团队滑向更深的深渊。

C++14-大餐后的甜点

这次C++标准委员会快速的通过了最新的C++标准:C++14,要知道C++11可是一再跳票后的产物。此次快速的发布的缘由可能和C++14的改动较小有关,C++之父Bjarne Stroustrup也说,相比与C++11来说,C++14的改动是谨小甚微的。相信C++14不会给我们带来像C++11那样的震撼,所以我们只能期待下一个版本C++17了。

但Bjarne还说了,C++永远是心向开发者的,C++14将给开发者大开方面之门。关于C++14更详细的细节可以看维基百科:C++14,这里讲下自己感兴趣的特性:

语言改变

范型lambda

在C++11下,如果你想要打印出一个数的平方,可能需要这样:

1
2
3
4
5
auto square_int = [](int x) { return x * x; };
auto square_double = [](double x) { return x * x; };

std::cout<<square_int(10)<<std::endl;
std::cout<<square_int(10.1)<<std::endl;

为了保持函数的局部性,我们才选择的lambda,但C++11的lambda却导致多个类型时代码膨胀且重复,此时我们需要回过头来借助全局的模板了。

但C++14可以完美的解决上面的问题,因为C++14中lambda的参数可以用auto代替具体的类型:

1
2
3
4
auto square = [](auto x) { return x * x; };

std::cout<<square_int(10)<<std::endl;
std::cout<<square_int(10.1)<<std::endl;

auto返回类型

C++11支持auto关键字,用于变量的自动类型推导。但由于时间限制,C++标准委员会并没有让auto也支持函数的返回值类型自动推导,现在C++14支持了。这将会在返回类内部类型的成员函数书写上减少好多工作量:

1
2
3
4
5
6
struct Wiget
{
  enum Status{show, hide}
  auto getStatus();
};
auto Wiget::getStatus() { return show; }

编译器支持

曾经被标准折磨的死去活来的编译器如今越挫越勇。标准出来的快,编译器支持的更快。CLang(3.4)半年前就宣布已完全支持C++14(draft)特性(语言和库)。本人电脑上的GCC4.9.1也已部分支持C++14特性。但公司的开发环境要支持C++14可就难了。

参考:

https://isocpp.org/std/status

http://cpprocks.com/c1114-compiler-and-library-shootout/

http://cpprocks.com/an-overview-of-c14-language-features/

http://llvm.org/releases/3.4/tools/clang/docs/ReleaseNotes.html

https://solarianprogrammer.com/2014/08/28/cpp-14-lambda-tutorial/