XCode汇编调试

xcode,汇编,调试 · 浏览次数 : 184

小编点评

**内存地址的存储采用大端模式** 对象内存长度才有8字节的倍数,如果实际只需要4个字节,系统还是会分配8个字节,这样提升代码执行效率。 **内存对齐** 对象在内存中实际占用的大小是按8字节对齐,实际需要不足8字节的,也按最小8字节分配。 **runtime** 在objc库中,init方法是直接返回的self, 里面什么都没有做。在objc项目中NSObject.mm中,init方法的实现如下:- (id)init { return _objc_rootInit(self);}id_objc_rootInit(id obj){ // In practice, it will be hard to rely on this function. return obj;} **打印的obj,obj1,obj2有什么区别** 都一样int main(int argc, const char * argv[]) { id obj = [NSObject alloc]; id obj1 = [obj init]; id obj2 = [obj init]; NSLog(@\"%p - %p - %p\",obj,obj1,obj2); return 0;}

正文

汇编调试的意义
了解常用的汇编指令和知识,可以知道经过编译器优化后,最终的代码调用,有可能和源码并不相同,如:设置faster,smallest 代码会更短,最终的汇编执行指令与源码不一样。
可以研究代码在二进制层面的执行流程是否和源码的流程一致,从二进制层面研究方法调用的传参,内部调用,方法返回值。
如下:
0
可以从汇编指令看出,init方法里只是把参数寄存器中的值移动到返回寄存器中就结束了,说明方法内部是直接返回,没有其他逻辑处理。
 
开启Xcode汇编调试
选中Always Show Disassembly项。
XCode -> Debug -> Debug Workflow -> Always Show Disassembly
 
0
在计算机中,虽然数据是存储在内存中,但内存中数据的加减计算并不是在内存中直接进行的。
而是把内存中的数据赋值到寄存器中,然后CPU在寄存器中计算好后把结果再赋值到内存中的。
0
对内存中3做加1计算,并把几个4存储到蓝色内存块中
movq 红色存储空间, %rax addq $0x1, $rax movq %rax, 蓝色地址空间
汇编语言和机器语言是一一对应的。
汇编语言通过汇编器可以变成机器语言,机器语言通过反汇编又可以转成汇编语言。
高级语言通过编译器可以变成汇编语言,汇编语言无法通过反编译转成高级语言了。
 
汇编语言种类
8086汇编(16bit)
x86汇编(32bit)
x64汇编(64bit)
ARM汇编(嵌入式,移动设备)
对应iOS开发来说,iOS模拟器使用的AT&T汇编,iOS真机使用的ARM汇编。
 
OC和Swift调试的汇编指令是AT&T
它的数据移动操作顺序是从左往右,比如movq指令是将左边寄存器的值移动到右边的寄存器中。
数据的移动:
movq -0x18(%rbp) , %rax //表示将%rbp-0x18 这个内存地址中保存的值移到 %rax寄存器中
地址的移动:
leaq -0x18(%rbp) , %rax //表示将%rbp-0x18 这个地址移到 %rax寄存器中
call 内存地址 // 方法调用
jmp 内存地址 // if 跳转
addq $0x1, $rax // 赋值,把左边的值设置到右边的寄存器中
0
 
mov 与 movq相比多了一个q, 这个q是什么意思呢?
q代表保存数据要用多少个字节,q:64-bits, 8字节。
指令在内存中是顺序保存的,基本上每条指令占4字节。
 
call和jmp指令相似
call是方法调用,jmp是if判断。
call是和ret配合使用的 call 0x00是跳到这个函数地址,等函数执行完,走到ret后会回到call指令的下一句的。
jmp是顺序执行,jmp 0x00后一直顺序往下执行。
call和jmp跳一个动态的函数地址时,命令是call *的,如:call *%rax。
0
从寄存器的发展来看是由小到大,比如从x86的32字节,到x64的64字节,它们是怎么兼容的呢?
解决方法是共用一个64位寄存器的内存,按照所占内存大小,从低位往高位占据。比如32位寄存器只使用64位全部空间的一半,占据寄存器的低地址区。
%rip, %rdx, 以r开头的是64位8字节寄存器
%esi, %idx, 以e开头的是32位4字节寄存器
其他的
ax ,bx, cx 占2字节
ah, al 占1字节
 
lldb汇编调试指令
内存/寄存器读写
读取寄存器中的值
register read/格式
register read/x
修改寄存器中的值
register write 寄存器名称 数值
register write rax 0
读取内存中的值
x/数值-格式-字节大小 内存地址
x/3xw 0x000010
x/3xw 0x000010的具体解释如下:
x:表示执行读取内存的命令。
/3:表示输出三个数据,即连续的三个内存地址。
x:表示以十六进制的形式输出数据。
w:表示输出的数据类型是一个 word,即 32 位长度的数据类型。
最后的参数 0x000010 则是要读取的内存地址,可以根据实际情况进行替换。在执行这个命令之后,LLDB 会输出指定内存地址中的三个 32 位数据值,以十六进制格式显示。
修改内存中的值
memory write 内存地址 数值
memory write 0x000010 10
汇编断点调试
thread step-inst-over, nexti, ni //单步运行,把子函数当做一个整体,一步执行
thread step-inst, stepi, si //单步运行,遇到子函数进入子函数
内存地址计算
当汇编调用,程序走到断点的位置时,要验证movq $0xa, 0x459d(%rip)这块汇编执行的效果时
会手动计算内存地址 = %rip + 0x459d
注意,此时从寄存器%rip拿到的值是错的,因为CPU的指令寄存器是保存的下一条指令要执行的地址
而当前断点断住了,相当于在CPU准备执行这个命令时会有一次执行下调指令的计算没有做,如果直接读取,那么读到的是还是CPU上一次的更新执行当前指令地址的值,正确的执行上下文时,应该是下一条指令的地址值。
0
%rip: 指令寄存器
iOS传参优先使用寄存器传参,寄存器不够了用栈传参。
内存地址格式规律
0x4bdc(%rip),一般是全局变量,全局区(数据段)
-0x78(%rbp), 一般是局部变量,栈空间
0x10(%rax), 一般是堆空间
常用寄存器
rax, rbx, rcx, rdx, rdi, rsi, rbp, rsp
r8,r9,r10,r11,r12,r13,r14,r15
rax 常用于函数返回值
rcx, rdx, rdi, rsi, r8, r9常用于存放函数参数。
rbp, rsp用于栈操作
rip作为指令指针(存放的是下一条执行指令的地址, 一旦CPU读取一条指令,rip自动指向下一条指令)
 
使用lldb工具进行汇编分析
通过使用调试指令,打印对象的内存地址分布,结合字节对齐,可以看到一个对象的总内存中,不同属性在内存中的保存样式。
可以查看参数寄存器,返回值寄存器的值是否符合逻辑,通常第一个寄存器用于保存函数结束时的返回值。
 
内存地址的存储采用大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:
地址由小向大增加,数据从高位往低位放;这和我们的阅读习惯一致。
0
 
验证里面是否是有效的对象指针时,可以p打印这个指针,如果是一个有效的指针会打印这个指针的类型,如果不是则是一堆数据。
p/print 打印
po/expr 打印对象,执行命令
x 打印内存地址
#根据对象的8字节对齐和数据存储的大端模式,可以还原出内存中保存的指针地址。
p (NSString *)0x0104294080 
汇编调试打断点
在源码要关注的方法前打断点,然后再打符号断点,即可进入到对应汇编方法的断点了。
然后使用xcode tab上的单步,进入按钮进行调试。
 
通过源码分析验证底层实现
通过断点liballoc和objc库,可以找到苹果提供的api的底层实现原理,从而验证汇编执行流程,内存的分配原则。
alloc创建对象的流程
根据类中成员变量占据的总内存大小去系统分配内存。去申请内存前要进行内存对齐,比如:对象内存长度才有8字节的倍数,如果实际只需要4个字节,系统还是会分配8个字节,这样提升代码执行效率。
系统字节对齐
系统分配大小是16字节对齐,如果对象内存小于16字节,就等于16。
对象在内存中实际占用的大小是按8字节对齐,实际需要不足8字节的,也按最小8字节分配。
 
通过汇编分析回答问题
 
runtime是什么?
从看objc源码可以发现,它是使用C ,C++,汇编实现的一套API。为OC提供运行时的功能,
是向系统申请内存,管理内存,处理垃圾回收的集合。objc_sendmsg用汇编统一实现函数的调用。
平时对runtime的使用。
 
打印的obj,obj1,obj2有什么区别?
答案:都一样
int main(int argc, const char * argv[]) {
    id obj = [NSObject alloc];
    id obj1 = [obj init];
    id obj2 = [obj init];
    NSLog(@"%p - %p - %p",obj,obj1,obj2);
    return 0;
}

2023-03-23 21:50:08.818412+0800 SimpleDemo[84990:4014556] 0x101345550 - 0x101345550 - 0x101345550
原因:在objc库中,init方法是直接返回的self, 里面什么都没有做。
在objc项目中NSObject.mm中,init方法的实现如下:
- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}
像平时常有的创建方法id obj = [[NSObject alloc] init];的原因是一种设计模式,引导开发者在init方法里初始化自己的一些属性方法状态。
 
 
 
 
 
 
 
 
 

