Chapter 1- Getting Started

以下内容整理自:<ADVANCED APPLE DEBUGGING & REVERSE ENGINEERING> 如有出入望指正

由于OSX EI Capitan(10.11)开始引入了SIP(System Integrity Protection/系统完整性保护机制), 所以首先关闭SIP

  • 重启电脑
  • 开机按 Command + R,此时进入Recovery 模式
  • 点击Utilities(工具),选择 Terminal,并输入
   csrutil disable; reboot

关闭SIP, 要开启SIP,使用

   csrutil endable; reboot

Terminal 中输入 lldb,

    lldb -n Finder

输出类似:

Attaching LLDB to Xcode

使用 LLDB 调试 Xcode

打开一个新的终端,然后, ⌘ + Shift + I ,更改标签页标题为 LLDB

确保没有 Xcode 正在运行,以防运行多个混淆 使用 ⌘ + T 新开一个, ⌘ + Shift + I , 命名为Xcode stderr

~$ tty

获取到当前窗口为

/dev/ttys003

这个可能会不同 新开一个窗口,输入

echo "hello debugger" 1>/dev/ttys003

这时在 Xcode stderr 中可以看到 hello debugger 打印出来了,关闭最后打开的窗口

切换到 LLDB 窗口,输入 lldb

(lldb) file /Users/mylove/Downloads/Xcode-beta.app/Contents/MacOS/Xcode

我使用的是Xcode 9 beta 版,正式版可能是

(lldb) file /Applications/Xcode.app/Contents/MacOS/Xcode

也可以使用

ps -ef `pgrep -x Xcode` 查找

然后在LLDB 窗口输入

(lldb) process launch -e /dev/ttys003 --

这时你可以在 Xcode stderr 看到Xcode输出的log,Xcode 也会被打开

新建swift项目

切换到Xcode,打开ViewController.swift

Finding a class with a click

-[NSView hitTest:] 在点击view的时候会被触发,我们通过给此方法添加断点进行调试 切换到 LLDB 窗口,Ctrl + C 暂停 debugger

(lldb) breakpoint set -n "-[NSView hitTest:]"
Breakpoint 2: where = AppKit`-[NSView hitTest:], address = 0x00007fffcb22b92b

此时处于debugger状态,输入continue

(lldb) continue
Process 13166 resuming

点击Xcode,任意位置

使用 RDI 进行输出

(lldb) po $rdi
<_NSThemeCloseWidget: 0x128a1e9b0>

hitTest: 方法被触发,点击的位置不同,得到的结果或许不同

Filter breakpoints for important content

给断点添加过滤条件,只有属于 NSTextView 类才会被触发

(lldb) breakpoint modify 2 -c "(BOOL)[$rdi isKindOfClass:[NSTextView class]]"

其中,2是要修改的断点,可以通过以下方法查看

(lldb) breakpoint list
Current breakpoints:
2: name = '-[NSView hitTest:]', locations = 1, resolved = 1, hit count = 1
  	2.1: where = AppKit`-[NSView hitTest:], address = 0x00007fffcb22b92b, resolved, hit count = 1 

因为我之前删了一个断点,所以现在的是2 之后就是点击Xcode,如果是 NSTextView 或者是其子类,都会响应。书中说是点击 code area in Xcode (代码区域),断点会停下来,但是我点击后也没有停下来(Xcode 9 deta 6),我是点击target version 区域

(lldb) po $rdi
<NSTextView: 0x13d18d960>
Frame = {\{2.00, 3.00}, {266.00, 14.00}\}, Bounds = {\{0.00, 0.00}, {266.00, 14.00}\}
Horizontally resizable: YES, Vertically resizable: YES
MinSize = {266.00, 14.00}, MaxSize = {40000.00, 40000.00}

输出值 (lldb) po [$rdi string] 1.0

以上都是使用 Objective-C, 下面来确认下在 swift 中也可以使用,导入Foundation,AppKit

(lldb) ex -l swift -- import Foundation 
(lldb) ex -l swift -- import AppKit

ex 是 expression 的缩写,-l swift 告诉 LLDB 这是 swift 代码,通过替换之前的内存地址

(lldb) ex -l swift -o -- unsafeBitCast(0x13d18d960, to: NSObject.self)
(lldb) ex -l swift -o -- unsafeBitCast(0x13d18d960, to: NSObject.self) is NSTextView

