2024年最全内存泄漏专题(6)AIX系统内存泄漏调试浅探_aix dbx调试(2),2024年最新Context都没弄明白凭什么拿高薪

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

当然,要想排查内存泄漏,首先我们得确认程序有没有内存泄漏。你可以使用诸如nmon之类的监控工具持续观察某个程序内存的使用情况,看它有没有持续上涨。如果你没有安装nmon,也可以使用系统命令去查看某个进程的内存占用情况:

svmon -P pid -o summary=basic,unit=MB

其中summary是摘要的意思,表示要显示哪些信息,可选项有 basic | longreal | ame | longame, 默认是basic,如果你想了解该参数具体代表什么含义,可以参考:svmon Command - IBM Documentation,由于这个参数在本文不是重点,我们使用basic就行了。

bash-5.0# svmon -P 7995550 -O summary=basic,unit=MB
Unit: MB

-------------------------------------------------------------------------------
     Pid Command          Inuse      Pin     Pgsp  Virtual
 7995550 flow            104.70     31.7        0     97.9


如果程序因为内存泄漏造成了核心转储,也就是所谓的coredump,那么dbx工具就派上用场了。dbx对于调试coredump文件,就像Linux上使用gdb一样丝滑。由于dbx工具基本为Unix系统自带,因此不仅适用于开发调试,在生产环境也有大大的用武之地。

通过dbx的帮助信息,可以大致了解dbx的使用方法:

bash-5.0# dbx -h
dbx: fatal error: DBX Startup Options:

dbx [-a ProcessID] [-B DebugFile] [-c CommandFile] [-I Directory]
[ -E DebugEnvironment ] [-p [OldPath=NewPath:... | File]] [-v]
[-u] [-x] [-F] [-L] [-r] [-C CoreFile | ObjectFile [CoreFile]]

        -a ProcessID        Attach to specified process
        -c CommandFile      Run dbx subcommands in specified file first
        -I Directory        Include Directory in list of directories
                            searched for source files
        -C CoreFile         Allow to analyze core dump without ObjectFile
        -p OldPath=NewPath  Substitute library path for core examination
                            or when attaching to a process
           File             Read library path substitutions for core
                            examination or when attaching to a process
                            from File
        -E DebugEnvironment Specifies the environment variable for the
                            debug program
        -B DebugFile        Specify an alternate debug file on startup
        -v                  Relax core file validity checking
        -u                  Prepend file name symbols with an '@'
        -x                  Strip postfix '\_' from FORTRAN symbols
        -F                  Read all symbols at start-up time
        -L                  Keep linkage symbols
        -r                  Run object file immediately

dbx的很多用法和gdb类似,因此,有gdb调试基础的人来说使用起来可能比较得心应手。这也是本文所使用的的主要的调试手段。

为了更好地演示dbx是如何工作的,我在这里准备 了一个示例代码:

//leak.c
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>

void func1();
void func2();

void main() {
    while(1) func1();
}

void func1() {
    func2();
}

void func2() {
    char \*str;
    str=(char \*) malloc(1024\*1024);
    strcpy(str,"testing");
}

上面是一段非常简单的代码,我们在main函数里不断地调用func1函数 ,又在func1函数里调用func2函数 ,每次调用func2函数,都会申请1MB空间大小的内存,该内存并没有释放 ,也就是说,只要main不终止,就会一直申请下去。

我们运行该段程序,很快,程序就coredump了。

bash-5.0# ./leak
Segmentation fault (core dumped)

如果你的程序没有出现coredump,那很有可能是ulimit没有设置,需要设置ulimit -c参数,它代表的是生成corefile的最大size

ulimit -c unlimited

如果一切设置正确的话,你会在当前目录得到一个core文件。我们可以使用dbx对它进行调试,使用命令为:

dbx process  corefile

运行上述命令后 ,可以得到如下信息:

bash-5.0# dbx ./leak core
Type 'help' for help.
[using memory image in core]
reading symbolic information ...internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: 1283-228 expected char ',', found 's\_\_LC\_locale:,768,32;\_\_meth\_ptr:92,800,32;\_\_data\_ptr:92,832,32;;'
internal error: 1283-228 expected char ',', found '\_\_LC\_locale:,768,32;\_\_meth\_ptr:92,800,32;\_\_data\_ptr:92,832,32;;'
internal error: 1283-228 expected char ';', found '\_LC\_locale:,768,32;\_\_meth\_ptr:92,800,32;\_\_data\_ptr:92,832,32;;'
internal error: unexpected value 44 at line 5176 in file stabstring.c
internal error: 1283-228 expected char ',', found '768,32;\_\_meth\_ptr:92,800,32;\_\_data\_ptr:92,832,32;;'
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: 1283-228 expected char ',', found 's\_LC\_locale\_objhdl:,64,32;;'
internal error: 1283-228 expected char ',', found '\_LC\_locale\_objhdl:,64,32;;'
internal error: 1283-228 expected char ';', found 'LC\_locale\_objhdl:,64,32;;'
internal error: unexpected value 44 at line 5176 in file stabstring.c
internal error: 1283-228 expected char ',', found '64,32;;'
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c
internal error: unexpected value 120 at line 5176 in file stabstring.c


