C/C++内存泄漏及检测
1、win下的内存泄露检测方法:_CrtDumpMemoryLeaks
通过包括 crtdbg.h,将 malloc 和 free 函数映射到它们的调试版本,即 _malloc_dbg 和 _free_dbg,这两个函数将跟踪内存分配和释放。 此映射只在调试版本(在其中定义了_DEBUG)中发生。 发布版本使用普通的 malloc 和 free 函数。
在程序退出前调用:_CrtDumpMemoryLeaks();
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#include <iostream>
using namespace std;
void GetMemory(char *p, int num)
{
p = (char*)malloc(sizeof(char) * num);
}
int main(int argc,char** argv)
{
char *str = NULL;
GetMemory(str, 100);
cout<<"Memory leak test!"<<endl;
_CrtDumpMemoryLeaks();
return 0;
}
2、Linux下的内存泄露检测方法
mtrace/muntrace
valgrind
memwatch
mtrace
Linux下mtrace的使用
#include <stdlib.h>
#include <mcheck.h>
int main(void) {
mtrace(); /* Starts the recording of memory allocations and releases */
int* a = NULL;
a = malloc(sizeof(int)); /* allocate memory and assign it to the pointer */
if (a == NULL) {
return 1; /* error */
}
free(a); /* we free the memory we allocated so we don't have leaks */
muntrace();
return 0; /* exit */
}
valgrind工具
The Valgrind Quick Start Guide
Memwatch
Usage:
只要在你程序中加入memwatch.h,编译时加上-DMEMWATCH -DMW_STDIO及memwatch.c就能使用memwatch
memwatch 的输出文件名称为memwatch.log,而且在程序执行期间,所有错误提示都会显示在stdout上,如果memwatch未能写入以上文件,它会尝试写入memwatchNN.log,而NN介于01至99之间,若它仍未能写入memwatchNN.log,则会放弃写入文件。
gcc -DMEMWATCH -DMW_STDIO test.c memwatch.c -o test
#include <stdio.h>
#include <signal.h>
#include "memwatch.h"
int main(int argc,char **argv)
{
int i = 0;
char *p;
mwInit(); //执行memwatch的初始化工作。虽然memwatch有在第一次使用时能够自动装载,但作者还是建议我们使用该函数。
p = malloc(100);
p = malloc(200);
free(p);
for(i=0;i<5;i++)
{
p = malloc(50);
if(p == NULL)
{
printf("can't malloc memory for test,num:%d\n",i);
continue;
}
if((i%2) == 0)
{
free(p);
p = NULL;
}
}
mwTerm(); //执行memwatch的清除工作。当我们使用了mwInit()时,就必须使用该函数来终止memwatch的工作。
return;
}
参考:这里写链接内容
gcc -E 产生预编译后的源代码,即源代码经过预编译后的结果,所有的预编译动作都已完成。如头文件的插入,宏定义的展开。
参考:GCC C Compiler
例子:
#include <stdlib.h>
#include <stdio.h>
#define MACRO1(x) (++(x))
#define MACRO2(x) (MACRO1(x)+100)
#define MACRO3(x) (MACRO2(x)+200)
int main(void)
{
int a = 0;
int b = 0;
b = MACRO3(a);
printf("%d\n", b);
return 0;
}
1、调试宏
在GCC编译程序的时候,加上-ggdb3/-g3参数,这样,你就可以调试宏了。
macro expand/exp 展开宏定义
info macro 查看宏展开,及被调用处
(gdb) macro exp MACRO3(3) //展开宏,并传入值
expands to: (((++(3))+100)+200)
(gdb) info macro MACRO3 //查看这个宏在哪些文件里被引用了,以及宏定义是什么样的
Defined at /mnt/hgfs/VMUB/codeTs/test/macro_gdb.c:6
#define MACRO3(x) (MACRO2(x)+200)
2、条件断点
break line-or-function if expr
(gdb) break main if b = 0
Breakpoint 1 at 0x8048426: file macro_gdb.c, line 10.
3、命令行参数
可以使用两种方法输入命令行参数
1)run 命令行参数
2)set args 命令行参数
如:我的程序中需要输入的时服务器端ip地址,可以通过以下两种方法输入
1)如果直接运行程序,run www.baidu.com
2)set args www.baidu.com,后面再继续进行调试
4、修改变量的值
p a = 100 #修改变量的文件中的值
参考:这里写链接内容
1、命令
info thread 查看当前进程的线程。
thread 切换调试的线程为指定ID的线程。
break file.c:100 thread all 在file.c文件第100行处为所有经过这里的线程设置断点。
set scheduler-locking off|on|step
这个是问得最多的。在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。
off 不锁定任何线程,也就是所有线程都执行,这是默认值。
on 只有当前被调试程序会执行。
step 在单步的时候,除了next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外,只有当前线程会执行。
thread apply ID1 ID2 command 让一个或者多个线程执行GDB命令command。
thread apply all command 让所有被调试线程执行GDB命令command。
gdb对于多线程程序的调试有如下的支持:
线程产生通知:在产生新的线程时, gdb会给出提示信息
(gdb) r
Starting program: /root/thread
[New Thread 1073951360 (LWP 12900)]
[New Thread 1082342592 (LWP 12907)]—以下三个为新产生的线程
[New Thread 1090731072 (LWP 12908)]
[New Thread 1099119552 (LWP 12909)]
查看线程:使用info threads可以查看运行的线程。
(gdb) info threads
4 Thread 1099119552 (LWP 12940) 0xffffe002 in ?? ()
3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? ()
2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? ()
* 1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)
注意,行首的蓝色文字为gdb分配的线程号,对线程进行切换时,使用该该号码,而不是上文标出的绿色数字。
另外,行首的红色星号标识了当前活动的线程
切换线程:使用 thread THREADNUMBER 进行切换,THREADNUMBER 为上文提到的线程号。下例显示将活动线程从 1 切换至 4。
(gdb) info threads
4 Thread 1099119552 (LWP 12940) 0xffffe002 in ?? ()
3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? ()
2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? ()
* 1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb) thread 4
[Switching to thread 4 (Thread 1099119552 (LWP 12940))]#0 0xffffe002 in ?? ()
(gdb) info threads
* 4 Thread 1099119552 (LWP 12940) 0xffffe002 in ?? ()
3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? ()
2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? ()
1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)
后面就是直接在你的线程函数里面设置断点,然后continue到那个断点,一般情况下多线程的时候,由于是同时运行的,最好设置 set scheduler-locking on这样的话,只调试当前线程
1、follow-fork-mode GDB 6.4
set follow-fork-mode [parent|child]
parent: fork之后继续调试父进程,子进程不受影响。
child: fork之后调试子进程,父进程不受影响。
因此如果需要调试子进程,在启动gdb后:
(gdb) set follow-fork-mode child
2、detach-on-fork GDB 6.6
set detach-on-fork [on|off]
on: 断开调试follow-fork-mode指定的进程。
off: gdb将控制父进程和子进程。follow-fork-mode指定的进程将被调试,另一个进程置于暂停(suspended)状态。
3、Attach子进程
attach
4、GDB wrapper
小结:
follow-fork-mode方法:方便易用,对系统内核和GDB版本有限制,适合于较为简单的多进程系统
attach子进程方法:灵活强大,但需要添加额外代码,适合于各种复杂情况,特别是守护进程
GDB wrapper方法:专用于fork+exec模式,不用添加额外代码,但需要X环境支持(xterm/VNC)。
参考:gdb调试子进程
linux 下调试core 的命令,察看堆栈状态命令
【实例】
#include <stdio.h>
static void sub(void) {
int *p = NULL;
printf("%d", *p);
}
int main(void)
{
sub();
return 0;
}
ulimit -c 查看core文件的生成开关。若结果为0,则表示关闭了此功能,不会生成core文件。
ulimit -c filesize(大小为KB)
ulimit -c unlimited
ulimit -a 显示所有的用户定制,其中选项-a代表“all”。
先运行,a.out -> 产生core dump文件
默认生成的文件就叫core,不带PID,如果要带PID需要设置,通过echo “1” > /proc/sys/kernel/core_uses_pid能够设置pid
$ gdb --core=core
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x08048373 in ?? ()
(gdb) bt
#0 0x08048373 in ?? ()
#1 0xbfffd8f8 in ?? ()
#2 0x0804839e in ?? ()
#3 0xb74cc6b3 in ?? ()
#4 0x00000000 in ?? ()
此时用bt看不到backtrace,也就是调用堆栈,原来GDB还不知道符号信息在哪里。我们告诉它一下:
(gdb) file ./a.out
Reading symbols from ./a.out...done.
Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) bt
#0 0x08048373 in sub () at foo.c:17
#1 0x08048359 in main () at foo.c:8
此时backtrace出来了。
调试core
四种方法:
内存映射、procfs、syscall、ioctl、netlink
1、内存映射的方式,将内核地址映射到用户态。这种方式最直接,可以适用大量的数据传输机制。
这种方式的缺点是很难进行“业务控制”,没有一种可靠的机制保障内核和用户态的调动同步,比如信号量等都不能跨内核、用户层使用。因此内存映射机制一般需要配合一种“消息机制”来控制数据的读取,比如采用“消息”类型的短数据通道来完成一个可靠的数据读取功能。
2、ioctl机制,ioctl机制可以在驱动中扩展特定的ioctl消息,用于将一些状态从内核反应到用户态。
Ioctl有很好的数据同步保护机制,不要担心内核和用户层的数据访问冲突,但是ioctl**不适合传输大量的数据,通过和内存映射结合可以很好的完成大量数据交换过程。但是,**ioctl的发起方一定是在用户态,因此如果需要内核态主动发起一个通知消息给用户层,则非常的麻烦。可能需要用户态程序采用轮询机制不停的ioctl。
3、系统调用必须通过用户态发起
4、proc方式不太可靠和实时,用于调试信息的输出还是非常合适的。
参考:Linux内核态与用户态通信的常用方法
Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:
1,为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。
2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接 收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。
3.使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。
4.netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在 后面的文章中将介绍这一机制的使用。
5.内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。
6.netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。
Linux内核态与用户态进程通信方法-用户上下文
Linux 内核提供 copy_from_user()/copy_to_user() 函数来实现内核态与用户态数据的拷贝
参考:Linux内核态与用户态进程通信方法-用户上下文
netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。
netlink套接字可以使用标准的套接字APIs来创建。socket(), bind(), sendmsg(), recvmsg() 和 close()很容易地应用到 netlink socket。
netlink包含于头文件linux/netlink.h中,
1、用户态使用 netlink
创建套接字:
socket(AF_NETLINK, SOCK_RAW, netlink_type)
绑定套接字:
//用于把【打开的netlink socket】与【源netlink socket】地址绑定在一起。
bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl));
对消息进行封装,加上消息头,即可发送。
sendmsg(fd, &msg, 0);
接收消息
//应用接收消息时需要首先分配一个足够大的缓存来保存消息头以及消息的数据部分,然后填充消息头,
//添完后就可以调用函数 recvmsg() 来接收。
recvmsg(fd, &msg, 0);
2、netlink内核API
创建一个内核的netlink套接字
struct sock *
netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));
发送消息:
//netlink_unicast 来发送单播消息:
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid,
int nonblock);
//netlink_broadcast来发送广播消息:
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid,
u32 group, int allocation);
释放套接字
void sock_release(struct socket * sock);
参考:
LINUX netlink机制
netlink socket编程实例解析
netlink(7) - Linux man page
用户空间与内核的接口(一)