获取iOS任意线程调用堆栈(一)获取任意线程的调用栈地址列表

转载自:http://blog.csdn.net/jasonblog/article/details/49909163

如果要获取当前线程的调用栈,可以直接使用现有API:[NSThread callStackSymbols]

但是并没有相关API支持获取任意线程的调用栈,所以只能自己编码实现。

1. 基础结构

一个线程的调用栈是什么样的呢?

我的理解是应该包含当前线程的执行地址,并且从这个地址可以一级一级回溯到线程的入口地址,这样就反向构成了一条链:线程入口执行某个方法,然后逐级嵌套调用到当前现场。

Call_stack_layout_svg(图片来源于维基百科)

如图所示,每一级的方法调用,都对应了一张活动记录,也称为活动帧。也就是说,调用栈是由一张张帧结构组成的,可以称之为栈帧。

我们可以看到,一张栈帧结构中包含着Return Address,也就是当前活动记录执行结束后要返回的地址(展开)。

那么,在我们获取到栈帧后,就可以通过返回地址来进行回溯了。

2. 指令指针和基址指针

我们明确了两个目标:(1)当前执行的指令,(2)当前栈帧结构。

以x86为例,寄存器用途如下:

SP/ESP/RSP: Stack pointer for top address of the stack.
BP/EBP/RBP: Stack base pointer for holding the address of the current stack frame.
IP/EIP/RIP: Instruction pointer. Holds the program counter, the current instruction address.

可以看到,我们可以通过指令指针来获取当前指令地址,以及通过栈基址指针获取当前栈帧地址。

那么问题来了,我们怎么获取到相关寄存器呢?

3. 线程执行状态

考虑到一个线程被挂起时,后续继续执行需要恢复现场,所以在挂起时相关现场需要被保存起来,比如当前执行到哪条指令了。

那么就要有相关的结构体来为线程保存运行时的状态,经过一番查阅,得到如下信息:

The function thread_get_state returns the execution state (e.g. the machine registers) of target_thread as specified by flavor.

Function - Return the execution state for a thread.SYNOPSISkern_return_t   thread_get_state(thread_act_t                     target_thread,thread_state_flavor_t                   flavor,thread_state_t                       old_state,mach_msg_type_number_t         old_state_count);
/** THREAD_STATE_FLAVOR_LIST 0*  these are the supported flavors*/
#define x86_THREAD_STATE32      1
#define x86_FLOAT_STATE32       2
#define x86_EXCEPTION_STATE32       3
#define x86_THREAD_STATE64      4
#define x86_FLOAT_STATE64       5
#define x86_EXCEPTION_STATE64       6
#define x86_THREAD_STATE        7
#define x86_FLOAT_STATE         8
#define x86_EXCEPTION_STATE     9
#define x86_DEBUG_STATE32       10
#define x86_DEBUG_STATE64       11
#define x86_DEBUG_STATE         12
#define THREAD_STATE_NONE       13
/* 14 and 15 are used for the internal x86_SAVED_STATE flavours */
#define x86_AVX_STATE32         16
#define x86_AVX_STATE64         17
#define x86_AVX_STATE           18

所以我们可以通过这个API搭配相关参数来获得想要的寄存器信息:

bool jdy_fillThreadStateIntoMachineContext(thread_t thread, _STRUCT_MCONTEXT *machineContext) {mach_msg_type_number_t state_count = x86_THREAD_STATE64_COUNT;kern_return_t kr = thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&machineContext->__ss, &state_count);return (kr == KERN_SUCCESS);
}

这里引入了一个结构体叫_STRUCT_MCONTEXT

4. 不同平台的寄存器

_STRUCT_MCONTEXT在不同平台上的结构不同:

x86_64,如iPhone 6模拟器:

