如何在LLDB中打印传递给Cocoa方法的NSRect参数?
调试macOS程序时,我需要打印传递给-[NSView setNeedsDisplayInRect:]的NSRect参数,断点能正常命中,但无法正常打印该参数。NSRect本质是包含四个double的结构体,为此我编写了一段测试程序(使用Xcode 12.5编译,在M1处理器的Mac mini上运行)来复现问题:
#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; }
根据ARM64架构过程调用标准:
如果参数类型是大于16字节的复合类型,则参数会被复制到调用者分配的内存中,并替换为指向该副本的指针。
我原本预期foo()和bar()都会以结构体指针作为参数调用。测试中bar()里的MyRect2确实可以通过*(MyRect2 *)$arg1正常打印,但foo()里的MyRect1执行同样操作会报错:
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory
两个结构体大小均为32字节,仅最后一个成员类型不同。
解决方法
方法1:直接打印LLDB解析好的变量
LLDB在栈帧信息中已经自动解析出了结构体的值(参考bt输出里的rect=(a = 1, b = 2, c = 3, d = 4)),直接打印变量名即可:
(lldb) p rect (MyRect1) $0 = (a = 1, b = 2, c = 3, d = 4)
针对-[NSView setNeedsDisplayInRect:],直接执行:
(lldb) p rect
方法2:手动从浮点寄存器拼接结构体
对于全double成员的结构体,ARM64 ABI会直接用浮点寄存器传递(四个double对应d0-d3寄存器),可以手动拼接结构体:
(lldb) expr MyRect1 {.a = $d0, .b = $d1, .c = $d2, .d = $d3} (MyRect1) $1 = (a = 1, b = 2, c = 3, d = 4)
对于NSRect(结构为{NSPoint origin; NSSize size;}),可以用:
(lldb) expr NSRect {.origin.x = $d0, .origin.y = $d1, .size.width = $d2, .size.height = $d3}
问题原因说明
ARM64 ABI对全浮点成员的结构体有特殊处理规则:当结构体所有成员都是浮点类型(如double)且大小不超过8个浮点寄存器(64字节)时,会直接用浮点寄存器传递参数,而非放到内存再传递指针。MyRect1四个double刚好占4个浮点寄存器(d0-d3),因此$arg1并不是指向结构体的有效指针,自然无法通过指针解引用读取。而MyRect2包含整数成员,不符合全浮点条件,因此采用内存+指针的传递方式,$arg1是有效指针。
内容的提问来源于stack exchange,提问作者user11253534

