Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >KSCrash源码分析

KSCrash源码分析

原创
作者头像
用户2297838
修改于 2018-12-10 03:25:36
修改于 2018-12-10 03:25:36
5K2
举报
文章被收录于专栏:Adam笔记Adam笔记

0x01 安装过程

1.1 抛砖引玉

代码语言:txt
AI代码解释
复制
KSCrashInstallationStandard* installation = [KSCrashInstallationStandard sharedInstance];
installation.url = [NSURL URLWithString:@"http://put.your.url.here"];
[installation install];

以上代码是KSCrash的安装代码,[KSCrashInstallationStandard init]底层对自身的属性进行赋值,然后,进入[KSCrashInstallationStandard install],这里会调用[KSCrash init]方法来设置要初始化的Crash监控器,然后调用[KSCrash install],这里会根据前面的设置好要启动的监控器来启动哪些监控器。

代码语言:txt
AI代码解释
复制
- (void) install
{
    KSCrash* handler = [KSCrash sharedInstance];
    @synchronized(handler)
    {
        g_crashHandlerData = self.crashHandlerData;
        handler.onCrash = crashCallback;
        [handler install];
    }
}	

在安装监控器的前面,还设置了crash的回调(onCrash),最终,会调用void kscm_setActiveMonitors(KSCrashMonitorType monitorTypes)来启动监控器,monitorTypes就是我们一开始设定好的g_monitoring,在KSCrashMonitor里面,设定好了监控器数组g_monitors,通过数组里面的monitorType来跟我们设定好的g_monitors进行运算,运算出的结果来决定我们是否安装该monitorType对应的监控器,下面,分析各个监控器的安装过程。

1.2 Mach kernel exceptions

Mach内核异常,由枚举KSCrashMonitorTypeMachException表示,由KSCrashMonitor_MachException来实现相关方法,如果添加至全局枚举g_monitoring里面,则需要开启,通过通用Apistatic void setEnabled(bool isEnabled)最后调用installExceptionHandler

代码语言:txt
AI代码解释
复制
static bool installExceptionHandler()
{
    ...

//    备份异常端口
    KSLOG_DEBUG("Backing up original exception ports.");
    kr = task_get_exception_ports(thisTask,
                                  mask,
                                  g_previousExceptionPorts.masks,
                                  &g_previousExceptionPorts.count,
                                  g_previousExceptionPorts.ports,
                                  g_previousExceptionPorts.behaviors,
                                  g_previousExceptionPorts.flavors);
    
    if(kr != KERN_SUCCESS)
    {
        KSLOG_ERROR("task_get_exception_ports: %s", mach_error_string(kr));
        goto failed;
    }

    if(g_exceptionPort == MACH_PORT_NULL)
    {
//          分配新端口并赋予接收权限
        KSLOG_DEBUG("Allocating new port with receive rights.");
        kr = mach_port_allocate(thisTask,
                                MACH_PORT_RIGHT_RECEIVE,
                                &g_exceptionPort);
        if(kr != KERN_SUCCESS)
        {
            KSLOG_ERROR("mach_port_allocate: %s", mach_error_string(kr));
            goto failed;
        }
        
//          给端口添加发送权限
        KSLOG_DEBUG("Adding send rights to port.");
        kr = mach_port_insert_right(thisTask,
                                    g_exceptionPort,
                                    g_exceptionPort,
                                    MACH_MSG_TYPE_MAKE_SEND);
        if(kr != KERN_SUCCESS)
        {
            KSLOG_ERROR("mach_port_insert_right: %s", mach_error_string(kr));
            goto failed;
        }
    }
// 将端口设置为接受异常的端口
    KSLOG_DEBUG("Installing port as exception handler.");
    kr = task_set_exception_ports(thisTask,
                                  mask,
                                  g_exceptionPort,
                                  EXCEPTION_DEFAULT,
                                  THREAD_STATE_NONE);
    if(kr != KERN_SUCCESS)
    {
        KSLOG_ERROR("task_set_exception_ports: %s", mach_error_string(kr));
        goto failed;
    }

//     创建辅助异常线程
    KSLOG_DEBUG("Creating secondary exception thread (suspended).");
//    创建一个分离线程
    pthread_attr_init(&attr);
    attributes_created = true;
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    error = pthread_create(&g_secondaryPThread,
                           &attr,
                           &handleExceptions,
                           kThreadSecondary);
    if(error != 0)
    {
        KSLOG_ERROR("pthread_create_suspended_np: %s", strerror(error));
        goto failed;
    }
//     获取分离线程的线程id
    g_secondaryMachThread = pthread_mach_thread_np(g_secondaryPThread);
    ksmc_addReservedThread(g_secondaryMachThread);

//    创建主要异常线程。
    KSLOG_DEBUG("Creating primary exception thread.");
    error = pthread_create(&g_primaryPThread,
                           &attr,
                           &handleExceptions,
                           kThreadPrimary);
    if(error != 0)
    {
        KSLOG_ERROR("pthread_create: %s", strerror(error));
        goto failed;
    }
    pthread_attr_destroy(&attr);
    g_primaryMachThread = pthread_mach_thread_np(g_primaryPThread);
    ksmc_addReservedThread(g_primaryMachThread);

    KSLOG_DEBUG("Mach exception handler installed.");
    return true;

···
}		

