100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > xcode反汇编调试iOS模拟器程序

xcode反汇编调试iOS模拟器程序

时间:2019-06-11 01:32:32

相关推荐

xcode反汇编调试iOS模拟器程序

分享一下我老师大神的人工智能教程。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!/jiangjunshow

/hursing/article/details/8697654

(前两节也许你会看得没劲,可直接先看第四节(四)自动断点应用之NSNotificationCenter/hursing/article/details/8752235)

iOS模拟器程序的实质就是Mac OS X程序,只不过它需要以模拟器为载体来运行显示。故它的反汇编代码指令都是x86 CPU的,不是真机上的arm指令。

研究模拟器程序的反汇编有两个目的,或叫做好处:

一是为了研究深入到iOS系统的类库,你可以较容易地发现私有API,以及看到系统的实现。

二是,很直接地,模拟器调试比真机快。而且,相信大多数人更懂x86指令,各类高校教材的汇编教程都不是arm指令的吧。

首先问题是如何看到反汇编代码:

操作:Xcode顶部菜单->Product->Debug Workflow->Show Disassembly When Debugging打钩

如果是在调试的过程中打钩,则调试窗口会立刻更新显示反汇编代码,如图:

以上主要是为了看到自己写的代码的反汇编情况,当调试进入不是自己写的代码(没有debug symbol)时,无论是否对这个选项打钩,都会显示成反汇编。

例如,在gdb/lldb调试中,break状态下输入

[plain]viewplain copy b-[UIViewaddSubview:]

再continue之后,如果有发生addSubview操作,不论是自己写的还是系统操作的,都会进入反汇编断点:

转载请注明出处:/hursing

这里当然不会从零开始讲汇编代码了,零基础的话可以看看 王爽 写的书《汇编语言》,请自己找度娘或谷哥要了。

这一节主要讲讲书上没有的东西。

在xcode中看到的汇编语法不是熟悉的intel格式,而是叫AT&T汇编。基本上只要懂intel,at&t会很快上手。两者的区别请参考这篇文章:

AT&T汇编/bigloomy/article/details/6581754

例如,在前一节的截图中,会看到movl这样的指令,比熟悉的mov多了个l。这个l表示本次操作是长字型(4字节)数据,操作数是32bit。

这里要注意,lldb和gdb看到的结果会有些差别,下面是对同一个C++函数的截图:

这个是lldb:

这个是gdb:

可以看到lldb会比gdb在AT&T的格式表示上更规范化,gdb只会在一些特定的地方才加l。

lldb以十进制表示常量;左侧内存地址省略了0;

gdb在尖括号内多显示了函数内的偏移。

其实最新版的gdb又会显示得不同,但是apple在强推lldb,不再升级gdb了……

如果实际的代码是在处理浮点数,则在反汇编中会看到xmm0,xmm1等的寄存器,如:

这些寄存器叫xmm寄存器,是为SSE指令服务的,所以也叫sse寄存器。操作他们的指令会有ss后缀。度娘给出了更多信息:/view/2679650.htm

sse寄存器是一组128bit的寄存器,其值有很多种可能性,可通过gdb查看,详情请查看下一节。

总结一下,主要明白这几个概念就行:

1. mov xxx, yyy 表示把xxx的值赋给yyy

2. call xxx 表示调用某函数

3. jmp xxx 表示直接跳转到某内存地址,一般是函数的入口地址

4. ret 表示要返回下一层堆栈了,如果函数有返回值,会存在eax寄存器里。

在Objective-C函数的入口处(第一行)加断点,可用esp指针来探查参数。

以esp为基址,往后的偏移分别是:

0:函数执行完毕后的返回地址(不是返回值的地址哦)

4:对象实例的指针,即self指针

8:selector,实际是一个char数组型的字符串,即char*

12:(如果有)第一个参数

…(前一个参数的基址+前一个参数所占的字节数):(如果有)第n个参数

由此,要调试这样一个函数

