为了调试一个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;
}
Procedure Call Standard for the ARM 64-bit Architecture (AArch64)声明
如果参数类型是大于16个字节的复合类型,则将参数复制到调用方分配的内存中,并将参数替换为指向副本的指针。
因此,我希望foo()
和bar()
都是用指向结构的指针作为单个参数来调用的。在bar()
中设置断点并将第一个参数转换为MyRect2 *
确实会产生预期的结果:
(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)
但是,这不适用于foo()
中的MyRect1
:
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
请注意,两个结构的大小相同(32个字节),只有最后一个成员不同。
- 问:* 如果传递给lldb中的函数的参数是
NSRect
类型(或任何其他四个双精度型的结构),我如何打印该参数?
1条答案
按热度按时间laximzn51#
在结构中将double转换为long实际上是很重要的。一个包含四个double的结构是一个同构浮点聚合,而包含三个double和一个long的结构是一个复合类型,它们有不同的传递规则。前者在浮点寄存器中传递,如果合适的话(正如你所发现的),而后者在堆栈上传递。
顺便说一句,如果你想读取传递给一个没有调试信息的函数的值,最好在“函数序言”执行之前停止。函数序言(特别是在优化代码中)经常会从它们的原始位置复制寄存器,并重用它们,所以序言之后的寄存器状态将不再反映调用约定。
然而,函数的调试信息通常在序言中是不准确的。没有技术上的原因为什么不能这样,但是我知道没有编译器跟踪序言,除非你实际上是在调试编译器的输出,否则代码的那个区域并不是很有趣。所以对于有调试信息的代码,在序言执行后停止会更方便,这是lldb的默认设置。
要切换该默认值,请将
--skip-prologue 0
添加到break set
命令中。