ValGrind的使用(转)

以前用ValGrind解决过内存泄露的问题。最近,Server莫名其妙的Crash,而且无法追溯(即使我使用了stack trace)。考虑到可能是由于Wild Pointer引起的问题,所以,再次借助ValGrind,

valgrind –tool=memcheck ./gameserver

很快定位到了问题所在。原来是一个动态数组在初始化的时候,大小没有确定。

转此文,以作备忘。

———————-D的分割线—————————

Valgrind是一个GPL的软件,用于Linux(For x86, amd64 and ppc32)程序的内存调试和代码剖析。你可以在它的环境中运行你的程序来监视内存的使用情况,比如C 语言中的malloc和free或者 C++中的new和 delete。使用Valgrind的工具包,你可以自动的检测许多内存管理和线程的bug,避免花费太多的时间在bug寻找上,使得你的程序更加稳固。

Valgrind的主要功能

Valgrind工具包包含多个工具,如Memcheck,Cachegrind,Helgrind, Callgrind,Massif。下面分别介绍个工具的作用:

Memcheck 工具主要检查下面的程序错误:

  • 使用未初始化的内存 (Use of uninitialised memory)
  • 使用已经释放了的内存 (Reading/writing memory after it has been free’d)
  • 使用超过 malloc分配的内存空间(Reading/writing off the end of malloc’d blocks)
  • 对堆栈的非法访问 (Reading/writing inappropriate areas on the stack)
  • 申请的空间是否有释放 (Memory leaks – where pointers to malloc’d blocks are lost forever)
  • malloc/free/new/delete申请和释放内存的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])
  • src和dst的重叠(Overlapping src and dst pointers in memcpy() and related functions)

Callgrind

Callgrind收集程序运行时的一些数据,函数调用关系等信息,还可以有选择地进行cache 模拟。在运行结束时,它会把分析数据写入一个文件。callgrind_annotate可以把这个文件的内容转化成可读的形式。

Cachegrind

它模拟 CPU中的一级缓存I1,D1和L2二级缓存,能够精确地指出程序中 cache的丢失和命中。如果需要,它还能够为我们提供cache丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数。这对优化程序有很大的帮助。

Helgrind

它主要用来检查多线程程序中出现的竞争问题。Helgrind 寻找内存中被多个线程访问,而又没有一贯加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发掘的错误。Helgrind实现了名为” Eraser” 的竞争检测算法,并做了进一步改进,减少了报告错误的次数。

Massif

堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小。Massif能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。

Valgrind 安装

1、 到www.valgrind.org下载最新版valgrind-3.2.3.tar.bz2
2、 解压安装包:tar –jxvf valgrind-3.2.3.tar.bz2
3、 解压后生成目录valgrind-3.2.3
4、 cd valgrind-3.2.3
5、 ./configure
6、 Make;make install

Valgrind 使用

用法: valgrind [options] prog-and-args [options]: 常用选项,适用于所有Valgrind工具

  1. -tool=<name> 最常用的选项。运行 valgrind中名为toolname的工具。默认memcheck。
  2. h –help 显示帮助信息。
  3. -version 显示valgrind内核的版本,每个工具都有各自的版本。
  4. q –quiet 安静地运行,只打印错误信息。
  5. v –verbose 更详细的信息, 增加错误数统计。
  6. -trace-children=no|yes 跟踪子线程? [no]
  7. -track-fds=no|yes 跟踪打开的文件描述?[no]
  8. -time-stamp=no|yes 增加时间戳到LOG信息? [no]
  9. -log-fd=<number> 输出LOG到描述符文件 [2=stderr]
  10. -log-file=<file> 将输出的信息写入到filename.PID的文件里,PID是运行程序的进行ID
  11. -log-file-exactly=<file> 输出LOG信息到 file
  12. -log-file-qualifier=<VAR> 取得环境变量的值来做为输出信息的文件名。 [none]
  13. -log-socket=ipaddr:port 输出LOG到socket ,ipaddr:port

