Assembly Register Calling Convention Tutorial

Learn how the CPU uses registers in this tutorial taken from our newest book, Advanced Apple Debugging & Reverse Engineering! By Derek Selander.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Swift and Registers

When exploring registers in Swift, you’ll hit two hurdles that make assembly debugging harder than it is in Objective-C.

  1. First, registers are not available in the Swift debugging context. This means you have to get whatever data you want and then use the Objective-C debugging context to print out the registers passed into the Swift function. Remember that you can use the expression -l objc -O -- command, or alternatively use the cpo custom command found in Chapter 8 of the book, “Persisting and Customizing Commands”. Fortunately, the register read command is available in the Swift context.
  2. Second, Swift is not as dynamic as Objective-C. In fact, it’s sometimes best to assume that Swift is like C, except with a very, very cranky and bossy compiler. If you have a memory address, you need to explicitly cast it to the object you expect it to be; otherwise, the Swift debugging context has no clue how to interpret a memory address.

That being said, the same register calling convention is used in Swift. However, there’s one very important difference. When Swift calls a function, it has no need to use objc_msgSend, unless of course you mark up a method to use dynamic. This means when Swift calls a function, the previously used RSI register assigned to the selector will actually contain the function’s second parameter.

Enough theory — time to see this in action.

In the Registers project, navigate to ViewController.swift and add the following function to the class:

