1

I have a strange problem with setting the argument of an NSInvocation with a struct that contains a double or any 64 bit type which is not aligned (I offset it with a char at the beginning of the struct). The problem is that some bytes are cleared after the argument is set. This problem occurs on ARM7 but not in the iOS simulator.

I'm using LLVM 3.0 and Xcode 4.2

Here is my code and test results:

NSInvocation+Extension.h

@interface NSInvocation (Extension)

+ (NSInvocation*) invocationWithTarget: (id)aTarget
                              selector: (SEL)aSelector
                       retainArguments: (BOOL)aRetainArguments, ...;

- (void) setArguments: (va_list)aArgList;
- (void) setArguments: (va_list)aArgList atIndex: (NSInteger)aIndex;

@end    // NSInvocation (Extension)

NSInvocation+Extension.m

#import <objc/runtime.h>

#import "NSInvocation+Extension.h"


@implementation NSInvocation (Extension)

+ (NSInvocation*) invocationWithTarget: (id)aTarget
                              selector: (SEL)aSelector
                       retainArguments: (BOOL)aRetainArguments, ...
{
    NSMethodSignature* signature = [aTarget methodSignatureForSelector: aSelector];
    NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: signature];

    if (aRetainArguments)
    {
        [invocation retainArguments];
    }
    [invocation setTarget: aTarget];
    [invocation setSelector: aSelector];

    va_list argList;
    va_start(argList, aRetainArguments);
    [invocation setArguments: argList];
    va_end(argList);

    return invocation;
}

- (void) setArguments: (va_list)aArgList
{
    [self setArguments: aArgList atIndex: 0];
}

- (void) setArguments: (va_list)aArgList atIndex: (NSInteger)aIndex
{
    // Arguments are aligned on machine word boundaries
    const NSUInteger KOffset = sizeof(size_t) - 1;

    UInt8* argPtr = (UInt8*)aArgList;
    NSMethodSignature* signature = [self methodSignature];

    // Indices 0 and 1 indicate the hidden arguments self and _cmd respectively.
    for (int index = aIndex + 2; index < [signature numberOfArguments]; ++index)
    {
        const char* type = [signature getArgumentTypeAtIndex: index];
        NSUInteger size = 0;
        NSGetSizeAndAlignment(type, &size, NULL);
        [self setArgument: argPtr atIndex: index];
        argPtr += (size + KOffset) & ~KOffset;
    }
}

@end  // NSInvocation (Extension)

Declare method to invoke and a data struct

- (void) arg1: (char)aArg1 arg2: (char)aArg2 arg3: (TEST)aArg3 arg4: (char)aArg4;

typedef struct test {
    char c;
    double s;
    char t;
    void* b;
    char tu;
} TEST;

Calling code

TEST df = { 'A', 12345678.0, 'B', (void*)2, 'C' }; 

char buf[100] = {0};

NSInvocation* ik = [NSInvocation invocationWithTarget: self selector: @selector(arg1:arg2:arg3:arg4:) retainArguments: NO, '1', '2', df, '3'];
[ik getArgument: &buf atIndex: 4];

Contents of buf on ARM7 (bytes 8, 9, 10 and 11 set to zero which messed up the double value)

41 00 00 00 00 00 00 00 29 8C 67 41 42 00 00 00 02 00 00 00 43 00 00 00

Contents of buf on i386 simulator (as expected)

41 00 00 00 00 00 00 C0 29 8C 67 41 42 00 00 00 02 00 00 00 43 00 00 00


Sam
  • 616
  • 7
  • 19

1 Answers1

2

First thought is that you really must use va_arg to access successive arguments in a variadic argument list. There is no way you can just assume that the arguments are arranged in a nice contiguous piece of memory as you do. For one thing, the ARM ABI says the first four arguments are passed in registers.

A va_list need not necessarily be just a pointer, it's an opaque type. Your cast to uint8_t* is almost certainly invalid.

JeremyP
  • 84,577
  • 15
  • 123
  • 161