得到的结果和文中的有出入,稍后我会研究一下

(lldb) ex -l swift -o -- unsafeBitCast(0x13d18d960, to: NSObject.self)
error: Couldn't lookup symbols:
__TTSfq4n_n_d_d_n___TFs18_fatalErrorMessageFTVs12StaticStringS_4fileS_4lineSu5flagsVs6UInt32_Os5Never
(lldb) ex -l swift -o -- unsafeBitCast(0x13d18d960, to: NSObject.self) is NSText
error: Couldn't lookup symbols:
__TTSfq4n_n_d_d_n___TFs18_fatalErrorMessageFTVs12StaticStringS_4fileS_4lineSu5flagsVs6UInt32_Os5Never

文中正确的结果,类似这种:

(lldb) ex -l swift -o -- unsafeBitCast(0x14bdd9b50, NSObject.self) <NSTextViewSubclass: 0x14b7a65c0>
	Frame = {\{0.00, 0.00}, {1089.00, 1729.00}}, Bounds = {\{0.00, 0.00}, {1089.00, 1729.00}}
	Horizontally resizable: NO, Vertically resizable: YES
	MinSize = {1089.00, 259.00}, MaxSize = {10000000.00, 10000000.00}
(lldb) ex -l swift -o -- unsafeBitCast(0x14bdd9b50, NSObject.self) is NSTextView
true

使用 Swift 需要很多必要的条件,所以LLDB默认使用的是 Objective-C

更改version值

po [$rdi setString:@"1.1"]
po [CATransaction flush]

观察Xcode 中version 值得变化,此时变为1.1

Hunting for private classes and methods in modules

(lldb) image lookup -rn 'NSTextView\ '

从正在运行的二进制和加载的动态库中查找 NSTextView 相关class

原文介绍

This command lets you introspect the running binary and all loaded dynamic libraries. The r option instructs it to use a regular expression search. The n option instructs it to search functions or symbols by name.
You’ll see a list of methods your NSTextView subclass implements!

Swizzling with block injection

通过 runtime 机制进行方法替换

(lldb) po @import Foundation

尽管Xcode中已经包含了这个库,但是 LLDB 并不知道识别不了,导入是为了在使用LLDB调试runtime 时使用

现在输入

(lldb) po
Enter expressions, then terminate with an empty line to evaluate:
1:  

输入以下代码:

@import Cocoa; 
id $class = [NSObject class]; 
SEL $sel = @selector(init); 
void *$method = (void *)class_getInstanceMethod($class, $sel); 
IMP $oldImp = (IMP)method_getImplementation($method);

输入完就 enter(回车)

在 LLDB 中,所有变量都是使用 $ 作为开头

(lldb) po $class
NSObject
(lldb) po $oldImp
(libobjc.A.dylib`-[NSObject init])

使用 imp_implementationWithBlock 新建一个 IMP,使用 po 输入

id (^$block)(id) = ^id(id object) { 
	if ((BOOL)[object isKindOfClass:[NSView class]]) {
    	fprintf(stderr, "%s\n", (char *)[[[object class] description] UTF8String]); 
    }
    return object;
}; 
IMP $newImp = (IMP)imp_implementationWithBlock($block);
method_setImplementation($method, $newImp);

此方法的目的是 swizzle -[NSObject init], -[NSObject init]除了返回它自己,什么也不做,新建的block检测object是否属于 NSView 类,是的话就输出出来

文中解释其如何工作:

1. You create a block that takes an object reference.

2. The block checks if the object passed in is of type NSView.

3. If so, it prints a description of the view to stderr, which will appear on your Xcode stderr Terminal tab.

4. It then returns object, to perform the equivalent implementation of -[NSObject init] that it’s swizzling. Ideally, swizzling these implementations would have been cleaner, and you could simply execute $oldImp with the right parameters. However, there is a bug in LLDB that will crash when executing IMPs inside of a block.

5. Finally, a new IMP is created from the block, and the method implementation is set to this new IMP. This has therefore swizzled -[NSObject init] with your new implementation.

使用 continue 开始debugging。 在Xcode上点击,在Xcode stderr窗口中将会看到符合要求的输出

第一节就先这样,有不足之处再进行补充。