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

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

1. 相关API和数据结构

由于我们在上面回溯线程调用栈拿到的是一组地址,所以这里进行符号化的输入输出应该分别是地址和符号,接口设计类似如下:

- (NSString *)symbolicateAddress:(uintptr_t)addr;

不过在实际操作中,我们需要依赖于dyld相关方法和数据结构:

/** Structure filled in by dladdr().*/
typedef struct dl_info {const char      *dli_fname;     /* Pathname of shared object */void            *dli_fbase;     /* Base address of shared object */const char      *dli_sname;     /* Name of nearest symbol */void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;_dyld_image_count() returns the current number of images mapped in by dyld. Note that using this count to iterate all images is not thread safe, because another thread may be adding or removing images dur-ing duringing the iteration.     
_dyld_get_image_header() returns a pointer to the mach header of the image indexed by image_index.  If image_index is out of range, NULL is returned.     
_dyld_get_image_vmaddr_slide() returns the virtural memory address slide amount of the image indexed by image_index. If image_index is out of range zero is returned.     
_dyld_get_image_name() returns the name of the image indexed by image_index. The C-string continues to be owned by dyld and should not deleted.  If image_index is out of range NULL is returned.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

又为了要判断此次解析是否成功,所以接口设计演变为:

bool jdy_symbolicateAddress(const uintptr_t addr, Dl_info *info)

Dl_info用来填充解析的结果。

2. 算法思路

对一个地址进行符号化解析说起来也是比较直接的,就是找到地址所属的内存镜像,然后定位该镜像中的符号表,最后从符号表中匹配目标地址的符号。

mach_o_segments

(图片来源于苹果官方文档)

以下思路是描述一个大致的方向,并没有涵盖具体的细节,比如基于ASLR的偏移量:

// 基于ASLR的偏移量https://en.wikipedia.org/wiki/Address_space_layout_randomization
/*** When the dynamic linker loads an image, * the image must be mapped into the virtual address space of the process at an unoccupied address.* The dynamic linker accomplishes this by adding a value "the virtual memory slide amount" to the base address of the image.
*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

2.1 寻找包含地址的目标镜像

通过遍历每个段,判断目标地址是否落在该段包含的范围内:

/** The segment load command indicates that a part of this file is to be* mapped into the task's address space.  The size of this segment in memory,* vmsize, maybe equal to or larger than the amount to map from this file,* filesize.  The file is mapped starting at fileoff to the beginning of* the segment in memory, vmaddr.  The rest of the memory of the segment,* if any, is allocated zero fill on demand.  The segment's maximum virtual* memory protection and initial virtual memory protection are specified* by the maxprot and initprot fields.  If the segment has sections then the* section structures directly follow the segment command and their size is* reflected in cmdsize.*/
struct segment_command { /* for 32-bit architectures */uint32_t    cmd;        /* LC_SEGMENT */uint32_t    cmdsize;    /* includes sizeof section structs */char        segname[16];    /* segment name */uint32_t    vmaddr;     /* memory address of this segment */uint32_t    vmsize;     /* memory size of this segment */uint32_t    fileoff;    /* file offset of this segment */uint32_t    filesize;   /* amount to map from the file */vm_prot_t   maxprot;    /* maximum VM protection */vm_prot_t   initprot;   /* initial VM protection */uint32_t    nsects;     /* number of sections in segment */uint32_t    flags;      /* flags */
};/*** @brief 判断某个segment_command是否包含addr这个地址,基于segment的虚拟地址和段大小来判断*/
bool jdy_segmentContainsAddress(const struct load_command *cmdPtr, const uintptr_t addr) {if (cmdPtr->cmd == LC_SEGMENT) {struct segment_command *segPtr = (struct segment_command *)cmdPtr;if (addr >= segPtr->vmaddr && addr < (segPtr->vmaddr + segPtr->vmsize)) {
            return true;}    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

这样一来,我们就可以找到包含目标地址的镜像文件了。

2.2 定位目标镜像的符号表

由于符号的收集和符号表的创建贯穿着编译和链接阶段,这里就不展开了,而是只要确定除了代码段_TEXT和数据段DATA外,还有个_LINKEDIT段包含符号表:

The __LINKEDIT segment contains raw data used by the dynamic linker, such as symbol, string, and relocation table entries.

所以现在我们需要先定位到__LINKEDIT段,同样摘自苹果官方文档:

Segments and sections are normally accessed by name. Segments, by convention, are named using all uppercase letters preceded by two underscores (for example, _TEXT); sections should be named using all lowercase letters preceded by two underscores (for example, _text). This naming convention is standard, although not required for the tools to operate correctly.

我们通过遍历每个段,比较段名称是否和__LINKEDIT相同:

usr/include/mach-o/loader.h
#define SEG_LINKEDIT    "__LINKEDIT"
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

接着来找符号表:

/*** 摘自《The Mac Hacker's Handbook》:* The LC_SYMTAB load command describes where to find the string and symbol tables within the __LINKEDIT segment.  * The offsets given are file offsets, so you subtract the file offset of the __LINKEDIT segment to obtain the virtual memory offset of the string and symbol tables.  * Adding the virtual memory offset to the virtual-memory address where the __LINKEDIT segment is loaded will give you the in-memory location of the string and sym- bol tables.*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

也就是说,我们需要结合__LINKEDIT segment_command(见上面结构描述)和LC_SYMTAB load_command(见下面结构描述)来定位符号表:

/** The symtab_command contains the offsets and sizes of the link-edit 4.3BSD* "stab" style symbol table information as described in the header files* <nlist.h> and <stab.h>.*/
struct symtab_command {uint32_t    cmd;        /* LC_SYMTAB */uint32_t    cmdsize;    /* sizeof(struct symtab_command) */uint32_t    symoff;     /* symbol table offset */uint32_t    nsyms;      /* number of symbol table entries */uint32_t    stroff;     /* string table offset */uint32_t    strsize;    /* string table size in bytes */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

如上述引用描述,LC_SYMTAB和_LINKEDIT中的偏移量都是文件偏移量,所以要获得内存中符号表和字符串表的地址,我们先将LC_SYMTAB的symoff和stroff分别减去LINKEDIT的fileoff得到虚拟地址偏移量,然后再加上_LINKEDIT的vmoffset得到虚拟地址。当然,要得到最终的实际内存地址,还需要加上基于ASLR的偏移量。

2.3 在符号表中寻找和目标地址最匹配的符号

终于找到符号表了,写到这里有点小累,直接贴下代码:

/*** @brief 在指定的符号表中为地址匹配最合适的符号,这里的地址需要减去vmaddr_slide*/
const JDY_SymbolTableEntry *jdy_findBestMatchSymbolForAddress(uintptr_t addr,JDY_SymbolTableEntry *symbolTable,uint32_t nsyms) {// 1. addr >= symbol.value; 因为addr是某个函数中的一条指令地址,它应该大于等于这个函数的入口地址,也就是对应符号的值;// 2. symbol.value is nearest to addr; 离指令地址addr更近的函数入口地址,才是更准确的匹配项;const JDY_SymbolTableEntry *nearestSymbol = NULL;uintptr_t currentDistance = UINT32_MAX;for (uint32_t symIndex = 0; symIndex < nsyms; symIndex++) {uintptr_t symbolValue = symbolTable[symIndex].n_value;if (symbolValue > 0) {uintptr_t symbolDistance = addr - symbolValue;if (symbolValue <= addr && symbolDistance <= currentDistance) {currentDistance = symbolDistance;nearestSymbol = symbolTable + symIndex;}}}return nearestSymbol;
}/** This is the symbol table entry structure for 64-bit architectures.*/
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) */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

找到匹配的nlist结构后,我们可以通过.n_un.n_strx来定位字符串表中相应的符号名。

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

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

相关文章

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…

让WKWebView支持NSURLProtocol

NSURLProtocol能够拦截UIWebView内所有的请求&#xff0c;但是WKWebView 中的请求却完全不遵从这一规则&#xff0c;只是象征性调用canInitWithRequest方法&#xff0c;之后的整个请求流程似乎就与 NSURLProtocol 完全无关了。使我一度认为WKWebView请求不遵守NSURLProtocol协议…

java8新特性(3)--- 函数式接口

java8新特性&#xff08;3&#xff09;— 函数式接口 有且仅有一个抽象方法 package com.common.jdk8;import java.util.Arrays; import java.util.List; import java.util.function.Predicate;// 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法&#xff0c;…

Git仓库迁移,包括所有的分支、标签、日志

仅三行命令即可完成&#xff1a; git clone --bare http://域名/分组/仓库名称.git cd 仓库名称.git git push --mirror http://新域名/新分组/新仓库名称.git

java8新特性(4)— Stream流

java8新特性&#xff08;4&#xff09;— Stream流 遍历集合更强大 package com.common.jdk8;import java.util.*; import java.util.stream.Collectors;//Java 8 API添加了一个新的抽象称为流Stream&#xff0c;可以让你以一种声明的方式处理数据。 //Stream 使用一种类似用…

二、工作量证明链-区块链技术的雏形

用算法解决难题——区块链技术的雏形 构造出一个完美的、可以解决问题的“拜占庭容错系统”是一个不小的挑战。而且构造出来以后&#xff0c;其是否真的有效&#xff0c;能否经得起时间的考验与各方的质疑&#xff0c;这些都关乎着这个系统未来的命运与其创造群体的声誉。 2008…

CocoaPods通过网络代理执行资源更新

一、使用网络代理 首先在电脑设置网络代理配置。 二、设置git的http、https代理 1.查看git是否已经设置网络代理 首先可以先查本地的git配置有没有配置http/https代理&#xff1a;git config --global -e 如果没有看到 [http]proxy和 [https]proxy就代表没有设置http/http…