LOG信息输出

  1. -xml=yes 将信息以xml格式输出,只有memcheck可用
  2. -num-callers=<number> show <number> callers in stack traces [12]
  3. -error-limit=no|yes 如果太多错误,则停止显示新错误? [yes]
  4. -error-exitcode=<number> 如果发现错误则返回错误代码 [0=disable]
  5. -db-attach=no|yes 当出现错误,valgrind会自动启动调试器gdb。[no]
  6. -db-command=<command> 启动调试器的命令行选项[gdb -nw %f %p]

适用于Memcheck工具的相关选项:

  1. -leak-check=no|summary|full 要求对leak给出详细信息? [summary]
  2. -leak-resolution=low|med|high how much bt merging in leak check [low]
  3. -show-reachable=no|yes show reachable blocks in leak check? [no]

Valgrind 使用举例

下面是一段有问题的C程序代码test.c

#i nclude <stdlib.h>
void f(void)
{
   int* x = malloc(10 * sizeof(int));
   x[10] = 0;  //问题1: 数组下标越界
}                  //问题2: 内存没有释放
int main(void)
{
   f();
   return 0;
 }
1、 编译程序test.c
gcc -Wall test.c -g -o test
2、 使用Valgrind检查程序BUG
valgrind --tool=memcheck --leak-check=full ./test
3、 分析输出的调试信息
==3908== Memcheck, a memory error detector.
==3908== Copyright (C) 2002-2007, and GNU GPL'd, by Julian Seward et al.
==3908== Using LibVEX rev 1732, a library for dynamic binary translation.
==3908== Copyright (C) 2004-2007, and GNU GPL'd, by OpenWorks LLP.
==3908== Using valgrind-3.2.3, a dynamic binary instrumentation framework.
==3908== Copyright (C) 2000-2007, and GNU GPL'd, by Julian Seward et al.
==3908== For more details, rerun with: -v
==3908==
--3908-- DWARF2 CFI reader: unhandled CFI instruction 0:50
--3908-- DWARF2 CFI reader: unhandled CFI instruction 0:50
/*数组越界错误*/
==3908== Invalid write of size 4
==3908==    at 0x8048384: f (test.c:6)
==3908==    by 0x80483AC: main (test.c:11)
==3908==  Address 0x400C050 is 0 bytes after a block of size 40 alloc'd
==3908==    at 0x40046F2: malloc (vg_replace_malloc.c:149)
==3908==    by 0x8048377: f (test.c:5)
==3908==    by 0x80483AC: main (test.c:11)
==3908==
==3908== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 14 from 1)
==3908== malloc/free: in use at exit: 40 bytes in 1 blocks.
==3908== malloc/free: 1 allocs, 0 frees, 40 bytes allocated.
==3908== For counts of detected errors, rerun with: -v
==3908== searching for pointers to 1 not-freed blocks.
==3908== checked 59,124 bytes.
==3908==
==3908==
/*有内存空间没有释放*/
==3908== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==3908==    at 0x40046F2: malloc (vg_replace_malloc.c:149)
==3908==    by 0x8048377: f (test.c:5)
==3908==    by 0x80483AC: main (test.c:11)
==3908==
==3908== LEAK SUMMARY:
==3908==    definitely lost: 40 bytes in 1 blocks.
==3908==      possibly lost: 0 bytes in 0 blocks.
==3908==    still reachable: 0 bytes in 0 blocks.
==3908==         suppressed: 0 bytes in 0 blocks.

Listen to your finger

有时候,手指比大脑的反映更迅速。So just listen to your finger~