Segmentation fault in func2 at line 20 in file "leak.c"
   20           strcpy(str,"testing");
(dbx) where
func2(), line 20 in "leak.c"
func1(), line 14 in "leak.c"
main(), line 10 in "leak.c"
(dbx)

以上信息已经报告了出现coredump的位置。位于leak.c的第20行,func2函数中,strcpy(str,"testing")这一句代码。你可能仍然不理解,strcpy为什么会造成核心转储,我们继续往下调试:

(dbx) p str
(nil)

当我们打印str的值的时候,得到的是nil,也就是 说 ,在19行,执行str=(char *) malloc(1024*1024);申请内存的时候,没有成功,为了验证这一点,我们将原始代码修改一下:

#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>

void func1();
void func2();

static int mem_size = 0;

void main() {
    while(1) func1();
}

void func1() {
    func2();
}

void func2() {
    char \*str;
    int size = 1024\*1024;
    str=(char \*) malloc(size);
    if (str == NULL) {
        printf("malloc %d bytes memory failed, total malloc size is %d bytes\n", size, mem_size);
        exit(-1);
    } else {
        mem_size += size;
    }
    strcpy(str,"testing");
}

执行后出现如下结果:

bash-5.0# ./leak
malloc 1048576 bytes memory failed, total malloc size is 267386880 bytes
bash-5.0#

可见,确实是申请内存失败了。同时,我们也注意到total memory267386880,这个大小正好是255M。也就是说,前255M内存申请都成功了,在第256次申请的时候失败了。

这个256M的限制其实 和AIX系统 上32位应用程序的数据段大小有关。在AIX上,进程数据段大小主要通过LDR_CNTRL环境变量设置,而LDR_CNTRL这个环境变量的值,往往是和MAXDATA值进行绑定的 。如果你想了解更详细的内容,请参考:进程内存大小限制 (ibm.com)

那么 ,为什么是256M呢 ?我们回到最原始的代码版本。仍然使用dbx打开core文件, 使用proc rlimit可以看到ulimit的限制值。该参数看到的往往和直接在命令行输入ulimit -a看到的一致,不过这种方式展示的是进程的更详细的信息。

(dbx) proc rlimit
rlimit name:          rlimit_cur               rlimit_max       (units)
 RLIMIT_CPU:         (unlimited)             (unlimited)        sec
 RLIMIT_FSIZE:        1073741312              1073741312        bytes
 RLIMIT_DATA:         2147483648             (unlimited)        bytes
 RLIMIT_STACK:          33554432              4294967296        bytes
 RLIMIT_CORE:        (unlimited)             (unlimited)        bytes
 RLIMIT_RSS:            33554432             (unlimited)        bytes
 RLIMIT_AS:          (unlimited)             (unlimited)        bytes
 RLIMIT_NOFILE:             2000             (unlimited)        descriptors
 RLIMIT_THREADS:     (unlimited)             (unlimited)        per process
 RLIMIT_NPROC:       (unlimited)             (unlimited)        per user
(dbx)

可以看到 ,RLIMIT_DATA最大值为unlimited,这说明可用堆内存是没有被限制的。我们可以在dbx外面,使用dump命令查看MAXDATA值:

bash-5.0# dump -Xany -ov ./leak

./leak:

                        ***Object Module Header***
# Sections Symbol Ptr # Symbols Opt Hdr Len Flags
         5      0x0000c9ee           1532                72     0x1002
Flags=( EXEC DYNLOAD DEP_SYSTEM )
Timestamp = "Mar 21 13:49:17 2022"
Magic = 0x1df  (32-bit XCOFF)

                        ***Optional Header***
Tsize        Dsize       Bsize       Tstart      Dstart
0x00000d4d  0x0000042b  0x00000218  0x10000150  0x20000e9d

SNloader     SNentry     SNtext      SNtoc       SNdata
0x0004      0x0002      0x0001      0x0002      0x0002

TXTalign     DATAalign   TOC         vstamp      entry
0x0005      0x0004      0x20001244  0x0001      0x200011f4

maxSTACK     maxDATA     SNbss       magic       modtype
0x00000000  0x00000000  0x0003      0x010b        1L


可以看到 ,最后一行 ,maxDATA显示是0x00000000,对于32位应用来说,0x00000000是一个默认设置,它和0x10000000是等价的,代表1*28大小的内存,即256M,如果超过这个数值,就会出现核心转储。

作为示例,我们不妨将其调大点,设置成0x20000000,也即512M

bash-5.0# LDR\_CNTRL=MAXDATA=0x20000000 ./leak
Segmentation fault (core dumped)

它同样会发生 coredump,我们主要关心一下它所能申请的内存大小。这次我们直接进入dbx去查看。

(dbx) malloc
The following options are enabled:

        Implementation Algorithm........ Default Allocator (Yorktown)


Statistical Report on the Malloc Subsystem:
        Heap 0
                heap lock held by................ UNLOCKED
                bytes acquired from sbrk().......    535895568
                bytes in the freespace tree......        65056
                bytes held by the user...........    535830512
                allocations currently active.....          511
                allocations since process start..          511