_STRUCT_MCONTEXT64
{_STRUCT_X86_EXCEPTION_STATE64   __es;_STRUCT_X86_THREAD_STATE64  __ss;_STRUCT_X86_FLOAT_STATE64   __fs;
};_STRUCT_X86_THREAD_STATE64
{__uint64_t  __rax;__uint64_t  __rbx;__uint64_t  __rcx;__uint64_t  __rdx;__uint64_t  __rdi;__uint64_t  __rsi;__uint64_t  __rbp;__uint64_t  __rsp;__uint64_t  __r8;__uint64_t  __r9;__uint64_t  __r10;__uint64_t  __r11;__uint64_t  __r12;__uint64_t  __r13;__uint64_t  __r14;__uint64_t  __r15;__uint64_t  __rip;__uint64_t  __rflags;__uint64_t  __cs;__uint64_t  __fs;__uint64_t  __gs;
};

x86_32,如iPhone 4s模拟器:

_STRUCT_MCONTEXT32
{_STRUCT_X86_EXCEPTION_STATE32   __es;_STRUCT_X86_THREAD_STATE32  __ss;_STRUCT_X86_FLOAT_STATE32   __fs;
};_STRUCT_X86_THREAD_STATE32
{unsigned int    __eax;unsigned int    __ebx;unsigned int    __ecx;unsigned int    __edx;unsigned int    __edi;unsigned int    __esi;unsigned int    __ebp;unsigned int    __esp;unsigned int    __ss;unsigned int    __eflags;unsigned int    __eip;unsigned int    __cs;unsigned int    __ds;unsigned int    __es;unsigned int    __fs;unsigned int    __gs;
};

ARM64,如iPhone 5s:

_STRUCT_MCONTEXT64
{_STRUCT_ARM_EXCEPTION_STATE64   __es;_STRUCT_ARM_THREAD_STATE64  __ss;_STRUCT_ARM_NEON_STATE64    __ns;
};_STRUCT_ARM_THREAD_STATE64
{__uint64_t    __x[29];  /* General purpose registers x0-x28 */__uint64_t    __fp;     /* Frame pointer x29 */__uint64_t    __lr;     /* Link register x30 */__uint64_t    __sp;     /* Stack pointer x31 */__uint64_t    __pc;     /* Program counter */__uint32_t    __cpsr;   /* Current program status register */__uint32_t    __pad;    /* Same size for 32-bit or 64-bit clients */
};

ARMv7/v6,如iPhone 4s:

_STRUCT_MCONTEXT32
{_STRUCT_ARM_EXCEPTION_STATE __es;_STRUCT_ARM_THREAD_STATE    __ss;_STRUCT_ARM_VFP_STATE       __fs;
};_STRUCT_ARM_THREAD_STATE
{__uint32_t  __r[13];    /* General purpose register r0-r12 */__uint32_t  __sp;       /* Stack pointer r13 */__uint32_t  __lr;       /* Link register r14 */__uint32_t  __pc;       /* Program counter r15 */__uint32_t  __cpsr;     /* Current program status register */
};

通过了解以上不同平台的寄存器结构,我们可以编写出比较通用的回溯功能。

5. 算法实现

/*** 关于栈帧的布局可以参考:* https://en.wikipedia.org/wiki/Call_stack* http://www.cs.cornell.edu/courses/cs412/2008sp/lectures/lec20.pdf* http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/*/
typedef struct JDYStackFrame {const struct JDYStackFrame* const previous;const uintptr_t returnAddress;
} JDYStackFrame;//int jdy_backtraceThread(thread_t thread, uintptr_t *backtraceBuffer, int limit) {if (limit <= 0) return 0;_STRUCT_MCONTEXT mcontext;if (!jdy_fillThreadStateIntoMachineContext(thread, &mcontext)) {return 0;}int i = 0;uintptr_t pc = jdy_programCounterOfMachineContext(&mcontext);backtraceBuffer[i++] = pc;if (i == limit) return i;uintptr_t lr = jdy_linkRegisterOfMachineContext(&mcontext);if (lr != 0) {/* 由于lr保存的也是返回地址,所以在lr有效时,应该会产生重复的地址项 */backtraceBuffer[i++] = lr;if (i == limit) return i;}JDYStackFrame frame = {0};uintptr_t fp = jdy_framePointerOfMachineContext(&mcontext);if (fp == 0 || jdy_copyMemory((void *)fp, &frame, sizeof(frame)) != KERN_SUCCESS) {return i;}while (i < limit) {backtraceBuffer[i++] = frame.returnAddress;if (frame.returnAddress == 0|| frame.previous == NULL|| jdy_copyMemory((void *)frame.previous, &frame, sizeof(frame)) != KERN_SUCCESS) {break;}}return i;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux---基础01