[cpp]viewplain copy -(void)para1:(id)arg1para2:(CGRect)p2para3:(CGPoint)p3para4:(id)p4{NSLog(@"para1:(id)arg1para2:(CGRect)p2para3:(CGPoint)p3para4:(id)p4");}-(void)viewDidLoad{[selfpara1:[UIApplicationsharedApplication]para2:CGRectMake(10,20,30,40)para3:CGPointMake(50,60)para4:self];

时,断点后可在gdb这样调试:

po *(id*)($esp+4) // 得到对象实例的描述

p (char*)*(SEL*)($esp+8) // 得到selector的名字

po *(id*)($esp+12) // 得到p1的description

p *(CGRect*)($esp+16) // 得到p2的各个成员值,输出结果为:“(CGRect) $1 = origin=(x=10, y=20) size=(width=30, height=40)”

p *(CGPoint*)($esp+32) // 因为一个CGRect结构体占16字节(4个float),所以是“+32”,即“+16+16”,得到p3的各个成员值,输出结果为:“(CGPoint) $4 = (x=50, y=60)”

po *(id*)($esp+40) // 因为一个CGPoint结构体占8字节(2个float),所以是“+40”,即“+32+8”、得到p4的description

[plain]viewplain copy (gdb)po*(id*)($esp+4)<ViewController:0x757f8c0>(gdb)p(char*)*(SEL*)($esp+8)$1=0xf37b"para1:para2:para3:para4:"(gdb)po*(id*)($esp+12)<UIApplication:0x9250000>(gdb)p*(CGRect*)($esp+16)$2={origin={x=10,y=20},size={width=30,height=40}}(gdb)p*(CGPoint*)($esp+32)$3={x=50,y=60}(gdb)po*(id*)($esp+40)<ViewController:0x757f8c0>

由于在第一行,push了ebp,会导致esp被修改,而第二行又立刻把esp的值赋给ebp,所以在经过函数的这些头部时,可以用ebp类似地访问,不过参数的偏移都需要+4。

下面是如何查看浮点型参数。

浮点型参数会通过SSE寄存器来传递,可以在gdb中这样查看:

[cpp]viewplain copy p$xmm0

(lldb不能用上面的命令,暂没去研究用什么替代)

例如调试上例中的

[cpp]viewplain copy CGRectMake(10,20,30,40)

编译器会把参数反序送入xmm寄存器组,即40传入xmm0,30传入xmm1……

在CGRectMake加断点,执行到下图中的位置时输入命令

命令结果如下:[plain]viewplain copy (gdb)p$xmm0$1={v4_float={0,0,0,40},v2_double={0,5.4811317061554153e-315},v16_int8={0<repeats12times>,66,32,0,0},v8_int16={0,0,0,0,0,0,16928,0},v4_int32={0,0,0,1109393408},v2_int64={0,1109393408},uint128=35467839930368} 因为SSE寄存器是128位的,gdb并不知道哪些bit是有意义的,所以列出了一些猜测的结果。

我们传入的是CGFloat,即float,故v4_float = {0, 0, 0, 40} 是有意义的。

当传入的是double型,则v2_double有意义。

知道怎么查看后,先看看有什么实际应用,拿NSNotificationCenter来做实践吧。

首先在某个容易进入的地方加断点,并停在那,例如main函数。在gdb或lldb输入命令,手工添加断点。如下图:

输入的命令如下:

[plain]viewplain copy b-[NSNotificationCenteraddObserver:selector:name:object:]b-[NSNotificationCenteraddObserverForName:object:queue:usingBlock:] 然后continue运行。很快,NSNotificationCenter的断点就会触发,是由系统的类调用的。因为是命令行添加的断点,所以不会被xcode记录,下次启动也不会再进入,此时我们需要在xcode里再加断点,如下图,在函数第一行的行号栏单击,标上蓝色断点标记。

然后,在断点上右键单击,选择“Edit Breakpoint...“

按照下图设置好:

点击”Done“后完成(另一个函数的照样做就行)。 最后,把Main函数上的断点去掉,然后重启程序。 这时就会看见命令行狂打印log了。

这log有什么用?呵呵,自己挖掘吧,至少你已经发现好多个non-public API了。

转载请注明出处:/hursing

反汇编调试objective-c,遇到最多的就是objc_msgSend这函数了,本节主要讲讲它的实现以及调试过程的一些技巧。

以UIWebView为例子,看看它在loadRequest时做了什么。

首先必须明白,原始代码中调用

[cpp]viewplain copy [uiWebViewInstanceloadRequest:request] 的实质是调用了[cpp]viewplain copy objc_msgSend(uiWebViewInstance,"loadRequest:"的selector,request)

编写一个用到UIWebView的demo,主要功能是打开一个网页。

在main函数加断点,触发后,在命令行输入:

[plain]viewplain copy b-[UIWebViewloadRequest:] 然后continue,直到触发断点,如下图:

用step over跑到第17行,或直接在那加个断点continue到那。旁边的注释已经显示,call的是存储了objc_msgSend这个函数的偏移地址的代码位置。

17行的前三行,可以看出其动作是往栈上传参数,这三个参数原本是放在edx,ecx,eax的,所以如果参数不多,也可以不用esp为基址来查参数。

在当前的命令行输入

[plain]viewplain copy registerread 来查看各寄存器的值。lldb会把当前值的意义也输出来。但gdb不会,gdb对应的命令是[plain]viewplain copy inforegisters

结果如下图:

从ecx的内容可以看出,要call的selector的名字同样是loadRequest:,后面会验证。

接着,step into,可以看到:

那里只有一个跳转命令,继续step into,跳到objc_msgSend真正的地址,看到下图所示的代码:

简单点说,这些代码做了这些事:

1. 先判断传来的对象是否nil,是的话,把返回值(esp+4)置0就返回了,不是就进行下一步。即使是void型的函数,也是被当做有返回值的,只是没用。

2. 查找这个method有没有被cache,有就jmpl到cache了的IMP地址;没有就以selector代表的字符串从这个类的method list里找出method来(第13到20行的循环),cache后再jmpl。如果找不到method就抛出异常然后程序崩溃了。

一个小技巧是,如果想省去点好多次step over来跳过查找的步骤,可以在两个jmpl *%eax的地方加断点,然后run到那里(图中的第31行和38行)。如果传来的对象不是nil,最后都会到这来。此时eax里保存的是函数的地址,即objective-c运行时库定义的IMP(指向函数的指针),jump过去就能到目标函数里了。

另外,在调试反汇编时,step over和step into不要用快捷键,要用调试工具栏上的按钮。不知道为什么按快捷键都等于run了……

接着,在jmpl处step into,就到真实的函数了:

证实了刚才ecx是传了selector名字。

在lldb中搜索

[plain]viewplain copy imagelookup-r-sobjc_msgSend 可以看到:[plain]viewplain copy 13symbolsmatchtheregularexpression'objc_msgSend'in/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator6.1.sdk/usr/lib/libobjc.A.dylib:Address:libobjc.A.dylib[0x0001708c](libobjc.A.dylib.__TEXT.__text+90252)Summary:libobjc.A.dylib`objc_msgSendAddress:libobjc.A.dylib[0x00017104](libobjc.A.dylib.__TEXT.__text+90372)Summary:libobjc.A.dylib`objc_msgSendSuperAddress:libobjc.A.dylib[0x0001716c](libobjc.A.dylib.__TEXT.__text+90476)Summary:libobjc.A.dylib`objc_msgSendSuper2Address:libobjc.A.dylib[0x00017468](libobjc.A.dylib.__TEXT.__text+91240)Summary:libobjc.A.dylib`objc_msgSendSuper2_debugAddress:libobjc.A.dylib[0x00017338](libobjc.A.dylib.__TEXT.__text+90936)Summary:libobjc.A.dylib`objc_msgSendSuper2_stretAddress:libobjc.A.dylib[0x00017478](libobjc.A.dylib.__TEXT.__text+91256)Summary:libobjc.A.dylib`objc_msgSendSuper2_stret_debugAddress:libobjc.A.dylib[0x000172c8](libobjc.A.dylib.__TEXT.__text+90824)Summary:libobjc.A.dylib`objc_msgSendSuper_stretAddress:libobjc.A.dylib[0x00017460](libobjc.A.dylib.__TEXT.__text+91232)Summary:libobjc.A.dylib`objc_msgSend_debugAddress:libobjc.A.dylib[0x000171dc](libobjc.A.dylib.__TEXT.__text+90588)Summary:libobjc.A.dylib`objc_msgSend_fpretAddress:libobjc.A.dylib[0x00017480](libobjc.A.dylib.__TEXT.__text+91264)Summary:libobjc.A.dylib`objc_msgSend_fpret_debugAddress:libobjc.A.dylib[0x00017488](libobjc.A.dylib.__TEXT.__text+91272)Summary:libobjc.A.dylib`objc_msgSend_noargAddress:libobjc.A.dylib[0x00017254](libobjc.A.dylib.__TEXT.__text+90708)Summary:libobjc.A.dylib`objc_msgSend_stretAddress:libobjc.A.dylib[0x00017470](libobjc.A.dylib.__TEXT.__text+91248)Summary:libobjc.A.dylib`objc_msgSend_stret_debug objc_msgSend会有好多个版本:

objc_msgSend是普通调用,返回值为void、id、指针、int等32bit能表示的数据的objective-c函数会用到它。

objc_msgSend_stret在返回值为结构体时用到,stret表示structure return

objc_msgSend_fpret在返回值为浮点数时用到,fpret表示floating point return

带Super的表示调用父类的函数。

如上节所说,在obj_msgSend的第一行加自动断点 p (char*)*(SEL*)($esp+8) 就会打印多到你不想看的selector名字。注意这个地方不能去po对象,因为po的实质是调用对象的description,这个会再调用obj_msgSend,无限递归就挂了。

转载请注明出处:/hursing

引用第二节的例子:

函数的入口处,通常都是把esp的值传给ebp保存,然后下面的操作以ebp为基准做偏移量引用。因为esp作为栈指针,push和pop都会自动修改其值,所以用ebp可以不受影响。

还有的常见情形是开头和结尾对应着

[cpp]viewplain copy subl$8,%espaddl$8,%esp 这里体现着所谓的“局部变量在栈上分配”原则,说明本函数需要用8字节作为局部变量的保存空间。同时因为ebp已指向未subl前的esp地址,所以往下的操作中,-x($ebp)这样的地方是在操作局部变量,其中x为非负整数。不过需要明确的是,未必x字节是被用完作为局部变量的,编译器出于字节对齐的考虑,如果只用了7字节,那么也会分配到8字节。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。