然后,使用分离线程,执行以下方法

代码语言:txt
AI代码解释
复制
static void* handleExceptions(void* const userData)
{
    ...
    for(;;)
    {
        KSLOG_DEBUG("Waiting for mach exception");

        // 等待异常触发
        kern_return_t kr = mach_msg(&exceptionMessage.header,
                                    MACH_RCV_MSG,
                                    0,
                                    sizeof(exceptionMessage),
                                    g_exceptionPort,
                                    MACH_MSG_TIMEOUT_NONE,
                                    MACH_PORT_NULL);
        if(kr == KERN_SUCCESS)
        {
            break;
        }

        // Loop and try again on failure.
        KSLOG_ERROR("mach_msg: %s", mach_error_string(kr));
    }

   ...
}

1.3 Fatal signals

Fatal signals在代码中,以KSCrashMonitorTypeSignal代表是否开启,在KSCrashMonitor_Signal里面实现相关实现,通用通过通用APIstatic void setEnabled(bool isEnabled),最后调用的是installSignalHandler

1.4 C++ exceptions

在代码里面,以枚举KSCrashMonitorTypeCPPException代表C++异常,在类KSCrashMonitor_CPPException里面实现相关代码。在通用APIstatic void setEnabled(bool isEnabled)里面通过std::set_terminate(terminate_handler)设置回调函数。

1.5 Objective-C exceptions

在代码里面,以KSCrashMonitorTypeNSException表示枚举值,在KSCrashMonitor_NSException里面实现具体代码,在通用APIstatic void setEnabled(bool isEnabled)里面通过void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable)设置回调

1.6 Main thread deadlock

Main thread deadlock是监控主线程死锁异常,在代码里面以枚举KSCrashMonitorTypeMainThreadDeadlock表示,在类KSCrashMonitor_Deadlock里面实现相关代码,在static void setEnabled(bool isEnabled)里面通过调用KSCrashDeadlockMonitor的初始化来安装死锁监控。

1.7 Custom crashes

自定义Crash,在代码里面用KSCrashMonitorTypeUserReported表示,在类KSCrashMonitor_User作具体实现,这个可以自定义crash,跟上面不一样的是,自定义的crash没有标准的crash时机,需要自己定义,也就是,遇到某些特发情况,可以收集信息,然后塞进KSCrashMonitor_User这里面,做后续处理。

0x02 运行过程

2.1 捕获

2.1.1 Mach kernel exceptions

在Mach中,异常是通过内核的基础设施——消息传递机制处理的。异常由出错的线程或任务(通过msg_send())抛出,然后由一个处理程序(msg_recv())捕捉。处理程序可以处理异常,也可以清除异常(即将异常标记为完成并继续),还可以决定终止线程。


Mach异常处理模型和其他的异常处理模型不同,其他模型的异常处理程序运行在出错的线程的上下文中,而Mach的异常处理程序在不同的上下文运行异常处理程序,出错的线程向预先制定好的异常端口发送消息,然后等待应答。每一个任务都可以注册一个异常端口,这个异常端口会对同一个任务中的所有线程起效。此外,单个线程还可以通过thread_set_exception_prots注册自己的异常端口。