VIM使用
(1) ctags -R ./*
vim 里面: set tags=../tags — CTag 给VIM插上了左翼,CTlist给VIM插上了右翼
(2) 制作标签(VS里面的快捷键是:Ctrl+k,k)
ma 则制定了一个a的标签
mb 则指定了一个b的标签
跳转到标签: `a, `b (VS里面的标签跳转时Ctrl+k,Ctrl+n)
mA
mB
注意小写标签用于文件内,大写标签用于文件之间
(3) 文件格式化对齐:用v选中需要格式化的东西,=
(4) 选中的功能,一种是v,另一种是Ctrl+v,也就是矩形选择
(5) 用//注释很多行,在这些行的前面,ctrl+v,选中所有要注释的东西,I,//, ESC
(6) Replace的命令: r
(7) 跳转到对应)或者}的命令: %
(8) 跳转到行首的命令: 0, 行尾: $
(9) 查找替换的命令: %s
(10) 打开另外一个文件:vsp filename
(11) 当前位置开始编辑: i,
(12) 折叠某些代码的的命令,选中之后:zf, 展开:zo
(13) 跳到上一个编辑的地方Ctrl+o, 下一个编辑的地方Ctrl+i
(14) u撤销,Ctrl-r 重做
(15) w往后移一个字到字首,b往后移动一个字到字尾,e往后移一个字到字首
(16) 替换命令: s – 替换,% – 所有的行,g – 全文,^ – 行首,$ – 行尾

一些自己不太常使用的命令
移动光标类命令
e或E :光标右移一个字至字尾
) :光标移至句尾
( :光标移至句首
}:光标移至段落开头
{:光标移至段落结尾
nG:光标移至第n行首
n+:光标下移n行
n-:光标上移n行
n$:光标移至第n行尾
H :光标移至屏幕顶行
M :光标移至屏幕中间行
L :光标移至屏幕最后行

屏幕翻滚类命令
Ctrl+u:向文件首翻半屏
Ctrl+b;向文件首翻一屏
Ctrl+d:向文件尾翻半屏
Ctrl+f:向文件尾翻一屏
nz:将第n行滚至屏幕顶部,不指定n时将当前行滚至屏幕顶部。

插入文本类命令
s:从当前光标位置处开始,以输入的文本替代指定数目的字符
S:删除指定数目的行,并以所输入文本代替之
ncw或nCW:修改指定数目的字
nCC:修改指定数目的行

删除命令
ndw或ndW:删除光标处开始及其后的n-1个字
do:删至行首
d$:删至行尾
ndd:删除当前行及其后n-1行
Ctrl+u:删除输入方式下所输入的文本

最后行方式命令
:n1,n2 co n3:将n1行到n2行之间的内容拷贝到第n3行下
:n1,n2 m n3:将n1行到n2行之间的内容移至到第n3行下
:n1,n2 d :将n1行到n2行之间的内容删除
:e filename:打开文件filename进行编辑
:!command:执行shell命令command
:n1,n2 w!command:将文件中n1行至n2行的内容作为command的输入并执行之,若不指定n1,n2,则表示将整个文件内容作为command的输入
:r!command:将命令command的输出结果放到当前行

指令模式:
B 移至该行第一个字符,若光标在该行第一字符则光标移至上一行第一字符。
b 由游标所在位置之前一个字串的第一个字元
cc 删除整行,修改整行的内容。
D 以行为单位,删除游标在内后面的所有字符。
db 删除该行光标前字符
de 删除自光标开始后面的字符
d  加字符 删除光标所在位置至字符之间的单

=============我是VI和VS的分割线==============

下面是VAX的快捷键,VAX是 给VS插上了两个翅膀~,其他的自己熟悉的就不记录了,以下是不太熟悉又很有用的~

VAX快捷键
Ctrl+Shift+L 删除当前行
Ctrl+E, W 自动换行
Shift+Alt+箭头 选择矩形文本
Ctrl+Shift+U 全部变为大写
Ctrl+U 全部变为小写
Ctrl+Shift+V 剪贴板循环
Ctrl+Shift+空格 参数信息
Ctrl+K,I 快速信息
Ctrl+Shift+Q 自动生成成员函数

关于CPPUnit的使用

简单介绍下CPPUnit的使用
第一步:从http://sourceforge.net/projects/cppunit/files/下载源代码,我这里用的是cppunit-1.12.1.tar.gz。
第二步:打开src下面的CppUnitLibraries.sln文件,这里用VS2005或2008都可以,VS会提示你是否要转换,选择全是。
第三步:删除sln里面的DSPlugIn文件,因为它只支持VC6.0。
第四步:修改MsDevCallerListCtrl.cpp文件。将

#import “libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2″ version(“7.0″) lcid(“0″) raw_interfaces_only named_guids

改为

#import “libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2″ version(“8.0″) lcid(“0″) raw_interfaces_only named_guids
第五步:生成->批生成。这样就会自动生成需要的所有的lib。
然后就可以使用了。

最后贴一下代码吧:

———————————–我是分割线———————————-

stdafx.h

#pragma once

#ifndef _WIN32_WINNT        // 允许使用特定于 Windows XP 或更高版本的功能。
#define _WIN32_WINNT 0×0501    // 将此值更改为相应的值,以适用于 Windows 的其他版本。
#endif

#include <stdio.h>
#include <tchar.h>

// TODO: 在此处引用程序需要的其他头文件
#ifndef _DEBUG
#pragma  comment(lib, “cppunit_dll.lib”)
#else
#pragma  comment(lib, “cppunitd_dll.lib”)
#endif

// For iostream
#include <iostream>

// For CppUnit
#include <cppunit/TestResult.h>
#include <cppunit/CompilerOutputter.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/BriefTestProgressListener.h>

#include <cppunit/ui/text/TestRunner.h>

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>

———————————–我是分割线,好圡———————————-

Math.h

#ifndef MATH_H
#define MATH_H

class Math
{
public:
Math();
~Math( );

float Add( float oprandOne, float oprandTwo );
float Multiple( float oprandOne, float oprandTwo );

};
#endif

———————————–我是华丽丽的分割线 ———————————-

Math.cpp

#include “stdafx.h”
#include “Math.h”

Math::Math()
{

}

Math::~Math()
{

}

float Math::Add( float oprandOne, float oprandTwo )
{
return ( oprandOne + oprandTwo );
}

float Math::Multiple( float oprandOne, float oprandTwo )
{
return ( oprandOne – oprandTwo );
}

———————————–我是分割线,华丽丽的分割线 ———————————-

MathTest.h

#ifndef MATHTEST_H
#define MATHTEST_H

#include “stdafx.h”
#include “Math.h”

class MathTest : public CppUnit::TestFixture
{
public:
CPPUNIT_TEST_SUITE( MathTest );
CPPUNIT_TEST( TESTAdd );
CPPUNIT_TEST( TESTMultiple );
CPPUNIT_TEST_SUITE_END();
public:

virtual void setUp();
virtual void tearDown();

protected:

void TESTAdd();
void TESTMultiple();

private:
Math m_math;
};

// 注册函数
CPPUNIT_TEST_SUITE_REGISTRATION( MathTest );

#endif

———————————–我是MSN的分割线 ———————————-

MathTest.cpp

#include “stdafx.h”
#include “MathTest.h”

void MathTest::setUp()
{
m_math = Math();
}

void MathTest::tearDown()
{

}

void MathTest::TESTAdd()
{
int result = m_math.Add( 2, 3 );
CPPUNIT_ASSERT_EQUAL( 5, result );
}

void MathTest::TESTMultiple()
{
int result = m_math.Multiple( 2 , 3 );
CPPUNIT_ASSERT_EQUAL( 5, result );
}

———————————–对不起,我是NPC———————————-

main.cpp

#include “stdafx.h”

int main()
{
// Create the event manager and test controller
CPPUNIT_NS::TestResult controller;

// Add a listener that colllects test result
CPPUNIT_NS::TestResultCollector result;
controller.addListener( &result );

// Add a listener that print dots as test run.
CPPUNIT_NS::BriefTestProgressListener progress;
controller.addListener( &progress );

// Add the top suite to the test runner
CPPUNIT_NS::TestRunner runner;
runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
runner.run( controller );

// Print test in a compiler compatible format.
CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
outputter.write();

char ch;
std::cin >> ch;
return 0;
}

近况

我的心态是不对的。
要调整过来。
彻头彻尾的调整。
剃了个光头,从头开始哈!

—————————–
最近看完的书
《世界游戏制作大师》
《游戏编程精粹1》
《我的成功可以复制》(第二版) 唐骏 胡腾 著
《我的奋斗》罗永浩著
《30上下,决定男人的一生》 王剑 张兵编著

唐骏和老罗的书是买的,后面的几本是从图书馆借的。T-T,我居然会买罗胖子的书。

在读:
《游戏编程精粹5》
《软件调试》

最近在玩的游戏:
1. Fallout3 玩了一点点,大约10%。
2. Fable I 玩了80%。
3. Dota

电脑坏了,无法/不敢再玩游戏了,怕烧显卡。

Chapter 20 Style and Efficiency

本章基本上字字珠玑,需要反复阅读。
1. Applications and Modules
a) Erlang里面一个紧密相互作用的模块集合被成为application。
b) 设计一个application来为来自其他所有application的调用提供一个单独的入口是一种好的实践。集合所有的外部输出函数到一个模块里面,在维护代码方面提供了更好的可扩展性。
c) 在大型系统里面,在一个特定的application里面为模块添加短的前缀是一种好的实践。
d) 当设计模块的时候,要尽量少的输出函数。
e) 尽量减少内部模块之间的依赖。内部模块之间的依赖必须是形成无环的图。
f) 在一个特定的模块里面,将相关的函数放在一起。
g) 将经常使用的代码放到库里面。尽可能的让库函数没有副作用,这样可以增强它的可复用性。
h) Dirty code就是那些你不应该写但是被迫去写的代码。Dirty code包含使用进程字典,使用process_info/2函数,使用对数据类型内部结构进行假设的代码,使用其他内部构造函数。将tricky和dirty的代码孤立到一个独立的模块里面非常重要。尽可能的减少或者避免这些代码。将dirty code写入代码非常重要,在代码里面写明为什么是dirty的,这样其他人在修改你的代码的时候就能够明白当你在写这个dirty code的时候你所做的假设。
i) 对你所有输出的接口写入文档。
j) 如果你export的语句超过了一行,你就应该分开它。
k) 函数可能不是总是调用成功,tag它的返回值。Erlang函数式使用标准的返回值格式ok, {ok, Result}或者{error, Reason}。如果你确定某个函数总是返回成功,那么你应该返回一个单独的值例如true,false,或者仅仅是一个整数,原子,或者复杂数据类型。这样就允许函数的返回值做为参数传递给另外一个函数,而不需要检查成功或者失败。
l) 不要对你的函数调用者对函数结果的处理做任何假设。
m) 总的来讲,最好让你的函数只做一件事情。
n) 不要让私有数据泄露出去,私有数据结构的所有细节必须从接口里面抽象出去。
2. Process and Concurrency
a) 在Erlang里面,一个基本的设计概念是,在你的Erlang程序里面的并行进程和你要建模的系统的并行行动之间创建一对一的映射。这会大量增加并行进程,所以,尽可能的避免比必要的进程交互和不必要的并发。确保你为每个并发的行动都有一个进程,而不是仅仅两三个。
b) 你必须总是实现一个进程循环和它的外围函数在同一模块里面。将所有的消息传递隐藏在函数接口里面,这样可以提高可扩展性和进行信息隐藏。
c) 注册函数要有一个比较长的生命周期,你不能在注册和取消注册他们之间频繁切换。因为原子使用的空间是不被垃圾回收的,避免动态创建原子来注册函数,因为这可能引起内存泄露。
d) 当进行消息传递的时候,所有的消息都有被tagged。这使得receive声明里面的语句顺序不再重要,这样就导致可以直接添加新的消息而不需要改变现有的行为,这样减少了bug的分先。
e) 使用reference。
f) 谨慎使用timeouts。如果你使用它们,要总是flush那些迟到的消息。另一种方式是使用reference。
g) 如果你在使用timeout来处理一种进程可能终止的情况,最好使用link或者一个monitor。
h) 将错误恢复与正常代码区别出来。
i) 让你的监视者处理退出信号,并且让它结合它在监控的其他进程来决定恢复策略。
j) 确保你的模块没有循环依赖。
3. Stylistic Conventions
a) 避免使用深层嵌套的代码。在你的case,if,receive和fun从句,你绝对不能有超过两层的嵌套代码。一种通用的减少缩进的技巧是创建暂时的组合数据类型。通过在你的函数头里面引入模式匹配,你可以减少缩进。
b) 当case是一种比较好的选择的时候避免使用if。
c) 确保你的模块的大小可控制。一个可控的模块代码行不应该超过400行,注释除外。
d) 代码行不可以太长,或者超过一行。一行代码不应该超过80个字符。
e) 选择有意义的函数名和变量名。避免长名字,因为他们是让你的代码超过一行的主要原因。使用缩写或者前缀来缩短你的名字。
f) 当使用“不在乎”变量的时候,避免在它上面使用下划线。
g) 使用records作为首选的数据结构来存储组合数据类型。
h) 只有在你需要的时候才使用变量。
i) 谨慎使用catch和throw。更倾向于使用try…catch而不是catch和throw。
4. Coding Strategies
a) 减少函数的副作用,将它们放进一个特定的模块里面。这些函数能够处理文件或者封装对数据库的操作。
b) 让你的代码的运行具有确定性。
c) 抽象出通用的设计模式。
d) 避免防御式编程。
e) 不要过度设计你的代码。
f) 避免复制,粘贴编程,不要注释掉死掉的代码,直接删除。如果将来你需要的话,你总是能通过某种方式取回的。
5. Efficiency
a) Erlang里面strings的实现不是高效的。
b) 如果速度和吞吐量非常重要的话,使用re库模块来处理正则表达式。
c) 如果你在设置和重置很多定时器,避免使用timer模块,因为它将进程里的所有的请求序列化,并且可能成为瓶颈。因此,使用Erlang的定事情BIF:erlang:send_after/3, erlang:start_timer/2, erlang:cancel_timer/1, 或者erlang:read_timer/1。
d) 如果可能的话,使用tuple来代替list。
e) 永远记住原子是不会被垃圾回收的。
f) 当处理使用密集内存的操作的时候,产生一个进程,一旦完成立即结束。
g) 你可以使用BIF garbage_collect/0在调用进程里面或者garbage_collect/1针对一个特定的进程来进行强制的垃圾回收。
6. Add Finally…

Chapter 19 EUnit and Test-Driven Development

1. Test-Driven Development
a) 每个要实现的新功能首先必须由一系列的测试来标识。只有测试通过了才能将新功能添加到代码。同时,不能让之前的测试失效。
2. EUnit
a) 可以将测试函数从模块(例如mod)里面分离出来,放入新的模块(例如mod_test.erl)里面。但是,新的模块也要包含eunit.hrl头文件。测试仍然用同样的方式(mod:test())来调用。如果你想在你的代码里面使用EUnit,例如,使用?assert宏,但是你不想产生测试函数。这种情况下,你要将下面的代码插入到包含eunit.hrl的代码前面:-define( NOTEST, true)
b) 如果你要将测试函数放入到一个单独的serial_tests模块,你可以使用import指令来包含测试函数而不需要在serial模块里面做任何改变。
3. The EUnit Infrastructure
a) 在测试失效的时候,assertEqual(E,F)能够比assert(E =:= F )产生更多的信息。
b) 你可以定义test-generating函数来将一系列的测试集成到一个单独的函数里面。
4. Testing State-Based Systems
5. Testing Concurrent Programs in Erlang
a) 一些EUnit工具非常有用:你可以在程序里面的任何地方?assert一个属性,这样当一个前置或者后置条件后系统变量被破坏的时候,对程序进行监控。EUnit还提供了对debug的支持,将信息输出到Erlang控制台上而不是标准输出上。

回到顶部