debugging 正在打印传递给lldb中可可方法的'NSRect'参数

i2byvkas  于 2022-11-14  发布在  其他
关注(0)|答案(1)|浏览(155)

为了调试一个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类型(或任何其他四个双精度型的结构),我如何打印该参数?
laximzn5

laximzn51#

在结构中将double转换为long实际上是很重要的。一个包含四个double的结构是一个同构浮点聚合,而包含三个double和一个long的结构是一个复合类型,它们有不同的传递规则。前者在浮点寄存器中传递,如果合适的话(正如你所发现的),而后者在堆栈上传递。
顺便说一句,如果你想读取传递给一个没有调试信息的函数的值,最好在“函数序言”执行之前停止。函数序言(特别是在优化代码中)经常会从它们的原始位置复制寄存器,并重用它们,所以序言之后的寄存器状态将不再反映调用约定。
然而,函数的调试信息通常在序言中是不准确的。没有技术上的原因为什么不能这样,但是我知道没有编译器跟踪序言,除非你实际上是在调试编译器的输出,否则代码的那个区域并不是很有趣。所以对于有调试信息的代码,在序言执行后停止会更方便,这是lldb的默认设置。
要切换该默认值,请将--skip-prologue 0添加到break set命令中。

相关问题