所以Mach kernel exceptions中,使用mach_task_self获取当前任务进程,因为Mach异常其实是一个消息转发的异常,所以需要消息接收权限,在初始化异常端口的时候就赋予了mach_port_allocate(thisTask,MACH_PORT_RIGHT_RECEIVE,&g_exceptionPort,然而后面还赋予改端口的MACH_MSG_TYPE_MAKE_SEND,这是后面的task_set_exception_ports要求这个权限,然后task_set_exception_ports将这个端口设置为目标任务的异常端口。


至此,已经捕获了异常,但是没有做任何处理,为此,需要使用mach_msg在异常端口创建一个活动监听者。异常处理可以有同一个程序的另外一个线程来完成。也可以有另外一个程序实现异常处理。launched注册的进程的异常端口就是这么做。所以在后面,分别起了两个分离线程g_secondaryMachThreadg_primaryPThread来等待异常触发。

代码语言:txt
AI代码解释
复制
for(;;)
    {
        KSLOG_DEBUG("Waiting for mach exception");
	// 消息循环,一直阻塞,直到收到一条消息,而且必须是一条异常消息。
    // 其他消息也不会到达异常端口。
        kern_return_t kr = mach_msg(&exceptionMessage.header,
                                    MACH_RCV_MSG,
                                    0,
                                    sizeof(exceptionMessage),
                                    g_exceptionPort,
                                    MACH_MSG_TIMEOUT_NONE,
                                    MACH_PORT_NULL);
        if(kr == KERN_SUCCESS)
        {
            break;
        }

        // Loop and try again on failure.
        KSLOG_ERROR("mach_msg: %s", mach_error_string(kr));
    }

Mach的等待异常的过程如下图:

37E95EFEF7A37779DA43AA40BC62D132.jpg
37E95EFEF7A37779DA43AA40BC62D132.jpg
2.1.2 Fatal signals

Mach已经通过异常机制提供了底层的陷阱处理,而BSD则在异常机制之上构建了信号处理机制。硬件产生的信号被Mach捕捉,然后转换为对应的UNIX信号。为了维护一个统一的机制,操作系统和用户尝试的信号首先被转换为Mach异常,然后再转换为信号(Signals),如下图所示:

7A6E63A9B9ED07F096D6EDB504F189C7.jpg
7A6E63A9B9ED07F096D6EDB504F189C7.jpg

可以看出,跟我们自定义的Mach异常捕获不一样的是在于捕获到Mach异常的处理上。


当BSD进程(用户态进程)被bsdinit_task()函数启动时,会设置一个名为ux_handle的Mach内核线程。而ux_handle,与上面的Mach异常捕捉基本类似,只是他处理的是讲Mach异常转换为信号。


硬件产生的信号始于处理器陷阱。处理器陷阱与平台有关。ux_exception负责将陷阱转换为信号。为了处理机器相关的情况,ux_exception会调用machine_exception首先尝试处理机器陷阱。如果这个函数无法转换信号,ux_exception则处理一般情况。


如果信号不是由硬件产生的,那么这个信号来源于两个API调用:kill或pthread_kill。这两个函数分别向进程发送信号。


综上,信号可以看做是对硬件异常跟软件异常的封装。 硬件软件的错误对应了相应的信号,在KSCrash中,对一下信号进行了注册回调。

代码语言:txt
AI代码解释
复制
static const int g_fatalSignals[] =
{
    SIGABRT,
    SIGBUS,
    SIGFPE,
    SIGILL,
    SIGPIPE,
    SIGSEGV,
    SIGSYS,
    SIGTRAP,
};
2.1.3 C++ exceptions

C++ exceptions使用系统封装好的函数std::set_terminate(CPPExceptionTerminate)来设置回调。

2.1.4 Objective-C exceptions

Objective-C exceptions使用了NSSetUncaughtExceptionHandler系统回调来调用。 需要注意的是,这里可能会出现覆盖注册的问题。

2.1.5 Main thread deadlock

首先,在子线程执行runMonitor方法,然后执行watchdogPulse方法,在watchdogPulse里面通过dispatch_async到主线程来复位标志位,来鉴别是否发生死锁。

2.2 记录

2.2.1 流程

采集:数据的采集,从Crash发生就开始,主要是采集系统信息,以及crash信息,crash的信息主要是包括堆栈地址,原因等。 流程:分别依次从reportbinary_imagesprocesssystemcrash这些字段的流程来记录,下面,详细说下符号还原。

符号采集

NSException中,直接获取callStackReturnAddresses地址堆栈。 在Mach异常中,堆栈来源是寄存器的地址,首先会获取当前pc寄存器的值地址,然后符号还原,然后再获取lr指针,然后进行符号还原,然后再获取当前的fp指针,符号还原,然后不断的重复递归fp指针,还原符号的操作,知道递归到当前地址为0或者前置帧为空 Signal异常中,堆栈获取跟Mach相同。

符号还原

符号还原的对象是地址,符号还原的核心代码在下面:首先,通过imageIndexContainingAddress方法来获取当前的传入地址所在的Image的index,怎么获取Image的index?

代码语言:txt
AI代码解释
复制
bool ksdl_dladdr(const uintptr_t address, Dl_info* const info)
{
    info->dli_fname = NULL;
    info->dli_fbase = NULL;
    info->dli_sname = NULL;
    info->dli_saddr = NULL;

    const uint32_t idx = imageIndexContainingAddress(address);
    if(idx == UINT_MAX)
    {
        return false;
    }
    const struct mach_header* header = _dyld_get_image_header(idx);
    const uintptr_t imageVMAddrSlide = (uintptr_t)_dyld_get_image_vmaddr_slide(idx);
    const uintptr_t addressWithSlide = address - imageVMAddrSlide;
    const uintptr_t segmentBase = segmentBaseOfImageIndex(idx) + imageVMAddrSlide;
    if(segmentBase == 0)
    {
        return false;
    }

    info->dli_fname = _dyld_get_image_name(idx);
    info->dli_fbase = (void*)header;

    // Find symbol tables and get whichever symbol is closest to the address.
    const STRUCT_NLIST* bestMatch = NULL;
    uintptr_t bestDistance = ULONG_MAX;
    uintptr_t cmdPtr = firstCmdAfterHeader(header);
    if(cmdPtr == 0)
    {
        return false;
    }
    for(uint32_t iCmd = 0; iCmd < header->ncmds; iCmd++)
    {
        const struct load_command* loadCmd = (struct load_command*)cmdPtr;
        if(loadCmd->cmd == LC_SYMTAB)
        {
            const struct symtab_command* symtabCmd = (struct symtab_command*)cmdPtr;
            const STRUCT_NLIST* symbolTable = (STRUCT_NLIST*)(segmentBase + symtabCmd->symoff);
            const uintptr_t stringTable = segmentBase + symtabCmd->stroff;

            for(uint32_t iSym = 0; iSym < symtabCmd->nsyms; iSym++)
            {
                // If n_value is 0, the symbol refers to an external object.
                if(symbolTable[iSym].n_value != 0)
                {
                    uintptr_t symbolBase = symbolTable[iSym].n_value;
                    uintptr_t currentDistance = addressWithSlide - symbolBase;
                    if((addressWithSlide >= symbolBase) &&
                       (currentDistance <= bestDistance))
                    {
                        bestMatch = symbolTable + iSym;
                        bestDistance = currentDistance;
                    }
                }
            }
            if(bestMatch != NULL)
            {
                info->dli_saddr = (void*)(bestMatch->n_value + imageVMAddrSlide);
                if(bestMatch->n_desc == 16)
                {
                    // This image has been stripped. The name is meaningless, and
                    // almost certainly resolves to "_mh_execute_header"
                    info->dli_sname = NULL;
                }
                else
                {
                    info->dli_sname = (char*)((intptr_t)stringTable + (intptr_t)bestMatch->n_un.n_strx);
                    if(*info->dli_sname == '_')
                    {
                        info->dli_sname++;
                    }
                }
                break;
            }
        }
        cmdPtr += loadCmd->cmdsize;
    }
    
    return true;
}

首先,通过传入地址减去地址偏移,得到地址在Image中的位置,暂且称之为处理过的地址,然后遍历当前加载的所有Image,然后在Image内部,在遍历所有的LC,比对地址是否在当前LC的的地址范围里面,具体:遍历每个Image,然后获取每个LC,然后比对当前处理过的地址是否大于VMAdress地址并且小于VM Adress 加上VM Size,如果符合条件,就是我们要找的Image。

1141930346A32F24CC2B214D89BC9812.jpg
1141930346A32F24CC2B214D89BC9812.jpg

回到ksdl_dladdr,获取到地址所在的Image,再获取segment base address of the specified image,同样,需要遍历当前Image的LC,然后获取到当前Image的LC_SEGMENT(__LINKEDIT),解析下__LINKEDIT__LINKEDIT动态库链接器需要使用的信息,包括重定位信息(例如符号表,字符串表),绑定信息,懒加载信息等,获取到LC_SEGMENT(__LINKEDIT)则返回VM_Address的值减去File Offset的值。然后用返回值加上实际的内存偏移。其实这里应该是求的当前符号表、字符串表的基地址,公式: 实际位置 = 当前__LINKEDIT在MachO的基地址 - 当前__LINKEDIT的文件偏移 + 实际的偏移量(这里已经包括了文件偏移)。

376D0C1799192A611F1B4A7DEAAFFF33.jpg
376D0C1799192A611F1B4A7DEAAFFF33.jpg

然后,同样利用遍历LC来获取当前的符号表信息,然后遍历符号表:然后用逐步靠近目标地址的方法,来获取最佳匹配的地址。

C8FFD20CF1AA7855E01AA08A9AA2F41A.jpg
C8FFD20CF1AA7855E01AA08A9AA2F41A.jpg
DEED97C7798DF3D3FF6837AA328CBF16.jpg
DEED97C7798DF3D3FF6837AA328CBF16.jpg

获取到了最加的符号地址匹配,然后求出对应的字符串,一下是单个符号的结构体,所以,我们利用n_strx 也就是String Table Index就可以获取到字符串了,至此,单个地址还原完成,其他的以此类推。

代码语言:txt
AI代码解释
复制
struct nlist_64 {
    union {
        uint32_t  n_strx; /* index into the string table */
    } n_un;
    uint8_t n_type;        /* type flag, see below */
    uint8_t n_sect;        /* section number or NO_SECT */
    uint16_t n_desc;       /* see <mach-o/stab.h> */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};

整个捕获流程,如下图所示

8ED95119ABB64DCDA6FA374F6AFF4AFB.jpg
8ED95119ABB64DCDA6FA374F6AFF4AFB.jpg

0x03 参考资料

《深入解析Mac OS X & iOS操作系统》

《iOS逆向应用与安全》

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
2 条评论
热度
最新
调试验证代码的时候发现,kscrash获取寄存器信息没对上。不知道遇到过这个么,还是有什么细节我没看到。麻烦指教下
调试验证代码的时候发现,kscrash获取寄存器信息没对上。不知道遇到过这个么,还是有什么细节我没看到。麻烦指教下
回复回复点赞举报
在用的过程中发现一个问题,捕捉到的crashed 线程,里面堆栈为空,但是其他非crashed线程堆栈拿到了,老哥遇到过么
在用的过程中发现一个问题,捕捉到的crashed 线程,里面堆栈为空,但是其他非crashed线程堆栈拿到了,老哥遇到过么
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
深入剖析 iOS 性能优化
在集合里数据量小的情况下时间复杂度对于性能的影响看起来微乎其微。但如果某个开发的功能是一个公共功能,无法预料调用者传入数据的量时,这个复杂度的优化显得非常重要了。
用户7451029
2020/06/16
1.7K0
深入剖析 iOS 性能优化
CVE-2020-9964:iOS中的信息泄露漏洞分析
2020年09月17日凌晨,苹果终于给所有用户推送了iOS14正式版,并同时发布了iOS 14.0的安全内容更新。阅读该公告后,你将会看到列表中的一个漏洞CVE-2020-9964,这是一个存在于IOSurfaceAccelerator中的安全漏洞。苹果将这个漏洞描述为:“本地用户将能够利用该漏洞读取内核内存数据,这是一个内存初始化问题。”那么在这篇文章中,我们将跟大家介绍有关该漏洞的详细信息。
FB客服
2020/10/27
7900
CVE-2020-9964:iOS中的信息泄露漏洞分析
iOS 卡顿监测方案总结
最近在写 APM 相关的东西,所以整理了一下 iOS 中卡顿监测的那些方案,不了解卡顿的原理的可以看这篇文章iOS 保持界面流畅的技巧[1],写的很好。
网罗开发
2021/11/02
2.3K0
Android 平台 Native 代码的崩溃捕获机制及实现
一、背景 在Android平台,native crash一直是crash里的大头。native crash具有上下文不全、出错信息模糊、难以捕捉等特点,比java crash更难修复。所以一个合格的异常捕获组件也要能达到以下目的: 支持在crash时进行更多扩展操作,如: 打印logcat和应用日志 上报crash次数 对不同的crash做不同的恢复措施 可以针对业务不断改进和适应 二、现有的方案 其实3个方案在Android平台的实现原理都是基本一致的,综合考虑,可以基于coffeecatch改进。
腾讯Bugly
2018/03/23
5.9K0
iOS你不知道的事--Crash分析
原文地址:https://www.jianshu.com/p/56f96167a6e9
iOSSir
2019/06/01
1.6K0
一份走心的runloop源码分析
对iOS开发者而言,runloop是一个老生常谈的话题,但凡是iOS开发者,在工作中必然直接或间接的接触过runloop。而对于面试者而言,runloop又几乎是必考点。在几年前,笔者写过一篇文章NSRunLoop,对runloop原理以及应用场景做了基本介绍。但是当时也是道听途说,简单的翻看了源码的do...while循环,并没有深入源码。所以,本文将从源码的角度剖析runloop的组成,强化自己对runloop的认识,验证我们脑海中一直以来似懂非懂的原理,真心希望这篇文章能够帮助到大家。
VV木公子
2020/05/20
9.3K0
一份走心的runloop源码分析
iOS 测试 | iOS 自动化性能采集
​今天小编跟大家分享一篇来自学院内部学员的技术分享,本文主要介绍了作者在进行 iOS 自动化性能采集的一些经验,希望对大家在进行 iOS 自动化测试时有一些启发。
霍格沃兹测试开发
2020/07/15
2.5K0
iOS 测试 | iOS 自动化性能采集
iOS底层 - 关于死锁,你了解多少?
我们从GCD函数和队列的内容中最后的经典案例中关于死锁的案例开始,从死锁的发生开始,看看其产生的本质原因是为什么。
CC老师
2021/08/25
5510
iOS-底层原理36:内存优化(一) 野指针探测
下面是Mach异常 与 UNIX信号 的转换关系代码,来自 xnu 中的 bsd/uxkern/ux_exception.c
conanma
2021/10/28
2.5K0
Android基础开发实践:如何分析Native Crash
Native Crash常常发生在带有Jni代码的APP中,或者系统的Native服务中。作为比较难分析的一类问题,Native Crash其实还是有较多的方法去定位。
天天P图攻城狮
2018/08/21
18.6K5
Android基础开发实践:如何分析Native Crash
Objective-C RunLoop 详解
本文转自ibireme的《深入理解RunLoop》 RunLoop 是 iOS 和 OSX 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的。 目录 RunLoop 的概念 RunLoop 与线程的关系 RunLoop 对外的接口 RunLoop 的 Mode RunLoop 的内部逻辑 RunLoop 的底层实现 苹果用 R
BY
2018/05/11
1.7K0
RunLoop总结:RunLoop基础知识
没有实际应用场景,很难理解一些抽象空洞的东西,所以前面几篇文章先介绍了RunLoop的几个使用场景。 另外AsyncDisplayKit中也有大量使用RunLoop的示例。 关于实际的使用RunLoop 的案例和使用场景就不总结了,今天总结一点RunLoop的基础知识和概念。
Haley_Wong
2018/08/22
8620
RunLoop总结:RunLoop基础知识
探秘 Mach-O 文件
之前负责项目的包体积优化学习了 Mach-O 文件的格式,那么 Mach-O 究竟是怎么样的文件,知道它的组成之后我们又能做点什么?本文会从 Mach-O 文件的介绍讲起,再看看认识它后的一些实际应用。
会写bug的程序员
2020/06/19
2.4K0
探秘 Mach-O 文件
Synchronized 源码分析
前面我们已经介绍和分析了管程,而 Synchronized 则是 JVM 层面中管程的一种实现,它通过对细节的屏蔽方便了开发人员的使用。
itliusir
2020/02/10
1.1K0
iOS 客户端动图优化实践
GIF 和 Animated WebP 是互联网上最主流的动图格式, 但是在 iOS 开发中, 原生的 UIImage 并不直接支持 GIF 以及 Animated WebP 的展示, 因此有了各种优秀的第三方开源方案, 例如 SDWebImage 以及 YYImage 等. 这篇文章将以 QQ 音乐 iOS 端优化动图的实践为基础, 来介绍不同方案的思路以及优劣, 并给出优化的方案. 1. 端内动图展示的问题以及优化结果 长期以来, 部分机型浏览 Q 音的图文流时很容易闪退, 端内其他业务也存在不少动图相
QQ音乐技术团队
2023/05/12
6.4K4
iOS 客户端动图优化实践
《iOS APP 性能检测》
| 导语 最近组里在做性能优化,既然要优化,就首先要有指标来描述性能水平,并且可以检测到这些指标,通过指标值的变化来看优化效果,于是笔者调研了iOS APP性能检测的一些方法,在此总结一下。 首先,要明确性能检测都需要关注哪些指标,笔者列举了以下几个主要的,后面会详细说: 启动时间 内存占用量,内存告警次数 CPU使用率 页面渲染时间,刷新帧率 网络请求时间,流量消耗 UI阻塞次数,不可操作时长,主线程阻塞超过400毫秒次数 耗电功率 对于静态页面来讲,页面的渲染时间就是从viewDidLoad第一行到vi
腾讯Bugly
2018/03/23
4.9K0
RunLoop 源码阅读
获取runloop的函数 // 获取主线程的runloop CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // no retain needed if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; }
用户2215591
2018/06/19
8620
Android Native Crash 收集
本文是『张涛的NDK之旅』,本来很早以前就有很多读者希望我能写一些关于MDK的文章,但是由于我本身对NDK不熟悉,所以找来了同事张涛的文章。欢迎大家关注他的博客——开源实验室(点击原文链接可以直接访问)
用户1907613
2018/09/30
2.4K0
Android Native Crash 收集
带你打造一套 APM 监控系统 之 OOM 问题
内存:由于硬盘读取速度较慢,如果 CPU 运行程序期间,所有的数据都直接从硬盘中读取,则非常影响效率。所以 CPU 会将程序运行所需要的数据从硬盘中读取到内存中。然后 CPU 与内存中的数据进行计算、交换。内存是易失性存储器(断电后,数据消失)。内存条区是计算机内部(在主板上)的一些存储器,用来保存 CPU 运算的中间数据和结果。内存是程序与 CPU 之间的桥梁。从硬盘读取出数据或者运行程序提供给 CPU。
用户2932962
2020/07/15
4.8K0
带你打造一套 APM 监控系统 之 OOM 问题
《Android 创建线程源码与OOM分析》
| 导语 企鹅FM近几个版本的外网Crash出现很多OutOfMemory(以下简称OOM)问题,Crash的堆栈都在Thread::start方法上。该文详细分析了发生原因。 ---- 有两种栈: 出现次数最多的一种,称之为 堆栈A。 java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Out of memory java.lang.Thread.nativeCreate(Native Method)
腾讯Bugly
2018/03/23
4.8K0
相关推荐
深入剖析 iOS 性能优化
更多 >
LV.0
广州腾讯科技有限公司高级工程师
交个朋友
加入腾讯云官网粉丝站
蹲全网底价单品 享第一手活动信息
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档