Linux是一种自由和开放源码的操作系统&#xff0c;存在着许多不同的Linux版本&#xff0c;但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中&#xff0c;比如手机、平板电脑、路由器、台式计算机 Linux的目录结构 bin (binaries)存放二进制可执行文件 boot 存放用…

获取iOS任意线程调用堆栈(二)符号化理论:Mach-o文件结构

我们知道Windows下的文件都是PE文件&#xff0c;同样在OS X和iOS中可执行文件是Mach-o格式的。 所以我们如果要进行逆向分析&#xff0c;首先要熟悉Mach-o文件结构。 Mach-o包含三个基本区域&#xff1a; 头部&#xff08;header structure&#xff09;。 加载命令&#xff08;…

linux---基础02

二、用户、组管理命令 Linux采用组来组织和管理用户 在Linux中每个用户有唯一的用户标识符UID&#xff0c;该UID是一个无符号整数。 同时每个用户也必须至少属于一个组&#xff0c;也有组标识符GID。 其中UID与GID独立编号。 1. groupadd命令 &#xff08;1&#xff09;grou…

获取iOS任意线程调用堆栈(三)符号化理论:从Mach-o结构分析类名方法名

下面来讲讲如何从Mach-o文件中分析出类名和方法名&#xff0c;也让我们了解下class-dump的原理。 Mach-o结构有两个节&#xff1a;__objc_classname 和 __objc_methname 其中就是类名和方法名。 其中__objc_classname的偏移为&#xff1a;ox7961 __objc_methname的偏移为0x6…

linux---基础03

三、 文件属性操作命令 1. chown命令 &#xff08;1&#xff09;chown示例一&#xff1a;改变文件的属主 [rootserver1 ~]# chown user1 hello.txt功能&#xff1a;将指定文件hello.txt的属主&#xff08;所有者&#xff09;改为user1。&#xff08;2&#xff09;chown示例…

获取iOS任意线程调用堆栈(四)符号化实战

转载自&#xff1a;http://blog.csdn.net/jasonblog/article/details/49909209 1. 相关API和数据结构 由于我们在上面回溯线程调用栈拿到的是一组地址&#xff0c;所以这里进行符号化的输入输出应该分别是地址和符号&#xff0c;接口设计类似如下&#xff1a; - (NSString *)s…

linux---基础04

六、网卡配置 分配临时IP地址&#xff1a; ifconfig eth0 up // 启用eth0网卡ifconfig eth0 192.168.1.104 netmask 255.255.255.0静态分配IP地址&#xff1a; vi /etc/sysconfig/network-scripts/ifcfg-eth0service network restart //重启 network服务(重新加载网卡配置)动态…

记录---基于BigDecimal的特殊的四舍五入

机试问题—实现基于BigDecimal的特殊的四舍五入。 这是一道外企的机试题&#xff0c;我把里面的某个难点部分&#xff0c;摘出来详细如下&#xff1a; //实现特殊的四舍五入&#xff0c;先普通四舍五入取到小数点后两位&#xff0c;然后最后一位向后进位&#xff0c;0和5不变&…

获取iOS任意线程调用堆栈(五)完整实现:BSBacktraceLogger

转载自&#xff1a;https://toutiao.io/posts/aveig6/preview BSBacktraceLogger 是一个轻量级的框架&#xff0c;可以获取任意线程的调用栈&#xff0c;开源在我的 GitHub&#xff0c;建议下载下来结合本文阅读。 我们知道 NSThread 有一个类方法 callstackSymbols 可以获取调…

Mac电脑如何彻底删除清除数据?CleanMyMac X软件更专业

