Tutorial: iOS Reverse Engineering Part I: LLDB
2016-05-16 00:00
876 查看
Tutorial: iOS Reverse Engineering Part I: LLDB
Thursday, 02 July 2015 21:26 Written by Jonathan SaggauHello World!
Little Jonathan’s First Reverse Engineering
Before I was knee high to a grasshopper as a kid in Minnesota my dad, a mechanical engineer by education, did a bad, bad thing he would occasionally regret. He taught me how to take things apart…When I was a few years old, I approached my grandfather with an odd request. “Grampa, can I borrow a screwdriver? I need one of the flat ones.” After a series of carry-this-with-the-point-facing-downward and please-don’t-run
warnings, he fished the requested tool from a kitchen drawer. Some time later, much to his surprise and chagrin, I proudly reappeared with two door knobs and a latch mechanism in hand. I had noticed the knobs would operate the latch when turned in one direction,
but when turned in the other direction nothing would happen. By methodically disassembling the door latch I had learned that the rectangular shaft the knob turns inside of the door was made of metal and accepted by an asymmetrically worn plastic part within
the latch mechanism. I found the reason that turning the knob worked in only one direction. My sharing this discovery amused my parents. My bemused grandfather asked me to please put his front door back together.
I am a software developer now, so I’m essentially an overgrown version of that methodically tinkering kid. At Enharmonic we spend a lot of time writing complex iOS applications. During the course of this work it
is not uncommon for us to push beyond documentation to ascertain how iOS actually behaves internally.
Scenario
Imagine that you’ve been asked to create a view controller for displaying a PDF using a UIWebView.
This view controller should monitor the scroll position of the PDF to display auxiliary UI when the user scrolls to a particular point inside.
It seems pretty straightforward to implement this behavior: subclass UIWebView, load the PDF, override the UIScrollViewDelegate methods to monitor the scroll view’s scroll position, update your auxiliary UI as needed.
Done!
Unfortunately, the documentation disallows subclassing
UIWebView.
Subclassing Notes
The UIWebView class should not be subclassed.
UIWebViewembeds web or document
content in a scrolling UI. You can load remote or local HTML, MS Office, iWork, RTF/RTFD, or PDF files. It acts in a way that is similar to the central scrolling view of the Safari web browser.
UIWebViewexposes
its
UIScrollView,
the scrolling view that hosts the web view’s content, as a readonly property as of iOS 5. The documentation for what you can do with the webview hosted scrollview is terse: “Your
application can access the scroll view if it wants to customize the scrolling behavior of the web view.”
UIWebViewimplements the
UIScrollViewDelegateprotocol.
A scroll view’s delegate can respond to scrolling events when the user interacts with the scroll view and can customize zooming behavior by providing a view within the scroll view for the system to scale up and down as the user pinches.
In the scenario described above you would need to find a way to insert some code into the
UIScrollViewDelegatemethods
of the
UIWebViewwithout getting in the way of Apple’s implementation of the
UIWebViewto
override the delegate methods. You are not allowed to accomplish this by subclassing
UIWebView.
Since the web view is its scroll view’s delegate, it may not respond well to some other object becoming its scroll view’s delegate. A
UIScrollViewcan
have only one
delegateand the web view definitely needs to know about scrolling,
but so does your view controller. They can’t both be the
delegate. The documentation
does not address whether being allowed to “…access the scroll view if it wants to customize the scrolling behavior of the web view” extends to becoming that scroll view’s
delegateinstead
of its web view nor does it mention how to do so without causing problems for the web view.
How can you implement the behavior you need? Would setting the scroll view’s
delegateto
an object other than its web view lead to problems in the web view itself? Grab a screwdriver. You are going to tear this doorknob apart to reverse engineer your way to answers that the documentation does not directly provide.
Test Harness
We have created a sample project called WebTest2 for testing UIWebView. Download
the project here. Open it in Xcode and build and run the app on a device or on the simulator. It should look like the image below.
Open
ViewController.m. Notice
that it has an IBOutlet property connected to a
UIWebViewloaded from the app’s
main storyboard file. The view controller loads a PDF containing a Beethoven piano sonata during the
viewDidLoadmethod
shown below.
- (void)viewDidLoad { [super viewDidLoad]; NSAssert(self.webView != nil, @"Check IB -- Wire-up the web view."); NSString *path = [[NSBundle mainBundle] pathForResource:@"IMSLP00008-Beethoven__L.v._-_Piano_Sonata_08" ofType:@"pdf"]; NSData *data = [NSData dataWithContentsOfFile:path]; [self.webView loadData:data MIMEType:@"application/pdf" textEncodingName:@"utf-8" baseURL:[NSURL URLWithString:@""]]; /* [self.webView.scrollView setDelegate:self]; */ }
Note: You may also notice some commented-out code in
ViewController.mand
that we’ve implemented a subclass of
UIWebViewwith some commented-out
UIScrollViewDelegatemethods
inside. You will need this code later on in this tutorial.
LLDB
You have probably used the LLDB debugger in Xcode to investigate bugs in your iOS or OS X code. Xcode’sUI allows you to add breakpoints, to step through your C, C++, Objective-C, or Swift code, and to investigate the values of local variables as your project as it is running on a device or on the simulator. In addition to the features exposed by Xcode’s UI,
LLDB contains a powerful set of debugging features through a command line interface.
Investigating the scroll view’s delegate callbacks
Open ViewController.mand add
a breakpoint at the top of the
viewDidLoadmethod by clicking in the trough
to the left of the code on line number 20 (
ViewController.m:20).
Build and run the app. The debugger should stop at the breakpoint. If the
(lldb)prompt
is not visible on the screen use the (⇧⌘Y) keyboard shortcut to toggle the debug area, and then use the (⇧⌘C) keyboard shortcut to place the cursor in the console.
To see which object is the delegate of the web view’s scroll view type
po self.webView.scrollView.delegate(
pois short for “print object”) command
at the
(lldb)prompt.
(lldb) po self.webView.scrollView.delegate nil
That’s curious. The web view’s scroll view does not report having a
delegateat
all. How does a
UIWebViewhandle scroll and zoom events if it is not the
delegateof
its scroll view?
In
ViewController.mtry setting
the
webview.scrollview.delegateto the view controller itself. Uncomment the
existing code shown below.
/* [self.webView.scrollView setDelegate:self]; */
You need not implement any delegate methods, yet. All of the methods in
UIScrollViewDelegateare
marked optional.
Disable all breakpoints by clicking the breakpoint button in the debug area toolbar. Now build and run with breakpoints disabled. Scroll up and down in the pdf just to see if becoming the scroll view’s delegate
without implementing any delegate methods causes any obvious trouble.
You will notice that nothing changes behaviorally. The web view continues to work as before. Scrolling works as always and the page number view at the top of the web view updates normally. Somehow, even though your
view controller is now the delegate of its scroll view, the web view continues to act as though it receives the delegate callbacks, even when another object becomes the delegate of its scroll view.
When you want content within a scroll view to zoom in and out when the user pinches on the screen, you implement
viewForZoomingInScrollView:in
the scroll view’s delegate and return the UIView you want the system to scale. You can return
nilif
you do not want the scroll view to zoom at all. Since the web view as it is configured in the test project zooms in and out, it seems likely that the web view returns a view when this delegate method is called. What happens when you override this method in
the view controller and return
nil? Since the view controller is now the delegate
of the scroll view, does this signal to the scroll view that it should not zoom or does the web view maintain control? Might it cause the app to crash or introduce some other nasty behavior? Will the scroll view even call this delegate method on the view controller?
Implement
viewForZoomingInScrollView:in ViewController.m to
return
nil. Do not uncomment the more complicated code in this file just yet.
Copy and paste the following code instead.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; { NSLog(@"%@ %@ %@", self, NSStringFromSelector(_cmd), scrollView); return nil; }
This code will log
self, the method
name, and the scroll view to the console and return
nil.
Build and run.
Notice in the console that
viewForZoomingInScrollView:on
the View Controller is getting called many times as the WebView is loaded and initially displayed.
Try pinching to zoom while watching the console. If running in the simulator you can hold down the option key to get two touch points, then click and drag to simulate a pinch.
Notice that the web view no longer zooms when you pinch and that
viewForZoomingInScrollView:is
called on the view controller when you attempt to zoom. Now try scrolling. Notice that the page number view and all other behavior remains unchanged. Returning
nilfrom
our view controller and setting our view controller as the scroll view’s delegate seems to short-circuit the zooming of the web view, but other behavior remains unchanged. The scroll view seems to treat the view controller as its delegate in this case.
Leave the app running and add a breakpoint in the view controller’s
viewForZoomingInScrollView:implementation.
Try to zoom. The debugger should break.
To look at the description of the web view’s scroll view, type
po scrollviewinto the
(lldb)prompt and hit the enter key.
(lldb) po scrollView ; layer = ; contentOffset: {0, 0}; contentSize: {320, 8401.9512195121952}>
Notice that the scroll view is a subclass of
UIScrollView—
_UIWebViewScrollView.
It looks like Apple has implemented a custom (private, as denoted by the underscore)
UIScrollViewsubclass
for the
UIWebView. This may be a clue.
Now take a look at the backtrace by typing
btor
backtraceinto
LLDB.
(lldb) bt * thread #1: tid = 0x25f377, 0x0000000101f0059d WebTest2`-[ViewController viewForZoomingInScrollView:](self=0x00007fa4b87877f0, _cmd=0x000000010336f950, scrollView=0x00007fa4b87922b0) + 125 at ViewController.m:34, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 * frame #0: 0x0000000101f0059d WebTest2`-[ViewController viewForZoomingInScrollView:](self=0x00007fa4b87877f0, _cmd=0x000000010336f950, scrollView=0x00007fa4b87922b0) + 125 at ViewController.m:34 frame #1: 0x000000010268fdec CoreFoundation`__invoking___ + 140 frame #2: 0x000000010268fc42 CoreFoundation`-[NSInvocation invoke] + 290 frame #3: 0x0000000102720016 CoreFoundation`-[NSInvocation invokeWithTarget:] + 54 frame #4: 0x0000000102e038a9 UIKit`-[_UIWebViewScrollViewDelegateForwarder forwardInvocation:] + 172 frame #5: 0x00000001026f6f4f CoreFoundation`___forwarding___ + 495 frame #6: 0x00000001026f6cd8 CoreFoundation`__forwarding_prep_0___ + 120 frame #7: 0x0000000102c24abd UIKit`-[UIScrollView _getDelegateZoomView] + 90 frame #8: 0x0000000102c28937 UIKit`-[UIScrollView _zoomScaleFromPresentationLayer:] + 24 frame #9: 0x000000010311f677 UIKit`-[UIWebPDFView _viewCachingBoundsInUIViewCoords] + 117 ...
Note the call to
-[_UIWebViewScrollViewDelegateForwarder forwardInvocation:]in the backtrace. This may be a clue.
Hypothesis
Hypothesis: _UIWebViewScrollViewDelegateForwardersomehow
ensures that the web view continues to work when another object becomes the
_UIWebViewScrollView‘s
delegate, performing some kind of magic to decide which of the delegates recieves call backs.
Subclassing UIWebView as an Investigative Tool.
Now you will replace the current, default UIWebViewwith
a subclass called
TESTWebViewincluded in the project you downloaded. First,
revert the code in
ViewController.mto its original state. The easiest way
to do this is to navigate in Terminal.app to the directory containing the project and typing
git reset --hard. The code you download has a basic git setup for this purpose. You may want to selectively checkout the view controller implementation file or
git stashyour changes first if you’ve done other experimentation in the project or changed provisioning and code signing settings as
git reset --hardwill reset the project completely.
Open
Main.storyboardand select
the
UIWebView. Navigate to the Identity Inspector and set the class to TESTWebView as
shown below.
Note: Do not ship a subclass of
UIWebViewto
users; Apple asks you in the documentation not to do this, but it’s often informative to do dirty things like subclassing Apple’s code to understand how things work.
Build and run. The app should behave as it did before you subclassed
UIWebView.
This serves as verification that implementing an empty subclass of
UIWebViewdoes
not break anything. Scrolling and zooming as well as the little page number view should work as before.
Uncomment the scroll view delegate methods in
TESTWebView.mshown
below.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView; { NSLog(@"%@ %@ %@", self, NSStringFromSelector(_cmd), scrollView); if ([super respondsToSelector:_cmd]) { [super scrollViewDidScroll:scrollView]; } } - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; { NSLog(@"%@ %@ %@", self, NSStringFromSelector(_cmd), scrollView); UIView *viewForZooming = nil; if ([super respondsToSelector:_cmd]) { viewForZooming = [super viewForZoomingInScrollView:scrollView]; } return viewForZooming; }
Build and run. Notice that the delegate methods are called repeatedly on launch, much as you saw previously. Add a breakpoint in each new method in
TESTWebView,
then scroll the PDF to drop into the debugger during
-[TESTWebView scrollViewDidScroll:].
Show the backtrace with
bt.
(lldb) bt * thread #1: tid = 0x28ee34, 0x000000010ed22280 WebTest2`-[TESTWebView scrollViewDidScroll:](self=0x00007fa2b8c26510, _cmd=0x000000011053b1ed, scrollView=0x00007fa2ba056200) + 112 at TESTWebView.m:16, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 * frame #0: 0x000000010ed22280 WebTest2`-[TESTWebView scrollViewDidScroll:](self=0x00007fa2b8c26510, _cmd=0x000000011053b1ed, scrollView=0x00007fa2ba056200) + 112 at TESTWebView.m:16 frame #1: 0x000000010f6906ec CoreFoundation`__invoking___ + 140 frame #2: 0x000000010f69053e CoreFoundation`-[NSInvocation invoke] + 286 frame #3: 0x000000010f722c76 CoreFoundation`-[NSInvocation invokeWithTarget:] + 54 frame #4: 0x000000010fe49b2b UIKit`-[_UIWebViewScrollViewDelegateForwarder forwardInvocation:] + 94 frame #5: 0x000000010f6f8c57 CoreFoundation`___forwarding___ + 487 frame #6: 0x000000010f6f89e8 CoreFoundation`__forwarding_prep_0___ + 120 frame #7: 0x000000010fc16068 UIKit`-[UIScrollView(UIScrollViewInternal) _notifyDidScroll] + 66 frame #8: 0x000000010fc0386b UIKit`-[`UIScrollView` setContentOffset:] + 651 frame #9: 0x000000010fc081ac UIKit`-[`UIScrollView` _updatePanGesture] + 2066 ...
Type
cand hit enter in the debugger
to continue. Next try pinching. The debugger should now break in the
TESTWebView‘s
viewForZoomingInScrollView:method.
Type
btand compare the backtraces. Note any similarities.
In
ViewController.m, once again
set the scroll view delegate and reimplement
viewForZoomingInScrollView:as
shown below.
- (void)viewDidLoad {
...
[self.webView.scrollView setDelegate:self];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView; { NSLog(@"%@ %@ %@", self, NSStringFromSelector(_cmd), scrollView); return nil; }
Place a breakpoint after the NSLog line in
-[ViewController viewForZoomingInScrollView:]. Build and run with the breakpoints disabled. Watch the console for log messages while scrolling and zooming. Do you discern a pattern or order for the scroll view delegate calls?
Some Useful LLDB Commands
The LLDB command line has a number of powerful commands built in. Try typing breakpoint listinto LLDB.
(lldb) breakpoint list Current breakpoints: 1: name = 'objc_exception_throw', locations = 1, resolved = 1, hit count = 0 1.1: where = libobjc.A.dylib`objc_exception_throw, address = 0x000000010c300d9d, resolved, hit count = 0 2: file = '/Users/jonathan/WebTest2/WebTest2/TESTWebView.m', line = 26, locations = 1, resolved = 1, hit count = 2 2.1: where = WebTest2`-[TESTWebView viewForZoomingInScrollView:] + 112 at TESTWebView.m:26, address = 0x000000010be09360, resolved, hit count = 2
This shows a list of all of your breakpoints. Now find the
viewForZoomingInScrollViewbreakpoint
in the list and note its number. In the above it’s “2.1”, but depending on your Xcode setup or other breakpoints you have enabled it may be a different number. To disable that breakpoint type
breakpoint disable 2.1replacing “2.1” with your number and hit enter.
(lldb) breakpoint disable 2.1
1 breakpoints disabled.
Now type
breakpoint listagain.
(lldb) breakpoint list Current breakpoints: ... 2: file = '/Users/jonathan/WebTest2/WebTest2/TESTWebView.m', line = 26, locations = 1 2.1: where = WebTest2`-[TESTWebView viewForZoomingInScrollView:] + 112 at TESTWebView.m:26, address = 0x000000010be09360, unresolved, hit count = 2 Options: disabled
You should see “
Options: disabled”
next to the breakpoint you identified. To re-enable a breakpoint, type
breakpoint enableand then the number and hit enter.
To step through code in the debugger you can use the execution control buttons in the debug area.
I find using the mouse to click those little buttons tedious. Luckily, there are commands for all of these in the LLDB command line.
– Type
nand hit enter to move the point of execution to the next line of code
(step over). I remember this as “n for next.” This is equivalent to clicking the button shown below in the Xcode debugger execution controls.
Type
sand hit enter to step into the function
or method on that line. I remember this as “s for step-in.” This is equivalent to clicking the button shown below in the Xcode debugger execution controls.
Type
finishand hit enter to step out of
a function or method. This is equivalent to clicking the button shown below in the Xcode debugger execution controls.
Type
cto continue execution. I remember
this as “c to continue.” This is equivalent to clicking the button shown below in the Xcode debugger execution controls.
You can repeat the previously entered command by hitting the enter key at the
(lldb)prompt.
For example, if you wanted to step 5 times, you can type
nenter, enter, enter,
enter, enter. LLDB will repeat the
ncommand without retyping.
Using
breakpoint deleteand the
number will delete a breakpoint.
Note: Xcode will recreate any breakpoints that you have set in Xcode the next time you run the debugger, but will not recreate any breakpoints you set directly in the command line.
The game is afoot
Build and run again with breakpoints disabled. Once the app is running, set a breakpoint in TESTWebView‘s
viewForZoomingInScrollView:(you
may already have one set there) and enable breakpoints. Pinch to zoom. The debugger should break in the
TESTWebView‘s’
viewForZoomingInScrollView:method.
... if ([super respondsToSelector:_cmd]) { viewForZooming = [super viewForZoomingInScrollView:scrollView]; //Step over this line } ...
Step over (
nfor next) until you
have reached just beyond the
viewForZooming =line below the “
Step over this line” comment in the code listing above. Remember, once you type
nfor
next once you can repeatedly tap the enter key until the debugger stops on the line you want. Now
pothe
viewForZoomingvariable.
(lldb) po viewForZooming ; layer = >
This shows that the web view does return a view to zoom. This makes sense given that we’ve seen the scroll view zooming. Notice the size of the
UIWebPDFView!
It’s a very tall pdf. Beethoven wrote a bunch of notes in there.
Use the
ccommand to continue.
Repeat until the debugger stops at the view controller’s breakpoint. Notice that the backtrace is similar to those you have seen before. You might have noticed the order of the delegate callbacks. It looks like the
_UIWebViewScrollViewDelegateForwarderis
designed to call the
UIWebViewfirst and then call its scroll view’s delegate.
It also looks like its scroll view’s delegate can overrule the web view’s
viewForZoomingInScrollViewreturn
value since returning
nilfrom the view controller caused the web view not
to zoom.
Try breaking on all implementations of viewForZoomingInScrollView:
(lldb) breakpoint set -r "viewForZoomingInScrollView" Breakpoint 5: 5 locations.
In LLDB
breakpoint setmeans “set
a breakpoint.” The
-rflag means “match this regular expression.” You can use
a more complicated regular expression to catch certain method signatures that match a pattern, but right now you’re just looking to break on any method that matches “viewForZoomingInScrollView”, so this command will work.
If you would like to see all 5 locations LLDB knows about where
viewForZoomingInScrollViewis
implemented, type
breakpoint listbefore moving on. This shows you all of the
locations LLDB has resolved for the regular expression you asked it to match for this breakpoint. There is some interesting stuff in the list that shows some other scroll view delegates that implement this method in Apple’s frameworks.
Use the
ccommand to continue.
Note the backtrace each time the debugger stops by typing
bt. You’ll also notice
that the debugger stops in
-[UIWebView viewForZoomingInScrollView:]because
you called through to
super‘s implementation in the subclass, but you see disassembly
instead of the actual code. Since Xcode doesn’t have the source code available for
UIWebView‘s’
implementation, it will show you disassembly instead.
You may have noticed
-[_UIWebViewScrollViewDelegateForwarder forwardInvocation:]in the backtraces for the delegate methods. You can set a breakpoint in that method using the
breakpoint set --namecommand.
Build and run with breakpoints enabled. Once the debugger stops execution, add a symbolic breakpoint in
-[_UIWebViewScrollViewDelegateForwarder forwardInvocation]like so.
(lldb) breakpoint set --name "-[_UIWebViewScrollViewDelegateForwarder forwardInvocation:]" Breakpoint 4: where = UIKit`-[_UIWebViewScrollViewDelegateForwarder forwardInvocation:], address = 0x00000001029cfacd
The “–name” flag means “break on this exact method signature by name”. If you type
breakpoint listagain, you’ll notice that the regular expression breakpoint you set before did not persist. Remember that every time you build and run, LLDB clears your list of breakpoints. When Xcode starts LLDB, it reinserts the breakpoints you set in Xcode,
which is why those persist between runs.
Type
cto continue and print a
backtrace each time the debugger stops. Do you notice any further similarities?
More LLDB Tricks
Build and run again with breakpoints enabled. Pinch to zoom. Once the debugger pauses execution, add your breakpoint set -r "viewForZoomingInScrollView"symbolic breakpoint as before. Type
cto
continue. The debugger should stop in
-[UIWebView viewForZoomingInScrollView:].
You are now stopped inside of Apple’s code. Since Xcode does not have source code for UIWebView, it shows you the disassembly you saw before.
<img src="http://www.enharmonichq.com/wp-content/uploads/2015/07/Xcode_viewForZoomingInScrollView_Assembly.jpg" alt="
-[UIWebView viewForZoomingInScrollView:]assembly.” title=”
-[UIWebView viewForZoomingInScrollView:]
assembly.” />
When you set a symbolic breakpoint like this one, the debugger will break before the function prologue. The function prologue is code that prepares the stack and processor registers for executing the code of the function itself. After the function prologue
executes, arguments to functions will be in known registers. For example during an Objective-C message send, the pointer to “self” is always placed in a particular register, which is different per platform (such as armv7 vs arm64 or i386). Before the function
prologue executes that may not be the case. It is the function prologue’s job to place the values in these known registers. It is sometimes useful to be able to print out the arguments to a function or method, so LLDB has an option you can add to
breakpoint setcalled
--skip-prologuethat will tell it to break after the prologue has placed those registers into a known state. You can then print values stored in registers to explore arguments to the function or method.
To see this in action, build and run with breakpoints enabled. Wait for the debugger to stop and type
breakpoint set --skip-prologue true -r " viewforzoominginscrollview"<="" code="" style="max-width: 100%; height: auto; vertical-align: middle; border: 0px;"> to cause LLDB to set a regular expression breakpoint just after the prologue. Type cto
continue until you again see disassembly in Xcode. The debugger should have stoped in
-[UIWebView viewForZoomingInScrollView:]as before. If you want to see the value of “self” (the
TESTWebView)
in this method on 32-bit arm processors (such as the iPhone 4S) you would type
po $r0, which translates to “printout register r-zero.” This is where the pointer to self is stored on 32-bit arm. On 64-bit arm processors, the command is
po $x0. On x86 processors… well, this can get complicated. Luckily, you do not need to memorize these; LLDB maintains an alias to the first argument’s register it uses on all platforms called $arg1. You can type
po $arg1to get the first argument to a C function on whichever architecture you are running. The [
objc_msgSend()][Messaging]
variadic C function, one of the functions the Objective-C runtime uses to send messages to objects, has a particular argument order. The first argument is
self,
the second argument is the selector, the third argument is the first argument to the method (the scrollView in
viewForZoomingInScrollView:,
for example) and so on.
(lldb) po $arg1 >
“Self” (stored in the register corresponding to the
$arg1alias)
is the
TestWebViewinstance.
To see the second, selector argument, you look in
$arg2.
This is the
_cmdargument you have seen in calls to
NSLogin
the test project. Since it’s an Objective-C selector, you will want to wrap
$arg2(
_cmd)
in a call to
NSStringFromSelector(), otherwise LLDB will treat it as an integer.
(lldb) po NSStringFromSelector($arg2) viewForZoomingInScrollView:
To see the first argument to the Objective-C method (the third argument to
objc_msg_send())
you use $arg3 like so.
(lldb) po $arg3 ; layer = ; contentOffset: {10, 18}; contentSize: {395.36166408245765, 10328.695109977556}>
If there were further arguments, you could use
$arg4,
etc.
To see the state of commonly used registers on the processor, use
register read. This is what that looks like on a 32-bit iOS device.
(lldb) register read General Purpose Registers: r0 = 0x145527f0 r1 = 0x2d084885 "viewForZoomingInScrollView:" r2 = 0x152a3200 r3 = 0x376300a1 libobjc.A.dylib`objc_msgSendSuper2 + 1 r4 = 0x14520260 r5 = 0x14573320 r6 = 0x145731e0 r7 = 0x00120fe8 r8 = 0x37f9b0a4 CoreFoundation`NSInvocation._frame r9 = 0x3860650c (void *)0x38606520: UIWebView r10 = 0x14665c80 r11 = 0x00000004 r12 = 0x2cae549d UIKit`-[UIWebView viewForZoomingInScrollView:] + 1 sp = 0x00120fac lr = 0x000157fd WebTest2`-[TESTWebView viewForZoomingInScrollView:] + 213 at TESTWebView.m:28 pc = 0x2cae549c UIKit`-[UIWebView viewForZoomingInScrollView:] cpsr = 0x20000030
That’s a little tough to read. LLDB has a “make this easier to read” command (well, it’s not called that, but that’s how I think of it):
register read -A
Note: You may get different output based on the device architecture and version of Xcode used.
(lldb) register read -A General Purpose Registers: arg1 = 0x145527f0 arg2 = 0x2d084885 "viewForZoomingInScrollView:" arg3 = 0x152a3200 arg4 = 0x376300a1 libobjc.A.dylib`objc_msgSendSuper2 + 1 r4 = 0x14520260 r5 = 0x14573320 r6 = 0x145731e0 fp = 0x00120fe8 r8 = 0x37f9b0a4 CoreFoundation`NSInvocation._frame r9 = 0x3860650c (void *)0x38606520: UIWebView r10 = 0x14665c80 r11 = 0x00000004 r12 = 0x2cae549d UIKit`-[UIWebView viewForZoomingInScrollView:] + 1 r13 = 0x00120fac r14 = 0x000157fd WebTest2`-[TESTWebView viewForZoomingInScrollView:] + 213 at TESTWebView.m:28 r15 = 0x2cae549c UIKit`-[UIWebView viewForZoomingInScrollView:] flags = 0x20000030
LLDB also has a built-in help system. If you’re searching for a command and do not know exactly what it is called use the
aproposcommand.
Try
apropos thread.
(lldb) apropos thread The following built-in commands may relate to 'thread': ... breakpoint command add -- Add a set of commands to a breakpoint, to be executed whenever the breakpoint is hit. If no breakpoint is specified, adds the commands to the last created breakpoint. breakpoint modify -- Modify the options on a breakpoint or set of breakpoints in the executable. If no breakpoint is specified, acts on the last created breakpoint. With the exception of -e, -d and -i, passing an empty argument clears the modification. breakpoint set -- Sets a breakpoint or set of breakpoints in the executable. ... thread -- A set of commands for operating on one or more threads within a running process. thread backtrace -- Show the stack for one or more threads. If no threads are specified, show the currently selected thread. Use the thread-index "all" to see all threads. thread continue -- Continue execution of one or more threads in an active process. ... The following settings variables may relate to 'thread': frame-format -- The default frame format string to use when displaying stack frame information for threads. thread-format -- The default thread format string to use when displaying thread information. target.process.thread.trace-thread -- If true, this thread will single-step and log execution.
If you know which command you are interested in, you can learn more about it by typing
helpand
the command name. Like so.
(lldb) help breakpoint The following subcommands are supported: clear -- Clears a breakpoint or set of breakpoints in the executable. command -- A set of commands for adding, removing and examining bits of code to be executed when the breakpoint is hit (breakpoint 'commands'). delete -- Delete the specified breakpoint(s). If no breakpoints are specified, delete them all. disable -- Disable the specified breakpoint(s) without removing it/them. If no breakpoints are specified, disable them all. enable -- Enable the specified disabled breakpoint(s). If no breakpoints are specified, enable all of them. list -- List some or all breakpoints at configurable levels of detail. modify -- Modify the options on a breakpoint or set of breakpoints in the executable. If no breakpoint is specified, acts on the last created breakpoint. With the exception of -e, -d and -i, passing an empty argument clears the modification. name -- A set of commands to manage name tags for breakpoints set -- Sets a breakpoint or set of breakpoints in the executable. For more help on any particular subcommand, type 'help <command></command> '. <command></command> ``` The help system will give you more information on a subcommand as well. For instance, if you wanted to know more about `breakpoint set`, you can type `help breakpoint set`. ```text (lldb) help breakpoint set Sets a breakpoint or set of breakpoints in the executable. Syntax: breakpoint set Command Options Usage: breakpoint set [-DHo] -l [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] [-f ] [-K ] [-N ] breakpoint set [-DHo] -a [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] [-N ] ... ... -F ( --fullname ) Set the breakpoint by fully qualified function names. For C++ this means namespaces and all arguments, and for Objective C this means a full function prototype with class and selector. Can be repeated multiple times to make one breakpoint for multiple names. ... -p ( --source-pattern-regexp ) Set the breakpoint by specifying a regular expression which is matched against the source text in a source file or files specified with the -f option. The -f option can be specified more than once. If no source files are specified, uses the current "default source file" ...
Discovery
By now you’ve probably noticed a pattern emerging within the backtraces in the test project. When the UIScrollViewDelegatemethods
in both the
UIWebViewand delegate you set (the view controller) gets called,
you can see from the backtraces that a consistent series of steps lead to each delegate method. First, there is a call to the web view’s implementation of each method. Second, the scroll view’s delegate gets called. By default, the scroll view’s
delegateproperty
getter returns
nil, but the web view gets the callbacks whether or not the
scroll view has a delegate. It also looks like when you do set a delegate on the scroll view, the delegate’s
viewForZoomingInScrollview‘s
return value overrides the return value of the web view’s implementation, otherwise the web view’s return value is used.
Initial Conclusions
You have noticed the delegate forwarder object seems designed to insinuate itself between the scroll view and its web view and between the scroll view and your delegate. Given its name, it seems likely that Appledesigned
_UIWebViewScrollViewDelegateForwarderto forward
UIScrollViewDelegatemessages
to both objects to allow you full access to the scroll view’s delegate methods without disrupting the web view.
Here is an object diagram including what you’ve discovered so far.
What you have found with LLDB helps explain Apple’s intentions when they say “Your
application can access the scroll view if it wants to customize the scrolling behavior of the web view”. Besides the behavior you have seen so far, given that there is a class named
_UIWebViewScrollViewDelegateForwarder,
it stands to reason that Apple intends for you to become the delegate of the scroll view if you need to do so. You could probably conclude now that it is safe to become the scroll view’s delegate, but you might not be comfortable with this just yet. You do
not yet know how the forwarder is implemented and whether its implementation could introduce unforeseen fragility into your app.
To learn how Apple implemented the delegate forwarder and how they place it between the scroll view and its delegates, read iOS
Reverse Engineering Part II: class-dump and Hopper Disassembler.
References
UIWebView Class ReferenceUIWebView
Class Reference: scrollView
UIScrollView Class Reference
UIScrollViewDelegate
Protocol Reference
WebTest2 test code
LLDB
Additional Resources
LLDBQuick Start Guide
WWDC 2013: Advanced Debugging with LLDB (#413)
LLDB Python Reference
Technical Note TN2239: iOS Debugging
Magic
Technical Note TN2124:
Mac OS X Debugging Magic
IOS LLDB MAC REVERSE
ENGINEERING XCODE
This entry was posted on Thursday, July 2nd, 2015 at 9:26 pm and is filed under Development, Tutorial.
You can follow any responses to this entry through the RSS 2.0 feed. You can leave
a response, or trackbackfrom your own site.
相关文章推荐
- 滑动手势
- iOS中的定位
- iOS开发装逼特技之 实用快捷键系列
- iOS中利用 runtime 一键改变字体
- iOS发开蛋疼集锦
- ios记录-数据存储
- IOS之Foundation框架
- iOS中复制对象的用法及深拷贝和浅拷贝详解
- iOS 浅谈:深.浅拷贝与copy.strong
- iOS安全入门
- ios objc 方法调用记录插件(支持arm64+devlog):itracer v1.2
- 【iOS--pod】使用初体验 --> libjingle_peerconnection
- 慧都独家揭晓国外十大Mac和iOS应用开发工具
- [转]iOS开发中@property的属性weak nonatomic strong readonly等介绍
- 【开源】专业K线绘制[K线主副图、趋势图、成交量、滚动、放大缩小、MACD、KDJ等)
- IOS函数可变参数
- iOS类似探探交友滑动动画分析
- iOS设计模式(一) 原型模式
- iOS framework 使用单元测试报错解决
- iOS MVVM 框架设计 和 MVC 框架