您的位置:首页 > 移动开发 > IOS开发

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 Saggau

Hello 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.

UIWebView
embeds 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.
UIWebView
exposes
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.”



UIWebView
implements the
UIScrollViewDelegate
protocol.
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
UIScrollViewDelegate
methods
of the
UIWebView
without getting in the way of Apple’s implementation of the
UIWebView
to
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
UIScrollView
can
have only one
delegate
and 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
delegate
instead
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
delegate
to
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
UIWebView
loaded from the app’s
main storyboard file. The view controller loads a PDF containing a Beethoven piano sonata during the
viewDidLoad
method
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.m
and
that we’ve implemented a subclass of
UIWebView
with some commented-out
UIScrollViewDelegate
methods
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’s
UI 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.m
and add
a breakpoint at the top of the
viewDidLoad
method 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
(
po
is 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
delegate
at
all. How does a
UIWebView
handle scroll and zoom events if it is not the
delegate
of
its scroll view?
In
ViewController.m
try setting
the
webview.scrollview.delegate
to 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
UIScrollViewDelegate
are
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
nil
if
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
nil
from
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
scrollview
into 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)
UIScrollView
subclass
for the
UIWebView
. This may be a clue.
Now take a look at the backtrace by typing
bt
or
backtrace
into
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:
_UIWebViewScrollViewDelegateForwarder
somehow
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
UIWebView
with
a subclass called
TESTWebView
included in the project you downloaded. First,
revert the code in
ViewController.m
to 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
stash
your changes first if you’ve done other experimentation in the project or changed provisioning and code signing settings as
git
reset --hard
will reset the project completely.
Open
Main.storyboard
and select
the
UIWebView
. Navigate to the Identity Inspector and set the class to TESTWebView as
shown below.



Note: Do not ship a subclass of
UIWebView
to
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
UIWebView
does
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.m
shown
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
c
and hit enter in the debugger
to continue. Next try pinching. The debugger should now break in the
TESTWebView
‘s
viewForZoomingInScrollView:
method.
Type
bt
and 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
list
into 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
viewForZoomingInScrollView
breakpoint
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.1
replacing “2.1” with your number and hit enter.
(lldb) breakpoint disable 2.1

1 breakpoints disabled.
Now type
breakpoint list
again.
(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
enable
and 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
n
and 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
s
and 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
finish
and 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
c
to 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
n
enter, enter, enter,
enter, enter. LLDB will repeat the
n
command without retyping.
Using
breakpoint delete
and 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 (
n
for 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
n
for
next once you can repeatedly tap the enter key until the debugger stops on the line you want. Now
po
the
viewForZooming
variable.
(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
c
command 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
_UIWebViewScrollViewDelegateForwarder
is
designed to call the
UIWebView
first and then call its scroll view’s delegate.
It also looks like its scroll view’s delegate can overrule the web view’s
viewForZoomingInScrollView
return
value since returning
nil
from 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 set
means “set
a breakpoint.” The
-r
flag 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
viewForZoomingInScrollView
is
implemented, type
breakpoint list
before 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
c
command 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 --name
command.
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
list
again, 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
c
to 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
c
to
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 set
called
--skip-prologue
that 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 c
to
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
$arg1
to 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
$arg1
alias)
is the
TestWebView
instance.
To see the second, selector argument, you look in
$arg2
.
This is the
_cmd
argument you have seen in calls to
NSLog
in
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
apropos
command.
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
help
and
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
UIScrollViewDelegate
methods
in both the
UIWebView
and 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
delegate
property
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 Apple
designed
_UIWebViewScrollViewDelegateForwarder
to forward
UIScrollViewDelegate
messages
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 Reference
UIWebView
Class Reference: scrollView
UIScrollView Class Reference
UIScrollViewDelegate
Protocol Reference
WebTest2 test code
LLDB


Additional Resources

LLDB
Quick 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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: