Native Heap 为 Android C++层(也为so库)所分配的内存
问题描述
使用dumspsys meminfo 发现 native heap持续增长
解决方式
使用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
名称,而且若此时点击保存同样会造成卡死,需要等到如下图所示,才可以保存。
解析时间巨长
出现上图,则表示解析完毕,可以将数据进行保存
单个数据样式如下
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