func executeLotsOfArguments(one: Int, two: Int, three: Int,
                            four: Int, five: Int, six: Int,
                            seven: Int, eight: Int, nine: Int,
                            ten: Int) {
    print("arguments are: \(one), \(two), \(three),
          \(four), \(five), \(six), \(seven),
          \(eight), \(nine), \(ten)")
}

Now, in viewDidLoad, call this function with the appropriate arguments:

override func viewDidLoad() {
  super.viewDidLoad()
  self.executeLotsOfArguments(one: 1, two: 2, three: 3, four: 4,
                              five: 5, six: 6, seven: 7,
                              eight: 8, nine: 9, ten: 10)
}

Put a breakpoint on the very same line as of the declaration of executeLotsOfArguments so the debugger will stop at the very beginning of the function. This is important, or else the registers might get clobbered if the function is actually executing.

Then remove the symbolic breakpoint you set on -[NSViewController viewDidLoad].

Build and run the app, then wait for the executeLotsOfArguments breakpoint to stop execution.

Again, a good way to start investigating is to dump the list registers. In LLDB, type the following:

(lldb) register read -f d 

This will dump the registers and display the format in decimal by using the -f d option. The output will look similar to this:

General Purpose Registers:
       rax = 7
       rbx = 9
       rcx = 4
       rdx = 3
       rdi = 1
       rsi = 2
       rbp = 140734799801424
       rsp = 140734799801264
        r8 = 5
        r9 = 6
       r10 = 10
       r11 = 8
       r12 = 107202385676032
       r13 = 106652628550688
       r14 = 10
       r15 = 4298620128  libswiftCore.dylib`swift_isaMask
       rip = 4294972615  Registers`Registers.ViewController.viewDidLoad () -> () + 167 at ViewController.swift:16
    rflags = 518
        cs = 43
        fs = 0
        gs = 0

As you can see, the registers follow the x64 calling convention. RDI, RSI, RDX, RCX, R8 and R9 hold your first six parameters.

You may also notice other parameters are stored in some of the other registers. While this is true, it’s simply a leftover from the code that sets up the stack for the remaining parameters. Remember, parameters after the sixth one go on the stack.

RAX, the Return Register

But wait — there’s more! So far, you’ve learned how six registers are called in a function, but what about return values?

Fortunately, there is only one designated register for return values from functions: RAX. Go back to executeLotsOfArguments and modify the function to return a String, like so:

func executeLotsOfArguments(one: Int, two: Int, three: Int,
                            four: Int, five: Int, six: Int,
                            seven: Int, eight: Int, nine: Int,
                            ten: Int) -> String {
    print("arguments are: \(one), \(two), \(three), \(four),
          \(five), \(six), \(seven), \(eight), \(nine), \(ten)")
    return "Mom, what happened to the cat?"
}

In viewDidLoad, modify the function call to receive and ignore the String value.

override func viewDidLoad() {
    super.viewDidLoad()
    let _ = self.executeLotsOfArguments(one: 1, two: 2,
          three: 3, four: 4, five: 5, six: 6, seven: 7,
          eight: 8, nine: 9, ten: 10)
}

Create a breakpoint somewhere in executeLotsOfArguments. Build and run again, and wait for execution to stop in the function. Next, type the following into the LLDB console:

(lldb) finish

This will finish executing the current function and pause the debugger again. At this point, the return value from the function should be in RAX. Type the following into LLDB:

(lldb) register read rax 

You’ll get something similar to the following:

rax = 0x0000000100003760  "Mom, what happened to the cat?"

Boom! Your return value!

Knowledge of the return value in RAX is extremely important as it will form the foundation of debugging scripts you’ll write in later sections.

Changing Values in Registers

In order to solidify your understanding of registers, you’ll modify registers in an already-compiled application.

Close Xcode and the Registers project. Open a Terminal window and launch the iPhone 7 Simulator. Do this by typing the following:

xcrun simctl list

You’ll see a long list of devices. Search for the latest iOS version for which you have a simulator installed. Underneath that section, find the iPhone 7 device. It will look something like this:

iPhone 7 (269B10E1-15BE-40B4-AD24-B6EED125BC28) (Shutdown)

The UUID is what you’re after. Use that to open the iOS Simulator by typing the following, replacing your UUID as appropriate:

open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app --args -CurrentDeviceUDID 269B10E1-15BE-40B4-AD24-B6EED125BC28

Make sure the simulator is launched and is sitting on the home screen. You can get to the home screen by pressing Command + Shift + H. Once your simulator is set up, head over to the Terminal window and attach LLDB to the SpringBoard application:

lldb -n SpringBoard

This attaches LLDB to the SpringBoard instance running on the iOS Simulator! SpringBoard is the program that controls the home screen on iOS.

Once attached, type the following into LLDB:

(lldb) p/x @"Yay! Debugging"

You should get some output similar to the following:

(__NSCFString *) $3 = 0x0000618000644080 @"Yay! Debugging!"

Take a note of the memory reference of this newly created NSString instance as you’ll use it soon. Now, create a breakpoint on UILabel’s setText: method in LLDB:

(lldb) b -[UILabel setText:]

Next, type the following in LLDB:

(lldb) breakpoint command add 

LLDB will spew some output and go into multi-line edit mode. This command lets you add extra commands to execute when the breakpoint you just added is hit. Type the following, replacing the memory address with the address of your NSString from above:

> po $rdx = 0x0000618000644080
> continue
> DONE

Take a step back and review what you’ve just done. You’ve created a breakpoint on UILabel’s setText: method. Whenever this method gets hit, you’re replacing what’s in RDX — the third parameter — with a different NSString instance that says Yay! Debugging!.

Resume the debugger by using the continue command:

(lldb) continue

Try exploring the SpringBoard Simulator app and see what content has changed. Swipe up from the bottom to bring up the Control Center, and observe the changes:

Try exploring other areas where modal presentations can occur, as this will likely result in a new UIViewController (and all of its subviews) being lazily loaded, causing the breakpoint action to be hit.

Although this might seem like a cool gimmicky programming trick, it provides an insightful look into how a limited knowledge of registers and assembly can produce big changes in applications you don’t have the source for.

This is also useful from a debugging standpoint, as you can quickly visually verify where the -[UILabel setText:] is executed within the SpringBoard application and run breakpoint conditions to find the exact line of code that sets a particular UILabel’s text.

To continue this thought, any UILabel instances whose text did not change also tells you something. For example, the UIButtons whose text didn’t change to Yay! Debugging! speaks for itself. Perhaps the UILabel’s setText: was called at an earlier time? Or maybe the developers of the SpringBoard application chose to use setAttributedText: instead? Or maybe they’re using a private method that is not publicly available to third-party developers?

As you can see, using and manipulating registers can give you a lot of insight into how an application functions. :]