虽然不用杀毒&#xff0c;但是日常的清理还是有必要的&#xff0c;特别是卸载一些软件会有残留&#xff0c;可以用命令mdfind来找&#xff0c;然后删&#xff0c;这里给新手用户推荐一款应用clean my mac x&#xff0c;定期清理一下&#xff0c;不用的时候关掉就可以。 CleanM…

git总是提示输入用户名密码的解决记录

git总是提示输入用户名密码的解决记录 git改过密码后&#xff0c;再用命令行pull代码&#xff08;举个例子&#xff09;&#xff0c;就会每次让你输入用户名密码&#xff0c;很麻烦&#xff0c;比如&#xff1a; git pull origin dev:dev 输入用户名密码&#xff0c;ok了 git…

Git的思想和基本工作原理

转载自&#xff1a;http://www.nowamagic.net/academy/detail/48160210# 在开始学习 Git 的时候&#xff0c;请不要尝试把各种概念和其他版本控制系统&#xff08;诸如 Subversion 和 Perforce 等&#xff09;相比拟&#xff0c;否则容易混淆每个操作的实际意义。Git 在保存和处…

java后端获取客户端(用户)真实ip,原理

java后端获取客户端真实ip&#xff0c;原理&#xff1a; 一般都是下面代码中的做法&#xff1a;但很多人只知道这样能拿到&#xff0c;稍微有改动就不知道怎么办了 看看网上的各种说法&#xff0c;接下来容我一一讲解&#xff0c;如有纰漏&#xff0c;敬请指正。 public sta…

kafka入门:简介、使用场景、设计原理、主要配置及集群搭建

本文转自&#xff1a;http://www.aboutyun.com/thread-9341-1-1.html一、入门1、简介Kafka is a distributed,partitioned,replicated commit logservice。它提供了类似于JMS的特性&#xff0c;但是在设计实现上完全不同&#xff0c;此外它并不是JMS规范的实现。kafka对消息保存…

git大小写解决

git大小写解决 git reset --hard head git的回退操作 git config --get core.ignorecase 查看git忽略大小写的状态&#xff0c;true就是忽略大小写&#xff0c;false就是不忽略大小写 git config core.ignorecase false 更改git为不忽略大小写 情景1&#xff1a;&#xff08;…

深入理解Hadoop集群和网络

云计算和Hadoop中网络是讨论得相对比较少的领域。本文原文由Dell企业技术专家Brad Hedlund撰写&#xff0c;他曾在思科工作多年&#xff0c;专长是数据中心、云网络等。文章素材基于作者自己的研究、实验和Cloudera的培训资料。 本文将着重于讨论Hadoop集群的体系结构和方法&am…

Method-Swizzling实战-实现iOS原生网络请求性能采集

一、方法交换实现步骤&#xff08;实例方法和类方法处理逻辑类似&#xff09; 1.检查原类&#xff08;要被替换方法的类&#xff09;的原实例方法是否存在&#xff0c;如果不存在&#xff0c;则不交换&#xff1b;2.检查新类&#xff08;最终被使用的方法所在的类&#xff09;的…

java8新特性(1)--- lambda表达式

java8新特性&#xff08;1&#xff09;— lambda表达式 函数式编程&#xff0c;简化开发 新增语法&#xff08;->&#xff09; package com.common.jdk8;// 试想&#xff0c;如果在jdk1.7中&#xff0c;我们要实现基于这个接口的加法运算&#xff0c;一般会这样 // 先创建…

iOS中WebKit框架应用与解析

一、引言 在iOS8之前&#xff0c;在应用中嵌入网页通常需要使用UIWebView这样一个类&#xff0c;这个类通过URL或者HTML文件来加载网页视图&#xff0c;功能十分有限&#xff0c;只能作为辅助嵌入原生应用程序中。虽然UIWebView也可以做原生与JavaScript交互的相关处理&#xf…

java8新特性(2)--- 方法引用

java8新特性&#xff08;2&#xff09;— 方法引用 新增语法双冒号&#xff08;::&#xff09; package com.common.jdk8;import java.util.Arrays; import java.util.List;//方法引用 public class Jdk8Test2 {public static void main(String[] args) {//构造器引用&#x…