Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

6. Thread, Frame & Stepping Around
Written by Derek Selander

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

You’ve learned how to create breakpoints, how to print and modify values, as well as how to execute code while paused in the debugger. But so far, you’ve been left high and dry on how to move around in the debugger and inspect data beyond the immediate. It’s time to fix that!

In this chapter, you’ll learn how to move the debugger in and out of functions while LLDB is currently paused.

This is a critical skill to have since you often want to inspect values as they change over time when entering or exiting snippets of code.

Stack 101

When a computer program executes, it stores values in the stack and the heap. Both have their merits. As an advanced debugger, you’ll need to have a good understanding of how these work. Right now, let’s take a brief look at the stack.

You may already know the whole spiel about what a stack is in computer science terms. In any case, it’s worth having a basic understanding (or refresher) of how a process keeps track of code and variables when executing. This knowledge will come in handy as you’re using LLDB to navigate around code.

The stack is a LIFO (Last-In-First-Out) queue that stores references to your currently executing code. This LIFO ordering means that whatever is added most recently, is removed first. Think of a stack of plates. Add a plate to the top, and it will be the one you take off first.

The stack pointer points to the current top of the stack. In the plate analogy, the stack pointer points to that top plate, telling you where to take the next plate from, or where to put the next plate on.

In this diagram, the high address is shown at the top (0xFFFFFFFF) and the low address is shown at the bottom (0x00000000) showcasing the stack would grow downwards.

Some illustrations like to have the high address at the bottom to match with the plate analogy as the stack would be shown growing upwards. However, I believe any diagrams showcasing the stack should be shown growing downwards from a high address because this will cause less headaches later on when talking about offsets from the stack pointer.

You’ll take an in depth look at the stack pointer and other registers in Chapter 13, “Assembly and the Stack”, but in this chapter you’ll explore various ways to step through code that is on the stack.

Examining the stack’s frames

You’ll continue to use the Signals project for this chapter.

Signals.MasterViewController.viewWillAppear(Swift.Bool) -> ()

(lldb) thread backtrace
(lldb) frame info
frame #0: 0x000000010ba1f8dc Signals`MasterViewController.viewWillAppear(animated=false, self=0x00007fd286c0af10) at MasterViewController.swift:50
(lldb) frame select 1

Stepping

When mastering LLDB, the three most important navigation actions you can do while the program is paused revolve around stepping through a program. Through LLDB, you can step over, step in, or step out of code.

Stepping over

Stepping over allows you to step to the next code statement (usually, the next line) in the context where the debugger is currently paused. This means if the current statement is calling another function, LLDB will run until this function has completed and returned.

(lldb) run
(lldb) next

Stepping in

Stepping in means if the next statement is a function call, the debugger will move into the start of that function and then pause again.

(lldb) run
(lldb) step
(lldb) settings show target.process.thread.step-in-avoid-nodebug
(lldb) step -a0 

Stepping out

Stepping out means a function will continue for its duration then stop when it has returned. From a stack viewpoint, execution continues until the stack frame is popped off.

(lldb) finish

Stepping in the Xcode GUI

Although you get much more finer-grained control using the console, Xcode already provides these options for you as buttons just above the LLDB console. These buttons appear when an application is running.

Examining data in the stack

A very interesting option of the frame command is the frame variable subcommand. This command will take the debug symbol information found in the headers of your executable (or a dYSM if your app is stripped… more on that later) and dump information out for that particular stack frame. Thanks to the debug information, the frame variable command can easily tell you the scope of all the variables in your function as well as any global variables within your program using the appropriate options.

(lldb) frame variable
(Bool) animated = false
(Signals.MasterViewController) self = 0x00007fb3d160aad0 {
  UIKit.UITableViewController = {
    baseUIViewController@0 = <extracting data from value failed>

    _tableViewStyle = 0
    _keyboardSupport = nil
    _staticDataSource = nil
    _filteredDataSource = 0x000061800005f0b0
    _filteredDataType = 0
  }
  detailViewController = nil
}

(lldb) frame variable -F self 
self = 0x00007fff5540eb40
self =
self =
self =
self = {}
self.detailViewController = 0x00007fc728816e00
self.detailViewController.some =
self.detailViewController.some =
self.detailViewController.some = {}
self.detailViewController.some.signal = 0x00007fc728509de0

Where to go from here?

In this chapter, you’ve explored stack frames and the content in them. You’ve also learned how to navigate the stack by stepping in, out, and over code.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now