2024年最全内存泄漏专题(6)AIX系统内存泄漏调试浅探_aix dbx调试(3),经典实战教程

img
img

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

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

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

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 memory`是`267386880`,这个大小正好是`255M`。也就是说,前`255M`内存申请都成功了,在第`256`次申请的时候失败了。


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


那么 ,为什么是`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

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©;
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§;
}


以上这个程序就稍微复杂一点,不仅在多处都有`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


从以上结果,可以看出,我们一共申请了`2`次`136`字节的内存,`1`次`72`字节的内存,`1`次`264`字节的内存,以及`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`,谁也不知道是怎么编译出来的,用的什么编译器,有没有开启编译器优化,所以最终我们看到的内容可能是这样子的,出现一堆问号,压根不知道是哪个函数调用引起的:





![img](https://img-blog.csdnimg.cn/img_convert/393b0365d4d8b1eaf6ab5907a4c26f43.png)
![img](https://img-blog.csdnimg.cn/img_convert/5f23bb157789b0e3eaeeb6ec82f87664.png)

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

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

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

给官方提`issue`,要么不用人家的库,换个更靠谱的实现。


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





[外链图片转存中...(img-zeOJc5lO-1715835355466)]
[外链图片转存中...(img-DmvIgGy0-1715835355466)]

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

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

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值