简介
QQLeakChecker是OOMDetector中用于检测内存走漏的库,现在只可检测真机运行时的内存走漏,模拟器暂不支撑
为了避免内存拜访冲突,扫描进程需要挂起一切线程,整个进程会卡住程序1-2秒。因为扫描进程较为耗时,占用的内存较大,建议在App的测验阶段使用,与自动化测验结合可快速高效的发现走漏问题
在IOS系统中,或许包括指针变量的内存区域有堆内存、栈内存、大局数据区和寄存器,OOMDetector 经过对这些区域遍历扫描即可找到一切或许的“指针变量”,整个扫描流程完毕后都没有“指针变量”指向的内存块即是走漏内存块。
OOMDetector能够记录到每一个目标的分配堆栈信息,要从这些目标中找出 “走漏目标”,我们需要知道在程序可拜访的进程内存空间中,是否有“指针变量”指向对应的内存块。那些在整个进程内存空间都没有指针指向的内存块,便是我们要找的走漏内存块。
作业流程
UML类图
调用
- (QQLeakChecker *)currentLeakChecker
{
return _leakChecker;
}
- (void)executeLeakCheck:(QQLeakCheckCallback)callback
{
[[self currentLeakChecker] executeLeakCheck:callback];
}
初始化
- 创建了CStackChecker
- 创建了CSegmentChecker
- 创建了CHeapChecker
- 创建了CRegisterChecker
-(id)init{
if(self = [super init]){
_stackChecker = new CStackChecker(global_leakChecker);
_segmentChecker = new CSegmentChecker(global_leakChecker);
_heapChecker = new CHeapChecker(global_leakChecker);
_registerChecker = new CRegisterChecker(global_leakChecker);
_max_stack_depth = 10;
_needSysStack = YES;
}
return self;
}
发动
-(void)executeLeakCheck:(QQLeakCheckCallback)callback{
if(!_isChecking && _isStackLogging){
_segmentChecker->initAllSegments();
global_leakChecker->leakCheckingWillStart();
if(_stackChecker->suspendAllChildThreads()){
global_leakChecker->unlockHashmap();
_registerChecker->startPtrCheck();
_stackChecker->startPtrCheck(2);
_segmentChecker->startPtrcheck();
_heapChecker->startPtrCheck();
size_t total_size = 0;
NSString *stackData = global_leakChecker->get_all_leak_stack(&total_size);
_stackChecker->resumeAllChildThreads();
_segmentChecker->removeAllSegments();
global_leakChecker->leakCheckingWillFinish();
callback(stackData,total_size);
}
}
}
获取对应的Segments
-
获取image_header
-
遍历load commands,获取__DATA段的segment
-
获取section,判别section的称号
-
获取__data段,__common段,__bss段的segment存起来
Data段结构
#define SEG_DATA "__DATA" /* the tradition UNIX data segment */
#define SECT_DATA "__data" /* the real initialized data section */
/* no padding, no bss overlap */
#define SECT_BSS "__bss" /* the real uninitialized data section*/
/* no padding */
#define SECT_COMMON "__common" /* the section common symbols are */
/* allocated in by the link editor */
void CSegmentChecker::initAllSegments()
{
uint32_t count = _dyld_image_count();
for (uint32_t i = 0; i < count; i++) {
const mach_header_t* header = (const mach_header_t*)_dyld_get_image_header(i);
const char* image_name = _dyld_get_image_name(i);
const char* tmp = strrchr(image_name, '/');
vm_address_t slide = _dyld_get_image_vmaddr_slide(i);
if (tmp) {
image_name = tmp + 1;
}
long offset = (long)header + sizeof(mach_header_t);
for (unsigned int i = 0; i < header->ncmds; i++)
{
const segment_command_t* segment = (const segment_command_t*)offset;
if (segment->cmd == MY_SEGMENT_CMD_TYPE && strncmp(segment->segname,"__DATA",6) == 0) {
section_t *section = (section_t *)((char*)segment + sizeof(segment_command_t));
for(uint32_t j = 0; j < segment->nsects;j++){
if((strncmp(section->sectname,"__data",6) == 0) || (strncmp(section->sectname,"__common",8) == 0) || (strncmp(section->sectname,"__bss",5) == 0)){
vm_address_t begin = (vm_address_t)section->addr + slide;
vm_size_t size = (vm_size_t)section->size;
dataSegment seg = {image_name,section->sectname,begin,size};
segments.push_back(seg);
}
section = (section_t *)((char *)section + sizeof(section_t));
}
}
offset += segment->cmdsize;
}
}
}
开端检测
避免内存拜访冲突,首先挂起一切线程,之后开端检测
- 获取task的一切线程
- 遍历线程,排除掉当时操作线程
- 调用thread_suspend
BOOL CStackChecker::suspendAllChildThreads(){
kern_return_t ret = task_threads(mach_task_self(), &thread_list, &thread_count);
if (ret != KERN_SUCCESS) {
return false;
}
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
thread_t thread = thread_list[i];
if (thread == mach_thread_self()) {
continue;
}
if (KERN_SUCCESS != thread_suspend(thread)) {
for (mach_msg_type_number_t j = 0; j < i; j++){
thread_t pre_thread = thread_list[j];
if (pre_thread == mach_thread_self()) {
continue;
}
thread_resume(pre_thread);
}
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
mach_port_deallocate(mach_task_self(), thread_list[i]);
}
vm_deallocate(mach_task_self(), (vm_address_t)thread_list, thread_count * sizeof(thread_t));
return false;
}
}
return YES;
}
寄存器
_registerChecker->startPtrCheck();
- 获取一切线程
- 遍历非本线程的一切线程
- 获取MY_EXCEPITON_STATE(不同设备不同,宏包裹),排除掉因为软中断,硬中断导致的反常
- 获取MY_THREAD_STATE
- 遍历通用寄存器,arm64有29个,arm有13个
bool CRegisterChecker::startPtrCheck(){
#if !TARGET_IPHONE_SIMULATOR
thread_act_array_t thread_list;
mach_msg_type_number_t thread_count;
kern_return_t ret = task_threads(mach_task_self(), &thread_list, &thread_count);
if (ret != KERN_SUCCESS) {
return false;
}
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
thread_t thread = thread_list[i];
if(thread != mach_thread_self()){
_STRUCT_MCONTEXT _mcontext;
mach_msg_type_number_t stateCount = MY_EXCEPTION_STATE_COUNT;
kern_return_t ret = thread_get_state(thread, MY_EXCEPITON_STATE, (thread_state_t)&_mcontext.__es, &stateCount);
if (ret != KERN_SUCCESS) {
return false;
}
if (_mcontext.__es.__exception != 0) {
return false;
}
stateCount = MY_THREAD_STATE_COUTE;
ret = thread_get_state(thread, MY_THREAD_STATE, (thread_state_t)&_mcontext.__ss, &stateCount);
if (ret != KERN_SUCCESS) {
return false;
}
#ifdef __LP64__
vm_address_t x_regs[29];
vm_size_t len = sizeof(x_regs);
ret = vm_read_overwrite(mach_task_self(), (vm_address_t)(_mcontext.__ss.__x),len, (vm_address_t)x_regs, &len);
for(int i = 0;i < 29;i++){
leakChecker->findPtrInMemoryRegion((vm_address_t)x_regs[i]);
}
#else
vm_address_t r_regs[13];
vm_size_t len = sizeof(r_regs);
ret = vm_read_overwrite(mach_task_self(), (vm_address_t)(_mcontext.__ss.__r),len, (vm_address_t)r_regs, &len);
for(int i = 0;i < 13;i++){
leakChecker->findPtrInMemoryRegion((vm_address_t)r_regs[i]);
}
#endif
}
}
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
mach_port_deallocate(mach_task_self(), thread_list[i]);
}
vm_deallocate(mach_task_self(), (vm_address_t)thread_list, thread_count * sizeof(thread_t));
return true;
#else
return true;
#endif
}
在内存区域中寻找指针
bool CLeakChecker::findPtrInMemoryRegion(vm_address_t address){
ptr_log_t *ptr_log = qleak_ptrs_hashmap->lookupPtr(address);
if(ptr_log != NULL){
ptr_log->size++;
return true;
}
return false;
}
ptr_log_t *CPtrsHashmap::lookupPtr(vm_address_t addr)
{
size_t offset = addr%(entry_num - 1);
base_entry_t *entry = hashmap_entry + offset;
ptr_log_t *parent = (ptr_log_t *)entry->root;
if(parent != NULL){
if(parent->address == addr){
return parent;
}
ptr_log_t *current = parent->next;
while(current != NULL){
if(current->address == addr){
return current;
}
parent = current;
current = current->next;
}
}
return NULL;
}
栈内存
_stackChecker->startPtrCheck(2);
- 获取一切线程
- 遍历线程,pthread需要从mach_thread转换过来
- 如果是当时线程,找对应的frame pointer
- 如果是其他线程,找对应的stack pointer
- 计算得到对应的vmrange
void CStackChecker::startPtrCheck(size_t bt){
thread_act_array_t thread_list;
mach_msg_type_number_t thread_count;
kern_return_t ret = task_threads(mach_task_self(), &thread_list, &thread_count);
if (ret != KERN_SUCCESS) {
return;
}
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
thread_t thread = thread_list[i];
pthread_t pthread = pthread_from_mach_thread_np(thread);
vm_size_t stacksize = pthread_get_stacksize_np(pthread);
if(stacksize > 0){
void *stack = pthread_get_stackaddr_np(pthread);
if(stack != NULL){
vm_address_t stack_ptr;
if(thread == mach_thread_self()){
find_thread_fp(thread, &stack_ptr, bt);
}
else{
find_thread_sp(thread, &stack_ptr);
}
vm_size_t depth = (vm_address_t)stack - stack_ptr + 1;
if(depth > 0 && depth <= stacksize){
vm_range_t range = {stack_ptr,depth};
check_ptr_in_vmrange(range, STACK_TYPE);
}
}
}
}
for (mach_msg_type_number_t i = 0; i < thread_count; i++) {
mach_port_deallocate(mach_task_self(), thread_list[i]);
}
vm_deallocate(mach_task_self(), (vm_address_t)thread_list, thread_count * sizeof(thread_t));
}
check_ptr_in_vmrange
void CMemoryChecker::check_ptr_in_vmrange(vm_range_t range,memory_type type)
{
const uint32_t align_size = sizeof(void *);
vm_address_t vm_addr = range.address;
vm_size_t vm_size = range.size;
vm_size_t end_addr = vm_addr + vm_size;
if (align_size <= vm_size)
{
uint8_t *ptr_addr = (uint8_t *)vm_addr;
for (uint64_t addr = vm_addr;
addr < end_addr && ((end_addr - addr) >= align_size);
addr += align_size, ptr_addr += align_size)
{
vm_address_t *dest_ptr = (vm_address_t *)ptr_addr;
leakChecker->findPtrInMemoryRegion(*dest_ptr);
}
}
}
在内存区域中寻找指针
findPtrInMemoryRegion
大局内存区
_segmentChecker->startPtrcheck();
void CSegmentChecker::startPtrcheck()
{
for(auto it = segments.begin();it != segments.end();it++){
dataSegment seg = *it;
vm_range_t range = {seg.beginAddr,seg.size};
check_ptr_in_vmrange(range,SEGMENT_TYPE);
}
}
check_ptr_in_vmrange
栈内存和大局内存的遍历,最后都是经过vmrange来检测
堆内存
_heapChecker->startPtrCheck();
- 遍历MemoryZone
- 遍历Zone中的指针
void CHeapChecker::startPtrCheck(){
vm_address_t *zones = NULL;
unsigned int zone_num;
kern_return_t err = malloc_get_all_zones (mach_task_self(), memory_reader, &zones, &zone_num);
if (KERN_SUCCESS == err)
{
for (int i = 0; i < zone_num; ++i)
{
if(zones[i] == (vm_address_t)(leakChecker->getMemoryZone())){
continue;
}
enumerate_ptr_in_zone(this,(const malloc_zone_t *)zones[i],CHeapChecker::check_ptr_in_heap);
}
}
}
遍历一切使用中的malloc指针
void CHeapChecker::enumerate_ptr_in_zone (void *baton, const malloc_zone_t *zone,vm_range_recorder_t recorder)
{
if (zone && zone->introspect && zone->introspect->enumerator)
zone->introspect->enumerator (mach_task_self(),
this,
MALLOC_PTR_IN_USE_RANGE_TYPE,
(vm_address_t)zone,
memory_reader,
recorder);
}
收拾上报
global_leakChecker->get_all_leak_stack(&total_size);
- 遍历leaked_hashmap,获取hashmap_entry
- 从entry->root得到ptr_log_t
- 经过ptr_log_t中的digest,从qleak_stacks_hashmap中找到merge_stack
- 在merge_stack中找到对应的image和adrress信息,记录下来
NSString* CLeakChecker::get_all_leak_stack(size_t *total_count)
{
get_all_leak_ptrs();
NSMutableString *stackData = [[[NSMutableString alloc] init] autorelease];
size_t total = 0;
for(size_t i = 0; i <leaked_hashmap->getEntryNum(); i++){
base_entry_t *entry = leaked_hashmap->getHashmapEntry() + i;
leaked_ptr_t *current = (leaked_ptr_t *)entry->root;
while(current != NULL){
merge_leaked_stack_t *merge_stack = qleak_stacks_hashmap->lookupStack(current->digest);
if(merge_stack == NULL) {
current = current->next;
continue;
}
total += current->leak_count;
[stackData appendString:@"********************************\n"];
[stackData appendFormat:@"[**LeakCheck**] Leak addr:0x%lx name:%s leak num:%u, stack:\n",(long)current->address, merge_stack->extra.name, current->leak_count];
for(size_t j = 0; j < merge_stack->depth; j++){
vm_address_t addr = (vm_address_t)merge_stack->stack[j];
segImageInfo segImage;
if(stackHelper->getImageByAddr(addr, &segImage)){
[stackData appendFormat:@""%lu %s 0x%lx 0x%lx" ",j,(segImage.name != NULL) ? segImage.name : "unknown",segImage.loadAddr,(long)addr];
}
}
[stackData appendString:@"\n"];
current = current->next;
}
}
[stackData insertString:[NSString stringWithFormat:@"QQLeakChecker find %lu leak object!!!\n",total] atIndex:0];
*total_count = total;
if(total > 0){
uploadLeakData(stackData);
}
return stackData;
}
global_leakChecker->get_all_leak_ptrs();
- 遍历qleak_ptrs_hashmap,获取hashmap_entry,得到ptr_log_t
- 过ptr_log_t中的digest,从qleak_stacks_hashmap中找到merge_stack
- 判别当时的ptr_log_t,添加在leaked_hashmap中,并从qleak_ptrs_hashmap中移除
void CLeakChecker::get_all_leak_ptrs()
{
for(size_t i = 0; i < qleak_ptrs_hashmap->getEntryNum(); i++)
{
base_entry_t *entry = qleak_ptrs_hashmap->getHashmapEntry() + i;
ptr_log_t *current = (ptr_log_t *)entry->root;
while(current != NULL){
merge_leaked_stack_t *merge_stack = qleak_stacks_hashmap->lookupStack(current->digest);
if(merge_stack == NULL) {
current = current->next;
continue;
}
if(merge_stack->extra.name != NULL){
if(current->size == 0){
leaked_hashmap->insertLeakPtrAndIncreaseCountIfExist(current->digest, current);
vm_address_t address = current->address;
qleak_ptrs_hashmap->removePtr(address,NULL,NULL);
}
current->size = 0;
}
else{
vm_address_t address = current->address;
const char* name = objcFilter->getObjectNameExceptBlack((void *)address);
if(name != NULL){
if(current->size == 0){
merge_stack->extra.name = name;
leaked_hashmap->insertLeakPtrAndIncreaseCountIfExist(current->digest, current);
vm_address_t address = (vm_address_t)(0x100000000 | current->address);
qleak_ptrs_hashmap->removePtr(address,NULL,NULL);
}
current->size = 0;
}
else {
qleak_ptrs_hashmap->removePtr(current->address,NULL,NULL);
}
}
current = current->next;
}
}
}
引用
【腾讯开源】iOS爆内存问题解决方案-OOMDetector组件