The Process Heap
                Initial process brk value........ 0x300014e0
                current process brk value........ 0x4ff132f0
                sbrk()s called by malloc.........        481
(dbx)

我们 重点关注bytes held by the user,它代表由用户申请但没有被释放的内存,可以看到其大小为535830512,大约是511M多一点。这是符合我们的预期的。

这种方式其实比使用where命令更能确定是否是内存泄漏问题造成的coredump,因为对于一些比较复杂的程序来说 ,有可能的确正常的业务处理就要占用超过256M内存,这时候就无法定位是正常业务内存申请导致的coredump,还是内存泄漏导致的。

malloc命令很明显就是查看的malloc申请的堆内存大小, bytes held by the user的意思也很直观。但这种查看手段无法定位是哪一行代码出现了问题。我们当然可以两种手段结合起来看,这里我们提供另外一种调试思路。即使用MALLOCDEBUG来记录内存申请的相关 信息。

使用也很简单:

export MALLOCDEBUG=log:extended,stack_depth:20
<start process>
unset MALLOCDEBUG

extended参数提供了一些额外的信息,比如分配的时间、线程ID等,在多线程调试时很有用。但一般是非必需的。stack_depth代表的是调用栈的深度,如果程序调用的函数层数比较多的话,那么可以设置多一点,可以动态调整。一般来说,20已经够用了。

bash-5.0# export MALLOCDEBUG=log:extended,stack\_depth:20
bash-5.0# ./leak
Segmentation fault (core dumped)
bash-5.0# unset MALLOCDEBUG

执行完后,使用dbx打开core文件, 输入malloc allocation,可以看到比较详细的信息:

(dbx) malloc allocation
Allocations Held by the Process:

   ADDRESS         SIZE HEAP      PID  PTHREAD_T  CLOCKTIME      SEQ STACK TRACEBACK
0x200204e8      1048576    0  2294034 0x00000000 1647844961        0 0xd01f27b4 malloc_common_debugging
                                                                     0xd01288ec init_malloc
                                                                     0xd012a234 malloc
                                                                     0x10000558 func2
                                                                     0x1000050c func1
                                                                     0x100004d8 main
                                                                     0x100001bc __start
0x201204f8      1048576    0  2294034 0x00000000 1647844961        1 0xd01f27b4 malloc_common_debugging
                                                                     0x10000558 func2
                                                                     0x1000050c func1
                                                                     0x100004d8 main
                                                                     0x100001bc __start
0x20220508      1048576    0  2294034 0x00000000 1647844961        2 0xd01f27b4 malloc_common_debugging
                                                                     0x10000558 func2
                                                                     0x1000050c func1
                                                                     0x100004d8 main
                                                                     0x100001bc __start
0x20320518      1048576    0  2294034 0x00000000 1647844961        3 0xd01f27b4 malloc_common_debugging
                                                                     0x10000558 func2
                                                                     0x1000050c func1
                                                                     0x100004d8 main
                                                                     0x100001bc __start
...more

以上信息大部分是重复的,我们可以选取其中一个查看,得到的有用信息是,malloc申请内存是在执行main函数时,每次调用func1函数,然后在func1里调用func2函数,在 func2里进行的内存分配,我们甚至能看到分配的内存大小,为1048576,即1MB

从这些信息 ,我们 大致就能定位 出,内存泄漏很可能出现在func2函数中。因此,我们只需要排查func2函数中malloc的内存是否及时释放就行了。

当然,上面这个例子过于简单,因为只有这一个地方使用了malloc申请内存,因此定位起来还是非常快的 。但实际的企业级代码中 ,调用malloc的地方可能有很多,不可能非常方便地找到到底是哪个地方申请 的内存出现了泄漏。如下面的程序:

//leak2.c
#include<string.h>
#include<stdlib.h>


void func1();
void func2();

void main() {
    char \*a,\*b,\*c,\*d;
    a = (char \*) malloc (1024\*1024);
    b = (char \*) calloc (4, 64);
    c = (char \*) malloc (1024\*1024\*3);
    d = (char \*) malloc (256);
    d = (char \*) realloc(d, 16);
    while(1) func1();
    free(a);
    free(b);
    free(c);
    free(d);
}

void func1() {
    char \*e;
    e = calloc(1, 1024\*1024);
    func2();
    free(e);
}

void func2() {
    char \*str, \*p;
    p = (char \*) malloc(256);
    str=(char \*) malloc(1024\*1024);
    strcpy(str,"testing");
    free(p);
}

以上这个程序就稍微复杂一点,不仅在多处都有malloc的调用,而且还有realloc以及calloc的调用。

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

c(256);
str=(char *) malloc(1024*1024);
strcpy(str,“testing”);
free§;
}


以上这个程序就稍微复杂一点,不仅在多处都有`malloc`的调用,而且还有`realloc`以及`calloc`的调用。



[外链图片转存中...(img-VJpITFPD-1715759831303)]
[外链图片转存中...(img-cFP5RuHV-1715759831303)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值