内存泄漏专题(6)AIX系统内存泄漏调试浅探

简介: 内存泄漏专题(6)AIX系统内存泄漏调试浅探

AIX系统作为IBM公司PowerPC架构的一款比较典型的操作系统,虽然随着Linux的免费开源的普及,市面上几乎很少见,但仍然广泛存在于政府、银行、证券等核心系统中。由于该系统闭源,很多调试手段都无法在AIX上有效运用,比如mtracebccvalgrind等,空有神力,却无法为之臂助。好在IBM自己提供了一套内存泄漏的调试工具,可以比较方便的定位与调试。

当然,要想排查内存泄漏,首先我们得确认程序有没有内存泄漏。你可以使用诸如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的调用。

运行这个程序,很明显也会产生coredump。我们使用上面的方式打开core文件:

bash-5.0# dbx ./leak2 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 33 in file "leak2.c"
   33       strcpy(str,"testing");
(dbx) malloc allocation
Allocations Held by the Process:
   ADDRESS         SIZE HEAP    ALLOCATOR
0x200015f8      1048584    0     YORKTOWN
0x20101608          264    0     YORKTOWN
0x20101718      3145736    0     YORKTOWN
0x20401728           24    0     YORKTOWN
0x20401748      1048584    0     YORKTOWN
0x20501758          264    0     YORKTOWN
0x20501868      1048584    0     YORKTOWN
0x20601878      1048584    0     YORKTOWN
0x20701888      1048584    0     YORKTOWN
0x20801898      1048584    0     YORKTOWN
0x209018a8      1048584    0     YORKTOWN
0x20a018b8      1048584    0     YORKTOWN
0x20b018c8      1048584    0     YORKTOWN
0x20c018d8      1048584    0     YORKTOWN
0x20d018e8      1048584    0     YORKTOWN
0x20e018f8      1048584    0     YORKTOWN
0x20f01908      1048584    0     YORKTOWN
0x21001918      1048584    0     YORKTOWN
0x21101928      1048584    0     YORKTOWN
...more

如上图所示,虽然我们能看出有很多1048584字节的内存申请,但并不能定位到是哪个地方泄漏造成的。这时候我们可以将allocation信息dump出来,操作十分简单,只需要在malloc allocation后面重定向到一个文件即可:

(dbx) malloc allocation > allocs.out

这样在本地就会产生一个allocs.out文件,当然这个文件我们仍然是无法直接分析的,这时候可以借助一些脚本来辅助分析,比如我们可以写一个如下的awk脚本:

#!/bin/awk -f
function sort(ARRAY, ELEMENTS,   temp, i, j) {
        for (i = 2; i <= ELEMENTS; ++i) {
                for (j = i; ARRAY[j-1] > ARRAY[j]; --j) {
                        temp = ARRAY[j]
                        ARRAY[j] = ARRAY[j-1]
                        ARRAY[j-1] = temp
                }
        }
        return
}
BEGIN{
        sum=0;
        check[0,0]=0
        arr[0]=0
        i=0
}
{
   if((match($1,"^0x[2-9a]") && length($1)==10) || (match($1,"^0x0") && length($1)==18)){
        sum+=$2;
        if(check[$2,0] == 1){
            check[$2,1]++
        #    print $2  "*" check[$2,1]
        }
        else {
             arr[i++]=$2
             check[$2,0] = 1
             check[$2,1] = 1
        }
   }
}
END{
    ind=0
    n=0
    print "Final Result"
    sort(arr,i)
    for(j=0;j<=i;j++){
        ind=arr[j];
#       print ind " " check[ind,1] " and " ind*check[ind,1]
        print "Total: " ind*check[ind,1] "      Size of allocation: " ind "     Frequency of allocation: " check[ind,1]
    }
    print sum
}

运行这个脚本,可以得到如下结果:

bash-5.0# ./alloc.awk allocs.out
Final Result
Total: 0      Size of allocation:      Frequency of allocation: 
Total: 272      Size of allocation: 136     Frequency of allocation: 2
Total: 72      Size of allocation: 72     Frequency of allocation: 1
Total: 264      Size of allocation: 264     Frequency of allocation: 1
Total: 267388920      Size of allocation: 1048584     Frequency of allocation: 255
267389528

