Android Native Leak分析

Native Heap 为 Android C++层(也为so库)所分配的内存


问题描述

使用dumspsys meminfo 发现 native heap持续增长
NativeHeap

解决方式

使用ddms插件分析Native Heap,可以获取到app从开始运行到点击时所有native heap的申请状况。
步骤
- 设置malloc debug模式
7.0之前 setprop libc.debug.malloc 1
7.0之后 setprop libc.debug.malloc.options backtrace
- 重启android服务
adb shell stop
adb shell start
- 查看so在android系统上的映射位置
cat /proc/<pid\>/maps | egrep /lib/arm/lib.*\.so | grep r-xp
需在app运行时查看

d70c3000-d7130000 r-xp 00000000 fc:02 11140      /a.so
d7150000-d7184000 r-xp 00000000 fc:02 11134      /b.so
d71dd000-d71e7000 r-xp 00000000 fc:02 11136      /c.so
d7206000-d723f000 r-xp 00000000 fc:02 11139      /d.so
e4f0c000-e4f18000 r-xp 00000000 fc:02 11135      /e.so
  • 解析申请Native Heap位置
    addr2line -Cfe lib*.so [addr]
    如出现问题查看[1]

数据分析

问题

由于查看的是video的native stack 溢出,使用ddms.bat时,很容易造成卡死

解决方法

将数据保存至本地,自行分析

获取数据方法

这里写图片描述
点击红框会出现很长时间的卡顿,接着会出现如下状况
这里写图片描述
此时还不能保存,因为还没有解析出lib.so名称,而且若此时点击保存同样会造成卡死,需要等到如下图所示,才可以保存。
解析时间巨长
Alt text

这里写图片描述
出现上图,则表示解析完毕,可以将数据进行保存
单个数据样式如下

Allocations: 1
Size: 614400
TotalSize: 614400
BeginStacktrace:
    d7a1127e    /lib/arm/a.so --- d7a1127e --- 
    d7a110e2    /lib/arm/a.so --- d7a110e2 --- 
    d7775f92    /lib/arm/b.so --- d7775f92 --- 
    d777614e    /lib/arm/b.so --- d777614e --- 
    d7773950    /lib/arm/b.so --- d7773950 --- 
    d777381c    /lib/arm/b.so --- d777381c --- 
    d777399c    /lib/arm/b.so --- d777399c --- 
    f42d9d4e    /system/lib/libc.so --- f42d9d4e --- 
    f42ad038    /system/lib/libc.so --- f42ad038 --- 
EndStacktrace

当保存完成后,建议重新打开ddms获取下一次数据

脚本 python3

思路
- 提取数据(不包含官方so)
- 相同数据,不添加到新链表,使用计数
- 按照size大小由大到小排序,若相同看第一个库的地址排序
- 比较两个链表计数数据,提取差值数据
- 通过so在Android中maps的映射位置,计算偏移量
链表 解析出来的Native Heap

#Parse Native heap from DDMS's Native Heap save
import linecache
import re

# Compare Files
FileName0 = "C:\\Users\\*\\Desktop\\memCrash\\allocations61.txt"
FileName1 = "C:\\Users\\*\\Desktop\\memCrash\\allocations64.txt"

app_lib  = {
'a.so' :   0xd70c9000, 
'b.so' :   0xd715d000, 
'c.so' :   0xd71d6000, 
'd.so' :   0xd7206000, 
'e.so' :   0xe4f23000
}

def parseNativeHeap(str):
    temp  = re.match(r'\t([0-9,a-f]{8}).*/([^/]*)\s---\s[0-9,a-f]{8}.*', str)
    key   = int(temp.group(1), 16)
    value = temp.group(2)

    # Remove system library
    if value not in app_lib:
        key = 0
        value = 0

    return key, value

def isNatvieSame(list1, list, list_num):
    # Remove empty list
    if len(list1) <= 1:
        return 1

    if len(list) >= len(list_num):
        list_num.append(1)

    if list1 in list:
        nbr = list.index(list1)
        list_num[nbr] += 1
        return 1
    return 0

# list sort
def addAndSortList(list1, list):
    if len(list) == 0:
        list.insert(0, list1)
        return

    for i in range(0, len(list)):
        # list中 0 = size, 1 = lib,2 = addr
        # 判断顺序
        ## 判断size,从大到小
        ## 地址从大到小
        if list1[0] < list[i][0] and (i + 1 == len(list) or list1[0] > list[i+1][0]):
            list.insert(i + 1, list1)
            return
        elif list1[0] == list[i][0]:
            # print(list1, "------", list[i],  "------", i, len(list))
            # 如果地址小 且 (下一个值不是终点 或 下一个值size不同 或 地址大于下一个值)
            if list1[2] < list[i][2] and (i + 1 == len(list) or list1[0] != list[i+1][0] or list1[2] > list[i+1][2]):
                list.insert(i + 1, list1)
                return

    list.append(list1)


