Hardcore debugging

Hi guys,

I’d like to expose some debugging magic today. There’s pretty much information can be found all over the internet about how to debug crashes, how to debug leaks and other memory issues. You can start from the official apple’s tech note and go further.

What I’d like to talk about today is something different from that. I stuck recently with a very hard-to-investigate bug in my project, and lldb’s ability to show me objective-c internals was of a very good help.

The Problem

But let me first describe what was the issue. It doesn’t matter what the project was and was it’s main functionality, but the essentials for the bug I faced were:

  • from time to time I needed to switch main window’s rootViewController
  • there was a separate component for showing some content fullscreen, with orientation handling support built-in. It is added as subview to main window and can handle rotation regardless of orientation support by controller currently used by window as rootViewController


So, the bug was being reproduced as follows:

  1. Show fullscreen component.
  2. Hide fullscreen component temporally (leave it’s current state, just remove it’s view from window)
  3. Switch window’s rootViewController
  4. Try rotating device, although current rootViewController supports rotation, it doesn’t get all that rotation notifications.

The key was that fullscreen component being shown. Before showing it, the viewController mentioned gets rotated as expected. I couldn’t even thing from where to start with this bug.

See inside, there is nothing to hide

And then I started from seeing internals. It’s obvious, that there’s some point in UIKit’s internals where decision is taken, whether to show rotation notifications or not. I tried to find this place.

So I put the breakpoint at the start of willRotateToInterfaceOrientation: duration: method to see the point in internals from where it is being called. Then start app and try rotate in usual way to get this breakpoint hit. Here’s the stack:

As you can see, there’s a bunch of methods in stack, I don’t know where is this my point of interest, so I decided to just to put breakpoint at every method’s start and at the place where next method is being called. I will use these path when bug is reproduced to figure out when this workflow goes out of track.

Next, let our bug occur and let’s step along this stack and find the breakpoint that is not fired. After some attempts, I have finally figured out, that following fragment is responsible for going further and perform rotation, or quit without doing anything:

0x5e424d:  movl   8(%ebp), %ebx
0x5e4250:  movl   6841556(%edi), %eax
0x5e4256:  cmpl   (%ebx,%eax), %esi
0x5e4259:  je     0x5e42b4                                 ; -[[UIWindow _updateInterfaceOrientationFromDeviceOrientation:]] + 170

As we can see there’s something being compared. First some local variable is placed in n $ebx, than something is loaded from data segment in $eax, next variable placed within given offset in object from $ebx is compared with contents of $esi. Let’s try to see what the object is:

(lldb) po *(id*)$ebx
(id) $27 = 0x00c63324 UIWindow

Aha, it’s UIWindow instance! so, offset from the beginning of this object is :

(lldb) print $eax
(unsigned int) $28 = 84

Honestly, I haven’t tried to see what’s  the variable is placed there computing offsets. I tried to see inside the UIWindow’s instance and guess. To do this I add new expression to variables view:

 (UIWindow*)(id*)$ebx
 

Here’s what I have:

It’s not that easy now to figure out what member variable is placed under 82 offset. The trick I used is to look inside of value. The value at this offset should be :

(lldb) print *(int*)($eax+$ebx)
(int) $45 = 2

print (UIInterfaceOrientation)*(int*)($eax+$ebx)
(UIInterfaceOrientation) $46 = UIInterfaceOrientationPortraitUpsideDown

Why did I cast it to UIInterfaceOrientation? Well, I guessed that it might be member variable _viewOrientation. And yes, it’s value the same we found. And remembering the context of the problem it definitely makes sense: before performing orientation change handing UIWindow checks if it is already in that orientation. But where the value in $esi comes from? Let’s see asm dump before this fragment:

0x5e4219:  movl   6805500(%edi), %eax
0x5e421f:  movl   6757808(%edi), %ecx
0x5e4225:  movl   %ecx, 4(%esp)
0x5e4229:  movl   %eax, (%esp)
0x5e422c:  calll  0xa6371a                  ; symbol stub for: objc_msgSend
0x5e4231:  movl   6759088(%edi), %ecx
0x5e4237:  movl   %ecx, 4(%esp)
0x5e423b:  movl   %eax, (%esp)
0x5e423e:  calll  0xa6371a                  ; symbol stub for: objc_msgSend
0x5e4243:  movl   %eax, %esi
0x5e4245:  leal   -1(%esi), %eax
0x5e4248:  cmpl   $3, %eax
0x5e424b:  ja     0x5e42ac                  ; -[UIWindow _updateInterfaceOrientationFromDeviceOrientation:] + 162
0x5e424d:  movl   8(%ebp), %ebx
0x5e4250:  movl   6841556(%edi), %eax
0x5e4256:  cmpl   (%ebx,%eax), %esi

As seen, $esi gets it’s value at line 0x5e423e from $eax, which takes it’s value as result of function call before. See, debugger even tells us that this function call is an objective-c method calling. If you’re not yet familiar with how objective-c methods are called, well it’s pretty well described here, at this point we’ll take as fact that $esp contains address of an object whom message is being sent, and $esp+4 is a selector name (as plain c string). So, let debugger run till line 0x5e423e and let’s see what’s is in the stack:

(lldb) po *(id*)$esp
(id) $7 = 0x07d66470 <UIDevice: 0x7d66470>
(lldb) print *(char**)($esp+4)
(char *) $10 = 0x04136f36 "orientation"

Performing the same actions some lines before would show that $eax value (address of object) is obtained through the call to [UIDevice currentDevice].
And now the question is why this member variable has already new orientation’s value? Well, it’s not so hard to find out: lets’ put watch on it and see.

Ok, I put the watch, rotated the simulator and gotcha! Watch was hit, here’s the stack:

See solid-black coloured line? Yes, it’s the line in my code, the exact line of code which leads the bug to appear. Going forward, the problem of my bug was that I observed deviceOrientation changes in that place and set statusBarOrientation manually, which apparently modified current UIWindow’s _viewOrientation property. Not so obvious to guess without seeing these internals, but now it’s clear for me.

Conclusion

Although this technic is rarely used, it shows how powerful lldb debugger can be in certain situations. If you’re not yet familiar with it  — seriously, take some time and learn how to use it. Some day it will save you week of guessing what could be wrong in your code.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s