Home > Software engineering >  Printing `NSRect` argument passed to Cocoa method in lldb
Printing `NSRect` argument passed to Cocoa method in lldb

Time:09-15

In order to debug a macOS program I need to print the NSRect that is passed to -[NSView:setNeedsDisplayInRect:]. I can set a breakpoint in that method, but I have trouble printing its argument.

NSRect is “essentially” a struct of four doubles. In order to demonstrate the problem I have written a small self-contained program. It is compiled with Xcode 12.5 and run on a Mac mini with M1 processor.

#include <stdio.h>

typedef struct {
    double a;
    double b;
    double c;
    double d;
} MyRect1;

typedef struct {
    double a;
    double b;
    double c;
    long d;
} MyRect2;

void foo(MyRect1 rect) {
    printf("%f\n", rect.a);
}

void bar(MyRect2 rect) {
    printf("%f\n", rect.a);
}

int main(int argc, const char * argv[]) {
    MyRect1 rect1 = { 1, 2, 3, 4 };
    MyRect2 rect2 = { 1, 2, 3, 4 };
    foo(rect1);
    bar(rect2);
    return 0;
}

The Procedure Call Standard for the ARM 64-bit Architecture (AArch64) states that

If the argument type is a Composite Type that is larger than 16 bytes, then the argument is copied to memory allocated by the caller and the argument is replaced by a pointer to the copy.

Therefore I would expect that both foo() and bar() are called with a pointer to the structure as the single argument. Setting a breakpoint in bar() and casting the first argument to MyRect2 * indeed produces the expected result:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100003e94 argtest2`bar(rect=(a = 1, b = 2, c = 3, d = 4)) at main.c:22:29
    frame #1: 0x0000000100003f34 argtest2`main(argc=1, argv=0x000000016fdff428) at main.c:29:9
    frame #2: 0x000000019871d430 libdyld.dylib`start   4
(lldb) expr -- *(MyRect2 *)$arg1
(MyRect2) $0 = (a = 1, b = 2, c = 3, d = 4)

However, this does not work with MyRect1 in foo():

lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x0000000100003e64 argtest2`foo(rect=(a = 1, b = 2, c = 3, d = 4)) at main.c:18:29
    frame #1: 0x0000000100003f1c argtest2`main(argc=1, argv=0x000000016fdff428) at main.c:28:9
    frame #2: 0x000000019871d430 libdyld.dylib`start   4
(lldb) expr -- *(MyRect1 *)$arg1
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory

Note that both structs have the same size (32 bytes), and differ only in the last member.

Question: How can I print the argument passed to a function in lldb if that argument is of type NSRect (or any other struct of four doubles)?

CodePudding user response:

Switching the double for the long in your structure was actually significant. A struct of four doubles is a Homogeneous Floating-point Aggregate, whereas the struct of three doubles and a long is a Composite Type, and they have different passing rules. The former is passed in the floating point registers if it fits (as you have found), whereas the latter is passed on the stack.

BTW, if you want to read the values passed into a function for which you don't have debug information, it's best to stop before the "function prologue" has been executed. The function prologue (especially in optimized code) will often copy registers from their original locations, and reuse them, so the register state after the prologue will no longer reflect the calling conventions.

However, debug information for functions is generally not accurate during the prologue. There's no technical reason why it can't be, but no compilers I know of track the prologue, and unless you are actually debugging the compiler output that region of code isn't terribly interesting. So for code with debug information it's more convenient to stop after the prologue is executed, and that's the lldb default.

To switch that default, add --skip-prologue 0 to your break set command.

  • Related