从以上结果,可以看出,我们一共申请了2136字节的内存,172字节的内存,1264字节的内存,以及255次内存,每次申请1048584字节,一共申请了267389528字节(255M)。这样很容易就能判断出出问题的是这255次申请的大内存。

但是在实际的场景中,内存的频繁申请和释放会导致分析变得异常困难,尤其是涉及到第三方库的时候,因为我们无法把控第三方库是怎么实现的,以及它们是如何编译出来的,如果第三方库有内存泄漏,那么用上面的方法,可能就不是那么友好了。

这时候,仍然要祭出MALLOCDEBUG大杀器。

比如我们看到的信息如下:

0x000000011010c290      1114112    0 17236260 0x00000102 1533826269     1270 0x00900000000053ec0 malloc_common_debugging
                                              0x0090000002172f890 sqloGetPrivateMemoryFromOs__FPPvCUlCUiCP12SMemLogEvent
                                              0x009000000217330ac allocateChunkGroup__7SMemSetF9SqloChunkT1UiCP12SMemResource
                                              0x00900000021645d10 getChunksFromTree__7SMemSetF9SqloChunkT1CPUiPP9SChunkGrpP9SqloC
                                              0x009000000216447c8 getContiguousChunks__7SMemSetF9SqloChunkCPUiPP9SChunkGrpP9SqloC
                                              0x0090000002178be7c getNewChunkSubgroup__13SQLO_MEM_POOLFCUlUiT1CP12SMemLogEventPP1
                                              0x00900000021790878 allocateMemoryBlock__13SQLO_MEM_POOLFCUlT1UiT1PP17SqloChunkSubg
                                              0x00900000021726824 sqloGetMemoryBlockExtended
                                              0x00900000021724168 sqlo_init_generic_data_temp__FP15sql_static_dataCUlT2CbP19SqloR
                                              0x009000000219cc170 sqlo_create_init_EDU_data_temp__FUiP19SqloResourceBinding
                                              0x00000000100001688 DB2main
                                              0x00900000021706194 sqloEDUMainEntry__FPcUi
                                              0x009000000217058b0 sqloEDUEntry
                                              0x00900000000598fec _pthread_body

上面这段信息告诉我们sqloGetPrivateMemoryFromOs__FPPvCUlCUiCP12SMemLogEvent申请了一块大内存,那么这个函数属于哪个第三方库呢?我们可以使用map命令来查看:

(dbx) map 0x0090000002172f890
Entry 61:
   Object name: ./db2/db2sox/DB2_11.1_FP2/lib64/libdb2e.a
   Member name: shr_64.o
   Text origin:     0x90000002158f000
   Text length:     0x10e1bded
   Data origin:     0x9001000a540b1d8
   Data length:     0x2d8e818
   File descriptor: 0x7d

即这个函数是db2/db2sox/DB2_11.1_FP2/lib64/libdb2e.a中的,如果你认定第三方库存在内存泄漏,那么就说明和你自己的代码关系不大,要么,你给官方提issue,要么不用人家的库,换个更靠谱的实现。

当然了,还有更麻烦的,因为第三方库我们拿来用的时候,可能本身只是一个.a或者.so,谁也不知道是怎么编译出来的,用的什么编译器,有没有开启编译器优化,所以最终我们看到的内容可能是这样子的,出现一堆问号,压根不知道是哪个函数调用引起的:

0x2006e138           12    0  8716372 0x00000000 1535042884      270 0xd01b1854 malloc_common_debugging
                                                                     0xd193a4a8 ??
                                                                     0xd193af9c ??
                                                                     0xd193dea0 ??
                                                                     0xd19550fc ??
                                                                     0xd196cabc ??
                                                                     0xd1a3b340 ??
                                                                     0xd1a3b180 ??
                                                                     0x1007c6e8 ssh_OpenSSL_add_all_algorithms
                                                                     0x10004604 main
                                                                     0x10000214 __start

这时,我们可以借助一个叫listi的命令来查看:

(dbx) listi 0xd193a4a8
0xd193a4a8 (default_malloc_ex+0x18) 80410014            lwz   r2,0x14(r1)
0xd193a4ac (default_malloc_ex+0x1c) 81810048            lwz   r12,0x48(r1)
0xd193a4b0 (default_malloc_ex+0x20) 38210040           addi   r1,0x40(r1)
0xd193a4b4 (default_malloc_ex+0x24) 7d8803a6           mtlr   r12
0xd193a4b8 (default_malloc_ex+0x28) 4e800020            blr