与XCode汇编调试相似的内容:

XCode汇编调试

汇编调试的意义 了解常用的汇编指令和知识,可以知道经过编译器优化后,最终的代码调用,有可能和源码并不相同,如:设置faster,smallest 代码会更短,最终的汇编执行指令与源码不一样。 可以研究代码在二进制层面的执行流程是否和源码的流程一致,从二进制层面研究方法调用的传参,内部调用,方法返回值

Xcode调试内存最新理解

前提: Xcode 16.0 beta 设置 Scheme设置中勾选Malloc Scribble、Malloc Stack Logging。 这么做是为了在Memory Graph、Profile中追溯数据在哪句代码生成。 此设置会导致App硬盘占用异常增多,调试完毕之后需要把选项关闭。 Allo

Xcode的Search Paths配置

在Xcode中的文件搜索路径配置有两个地方,一个是Project层的配置,一个是Target的配置。 Project-Build Settings-Search Paths Target-Build Settings-Search Paths 在Target中的配置选项中,可以通过配置$(inher

Xcode编译流程

Xcode的构建过程本质上是执行一系列构建任务。如:代码检测,编译代码,链接目标文件,拷贝资源(图片, plist, nib)文件,代码签名等。大部分任务是执行命令行工具,如(clang编译、 ld链接、 codesign签名, altool上传)。这些工具使用xcode项目的配置信息,根据特定的顺

Xcode常用环境变量与常见使用场景

在Xcode的工程配置中,与路径相关的都是使用环境变量,这样可以避免使用决定路径时项目移植性差的问题。 Xcode常用宏 __FILE__ 当前文件所在目录 __DATE__ 编译日期的字符串,格式为“mm dd yyyy”(例如:“Sep 16 2015”) __FUNCTION__ 当前函数名称

iOS使用Run Script提升开发效率

通过在Xcode Run Script添加shell脚本,然后通过脚本来帮助我们在编译阶段完成一下资源的copy,文件替换,修改等繁琐的事件。使Xcode在编译过程中自动完成耗时繁琐的操作提升开发效率。 添加脚本的过程很简单,添加+添加 添加完脚本后可以根据需要调整脚本的执行顺序,如:对应Bundl

Instruments中常用Template的使用

Instruments是苹果提供的Xcode套件,可用于分析iOS,MacOS程序的性能数据,进行性能提升。Instruments提供了很多类型的Template,用于特定场景的分析。这里选了3种常用的Template进行使用方法的讲解,对于其他Template的用法则用到时再了解吧,没必要一次把所

使用symbolicatecrash工具符号化Crash日志

对于打包上线的APP,或者打包测试的APP,出现了崩溃并不能方便的把手机链接到电脑使用XCode自动符号化,此时手动符号化就是重要的选项。 1.查找符号化工具symbolicatecrash find /Applications/Xcode.app -name symbolicatecrash -t

WWDC2023 Session系列:探索XCode15新特性

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/44a0e0fd567c4421bc94be83e84f6dce~tplv-k3u1fbpfcp-zoom-1.image) ## 一、版本说明 XCode 15 beta 发布于 2023

iOS测试包的安装方法

iOS测试包根据要安装的机器类型可以分为2种: .app模拟器测试包 .ipa真机测试包 .app模拟器测试包的安装方式 方式一:Xcode生成安装包 1.Xcode运行项目,生成app包 2.将APP包拖到模拟器中 方式二:IPA包下载得到安装包 1.将ipa包的后缀改成.zip, 然后解压 2.