def countNativeHeap(file_name):
    lineLen  = len(open(file_name, 'r').readlines())
    lineStrs = linecache.getlines(file_name)

    list = []
    list_num = []
    for i in range(0, lineLen):
        line = lineStrs[i]

        # Pass blank line
        if len(line) < 10:
            continue

        name = line.split(':')[0]

        if   name == "Allocations":
            list1 = []
        elif name == "Size":
            continue
        elif name == "TotalSize":
            list1.append(int(line.split(':')[1]))
        elif name == "BeginStacktrace":
            continue
        elif name == "EndStacktrace\n":
            if not isNatvieSame(list1, list, list_num):
                addAndSortList(list1, list)
        else:
            key, value = parseNativeHeap(line)
            if key != 0:
                list1.append(value)
                list1.append(key)

    # print(len(list))
    # print(list_num)

    return list, list_num

# Get Native List
list1, list1_num = countNativeHeap(FileName0)
list2, list2_num = countNativeHeap(FileName1)

print('len', len(list1), len(list2))
# print(list1)

# In second list find first element
def findDiffStack(first, second, first_num, second_num, diff, diff_num, noneExist, noneExist_num):
    for i in second:
        second_nbr = second.index(i)
        if i in first:
            first_nbr = first.index(i)
            noneExist_size = second_num[second_nbr] - first_num[first_nbr]
            if noneExist_size != 0:
                diff.append(i)
                diff_num.append(noneExist_size)
        else:
            noneExist.append(i)
            noneExist_num.append(second_num[second_nbr])

# 变化值
list_diff = []
list_diff_num = []
# 不存在值
list_noneExist = []
list_noneExist_num = []

findDiffStack(list1, list2, list1_num, list2_num, list_diff, list_diff_num, list_noneExist, list_noneExist_num)

print('diff     ', len(list_diff_num),  'size', list_diff_num)
print('noneExist', len(list_noneExist), 'size', list_noneExist)

# Caculate native heap create addr
def caculateAddr(src, orgin):
    for i in src:
        for j in range(1, len(i), 2):
            key = i[j]
            if i[j] in orgin:
                i[j + 1] -= orgin.get(i[j])

caculateAddr(list_diff, app_lib)
caculateAddr(list_noneExist, app_lib)

# Display
def displayResult(list, num):
    for i,z in zip(list, num):
        str = '\nSize %d * %d'%(i[0], z)
        print(str)
        for j in range(1, len(i), 2):
            str = '%20s %X'%(i[j], i[j+1])
            print(str)

displayResult(list_diff,      list_diff_num)
displayResult(list_noneExist, list_noneExist_num)

解析结果

len 250 250
diff      21 size [77, 17, -1, 73, 14, 7, -1, 1, -1, -1, 73, 46, 7, 8, 77, 15, 25, 14, 17, 1, -1]
noneExist 1 size [[64, 'b.so', 3609046558, 'a.so', 3609290044, 'a.so', 3609286868]]

Size 921600 * 77
       a.so F150
       a.so EFB2
      c.so 2A45E
      c.so 2A61A
      c.so 27E1C
      c.so 27CE8
      c.so 27E68

Size 921600 * 17
       a.so F150
       a.so EFB2
      c.so 2789A
      c.so 2C0D2
      c.so 272EC
      c.so 2E666
      c.so 484CA

Size 176 * -1
      d.so 9186
      d.so 4EE0
      c.so 484E0

出现问题

  • 使用addr2line 显示 ??:0
    分析方式,需找到obj/local/armeabi-v7a/*.so文件分析 注意非最终so路径,而是中间路径obj
  • 使用readelf解析so库
    readelf -a *.so
  • 使用anroid7.0通过HDMI接显示器,start后不能显示,重新插拔HDMI线即可
  • 打开ddms.bat打开失败,没有安装java;
    ddms.bat在android-sdk/tools目录下
  • 编译需要加-g会生成symbols信息在lib中
  • ddms中没有Native Heap
    在ddms.cfg中添加 native=true
    注意写ddms.cfg如果不成功,有可能是权限问题,可以某度查找解决方法

技巧

  • 查找文件
    使用everything检索文件
    这里写图片描述
  • 查看汇编代码
    arm-linux-androideabi-objdump -dS *.so > *.dump
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值