这个命令会以汇编的方式将该地址展开,从以上信息中,我们可以看到调用的函数是default_malloc_ex,然后拿到0xd193a4a8地址后,就可以使用map命令来判断到底哪个第三方库的问题了。

注意事项

前面提到,AIX定位内存泄漏的主要手段是调试生成的core文件;而前面又提到过,生成core文件的阈值是由LDR_CNTRL环境变量决定的。那么,这也就对core文件的生成提出了比较高的要求。

首先引出的问题是,如果LDR_CNTRL设置的比较大,如2G,程序短时间内并不会达到这么大的内存,因此也不会主动core掉,这时候如果你发现内存是在缓慢上升,那么该如何判断是否是内存泄漏呢?

其实这时候我们可以手动生成一个core文件,生成方式如下:

gencore <PID of process> /<directory>/core

如果你觉得生成core文件比较麻烦,你也可以直接attach到正在运行的进程上,命令如下:

dbx -a <PID>

具体使用和gdb类似,这里就不多赘述了。

第二个问题,我们必须保证core文件是完整的,残缺的core文件是无法调试的,这样的core文件有还不如没有。

那么如何保证core文件的完整呢?首先我们可以修改ulimit参数,这点和Linux下一样。

ulimit -c unlimited

如果想永久生效,则可以修改/etc/security/limits文件。当然还可以通过chdev -l sys0 -a fullcore=true命令来允许生成完整的core文件(参考[Enabling full AIX core files - IBM Documentation](Enabling full AIX core files - IBM Documentation))。

还有一点就是,MALLOCDEBUG虽然好用,但会带来额外的CPU和内存开销,适用于开发环境调试,但不适用于生产环境长时间运行。否则你以为为啥这个参数不默认放开呢?


本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对C/C++课程感兴趣的读者,可以点击链接,查看详细的服务:C/C++Linux服务器开发/高级架构师

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
1月前
|
存储 缓存 监控
|
1月前
麒麟系统mate-indicators进程占用内存过高问题解决
【10月更文挑战第7天】麒麟系统mate-indicators进程占用内存过高问题解决
195 2
|
9天前
|
监控 Java Android开发
深入探讨Android系统的内存管理机制
本文将深入分析Android系统的内存管理机制,包括其内存分配、回收策略以及常见的内存泄漏问题。通过对这些方面的详细讨论,读者可以更好地理解Android系统如何高效地管理内存资源,从而提高应用程序的性能和稳定性。
37 16
|
16天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
36 6
|
25天前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
147 9
|
2月前
|
Java
在 ArkTS 中,如何有效地进行内存管理和避免内存泄漏?
【9月更文挑战第25天】在ArkTS中,有效进行内存管理并避免内存泄漏的方法包括:及时释放不再使用的资源,如关闭监听器和清理定时器;避免循环引用,通过弱引用打破循环;合理使用单例模式,确保单例对象正确释放;及时处理不再使用的页面和组件,在卸载时清理相关资源。
101 9
|
1月前
|
运维 JavaScript Linux
容器内的Nodejs应用如何获取宿主机的基础信息-系统、内存、cpu、启动时间,以及一个df -h的坑
本文介绍了如何在Docker容器内的Node.js应用中获取宿主机的基础信息,包括系统信息、内存使用情况、磁盘空间和启动时间等。核心思路是将宿主机的根目录挂载到容器,但需注意权限和安全问题。文章还提到了使用`df -P`替代`df -h`以获得一致性输出,避免解析错误。
|
2月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
1月前
麒麟系统mate-indicators进程占用内存过高问题解决
【10月更文挑战第5天】麒麟系统mate-indicators进程占用内存过高问题解决
149 0
|
2月前
|
监控 Ubuntu API
Python脚本监控Ubuntu系统进程内存的实现方式
通过这种方法,我们可以很容易地监控Ubuntu系统中进程的内存使用情况,对于性能分析和资源管理具有很大的帮助。这只是 `psutil`库功能的冰山一角,`psutil`还能够提供更多关于系统和进程的详细信息,强烈推荐进一步探索这个强大的库。
45 1