use of undeclared identifier '$rdi'

I am on M1 (Xcode Version 15.4 (15F31d), MacOS 14.5 (23F79), Simulator IOS 17.4) and as far as I remember printing common usage register was possible. I am not sure why it stopped to work rid, rsi and etc (arg1 arg2 seems like still working).

Answered by DTS Engineer in 796436022

You have be careful here. While objc_msgSend is declared as a variadic function, it doesn’t follow variadic calling conventions. Rather, it’s a single function that you can call with any Objective-C method arguments. It only cares about id and SEL [1]. This isn’t something you can describe in C.

To see this in action, consider this:

@interface Test : NSObject
@end

@implementation Test

- (void)myMethodArg1:(NSInteger)arg1 arg2:(NSInteger)arg2 {
    NSLog(@"%zd", arg1);
    NSLog(@"%zd", arg2);
}

@end

static void callTestObj(Test * testObj) {
    [testObj myMethodArg1:42 arg2:666];
}

static void callPrintf(void) {
    printf("%zd %zd", (NSInteger) 42, (NSInteger) 666);
}

If you disassemble the callTestObj function you see this:

(lldb) disas -n callTestObj
…
    0x100003e24 <+40>: ldr    x1, [sp]
    0x100003e28 <+44>: ldur   x0, [x29, #-0x8]
    0x100003e2c <+48>: mov    x2, #0x2a                 ; =42 
    0x100003e30 <+52>: mov    x3, #0x29a                ; =666 
    0x100003e34 <+56>: bl     0x100003f20               ; objc_msgSend$myMethodArg1:arg2:
…

Note how 42 and 666 are passed in registers.

OTOH, the callPrintf code shows this:

(lldb) disas -n callPrintf
xxot`callPrintf:
…
    0x100003e5c <+12>: mov    x9, sp
    0x100003e60 <+16>: mov    x8, #0x2a                 ; =42 
    0x100003e64 <+20>: str    x8, [x9]
    0x100003e68 <+24>: mov    x8, #0x29a                ; =666 
    0x100003e6c <+28>: str    x8, [x9, #0x8]
    0x100003e70 <+32>: adrp   x0, 0
    0x100003e74 <+36>: add    x0, x0, #0xf58            ; "%zd %zd"
    0x100003e78 <+40>: bl     0x100003eac               ; symbol stub for: printf
…

At +12 you see x9 being set up as a frame pointer and then you see the 42 and 66 stored into that.

This is why, if you call objc_msgSend directly, you have to cast it to the correct function pointer before calling it. If you try to call it as a variadic function, arguments end up in the wrong place.


Some time arg1, arg2 doesnt work correctly for some frames

One thing to watch out for here is the function prologue. If you just set a normal breakpoint on the function then LLDB sets it after the prologue. It’s easy for that prologue to corrupt those argument registers. So, when debugging at the assembly level, it’s best to set your breakpoint on the first instruction of the function.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] And the function result, which can cause problems and is why you end up with the _stret and _fpret variants.

rdi and rsi are register names defined by the x86_64 architecture, but the arm64 architecture of Apple silicon doesn't use the same nomenclature. arm64 numerically identifies the registers as x1, x2, etc.

— Ed Ford,  DTS Engineer

@DTS Engineer

Thanks for you answer (I was pretty sure that was the reason but just wanted a confirmation.) If possible I would like to ask a follow up question. Some time arg1, arg2 doesnt work correctly for some frames: So for instance I have the following frame:

frame #0: 0x000000010587ef98 Pendo`-[PNDScreenManager mymethod:arg1:arg2:](self=0x000060000176b300, _cmd="PNDScreenManager mymethod:arg1:arg2:", dict=0 key/value pairs, window=0x000000010370a400, rootViewController=0x000000010450b350) at PNDScreenManager.m:313:9

and those are the registers:

register read
General Purpose Registers:
        x0 = 0x0000000000000002
        x1 = 0x000000000000000d
        x2 = 0x000060000029ed80
        x3 = 0x0000000000000000
        x4 = 0x000000010450b350
        x5 = 0x0000000000000000
        x6 = 0x0000000000000000
        x7 = 0x0000000000000000
        x8 = 0x0000000105b5b4df  "PNDScreenManager mymethod:arg1:arg2:"
        x9 = 0x0abc76522d81005b
       x10 = 0x000000010371f948
       x11 = 0x000000000000001f
       x12 = 0x000000010371f940
       x13 = 0x0000000000000000
      ...

arg1 , and arg2 will print x0 and x1 as u suggested BUT not self and SEL and so on with rest of the parameters(as I would like). (only x4 the last parameter can be spotted). My question is: What will be the accurate and easy alternative for arg1, arg2 and etc (as it seems like x1,x2 are not consistent in that sense )

Thanks
(if you could share any documentation about arm64 registers and how to work/debug with them will be appreciated )

What you're looking for is the calling convetion for Objective-C on ARM64. While the ARM64 calling convention specifies that the first 8 arguments are passed on the first 8 registers (x0 through x7), there's also a notable difference between x86_64 and arm64:

On x86_64, the compiler treats fixed and variadic parameters the same, placing parameters in registers first and only using the stack when no more registers are available. On arm64, the compiler always places variadic parameters on the stack, regardless of whether registers are available. If you implement a function with fixed parameters, but redeclare it with variadic parameters, the mismatch causes unexpected behavior at runtime.

Source: Addressing Architectural Differences in Your macOS Code.

So if a method accepts a variable list of parameters (being a variadic function), all arguments will be passed on the stack. An example of such a function is objc_msgSend.

Accepted Answer

You have be careful here. While objc_msgSend is declared as a variadic function, it doesn’t follow variadic calling conventions. Rather, it’s a single function that you can call with any Objective-C method arguments. It only cares about id and SEL [1]. This isn’t something you can describe in C.

To see this in action, consider this:

@interface Test : NSObject
@end

@implementation Test

- (void)myMethodArg1:(NSInteger)arg1 arg2:(NSInteger)arg2 {
    NSLog(@"%zd", arg1);
    NSLog(@"%zd", arg2);
}

@end

static void callTestObj(Test * testObj) {
    [testObj myMethodArg1:42 arg2:666];
}

static void callPrintf(void) {
    printf("%zd %zd", (NSInteger) 42, (NSInteger) 666);
}

If you disassemble the callTestObj function you see this:

(lldb) disas -n callTestObj
…
    0x100003e24 <+40>: ldr    x1, [sp]
    0x100003e28 <+44>: ldur   x0, [x29, #-0x8]
    0x100003e2c <+48>: mov    x2, #0x2a                 ; =42 
    0x100003e30 <+52>: mov    x3, #0x29a                ; =666 
    0x100003e34 <+56>: bl     0x100003f20               ; objc_msgSend$myMethodArg1:arg2:
…

Note how 42 and 666 are passed in registers.

OTOH, the callPrintf code shows this:

(lldb) disas -n callPrintf
xxot`callPrintf:
…
    0x100003e5c <+12>: mov    x9, sp
    0x100003e60 <+16>: mov    x8, #0x2a                 ; =42 
    0x100003e64 <+20>: str    x8, [x9]
    0x100003e68 <+24>: mov    x8, #0x29a                ; =666 
    0x100003e6c <+28>: str    x8, [x9, #0x8]
    0x100003e70 <+32>: adrp   x0, 0
    0x100003e74 <+36>: add    x0, x0, #0xf58            ; "%zd %zd"
    0x100003e78 <+40>: bl     0x100003eac               ; symbol stub for: printf
…

At +12 you see x9 being set up as a frame pointer and then you see the 42 and 66 stored into that.

This is why, if you call objc_msgSend directly, you have to cast it to the correct function pointer before calling it. If you try to call it as a variadic function, arguments end up in the wrong place.


Some time arg1, arg2 doesnt work correctly for some frames

One thing to watch out for here is the function prologue. If you just set a normal breakpoint on the function then LLDB sets it after the prologue. It’s easy for that prologue to corrupt those argument registers. So, when debugging at the assembly level, it’s best to set your breakpoint on the first instruction of the function.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] And the function result, which can cause problems and is why you end up with the _stret and _fpret variants.

use of undeclared identifier '$rdi'
 
 
Q