检测内存泄露、多线程gdb调试(core)、内核态用户态的通信

44 篇文章 0 订阅

快速检测内存泄露

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

mtrace
Linux下mtrace的使用 

#include <stdlib.h>
#include <mcheck.h>

int main(void) { 

    setenv("MALLOC_TRACE", "mtrace.out", 1);

    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 */

}

编译

gcc -g test.c -o test

执行

./test

分析

 mtrace test mtrace.out  (安装 mtrace命令, yum install glibc-utils )

---------------------------------------------------------------------------------------------------------------------------------

gcc

gcc -E 产生预编译后的源代码,即源代码经过预编译后的结果,所有的预编译动作都已完成。如头文件的插入,宏定义的展开。

参考:GCC C Compiler

gdb调试宏、条件断点、命令行参数

#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 #修改变量的文件中的值
参考:这里写链接内容

gdb调试多线程
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这样的话,只调试当前线程

gdb调试多进程
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调试子进程

core
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() 函数来实现内核态与用户态数据的拷贝
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值