5

Sto l'attuazione di un "Codice iniettore Class", che attraverso il metodo swizzling può darvi la possibilità di fare qualcosa di simile:Swizzling un metodo con argomenti variabili e inoltrare il messaggio - Bad Accesso

FLCodeInjector *injector = [FLCodeInjector injectorForClass:[self class]]; 
[injector injectCodeBeforeSelector:@selector(aSelector:) code:^{ 
    NSLog(@"This code should be injected"); 
}]; 

aSelector può essere un metodo con numero variabile di argomenti e tipo di ritorno variabile. Gli argomenti/e il tipo restituito possono essere oggetti o tipi primitivi.

In primo luogo, ho allegare il codice di injectCodeBeforeSelector: per farvi capire quello che sto facendo (non rimosso parti interessanti del codice):

- (void)injectCodeBeforeSelector:(SEL)method code:(void (^)())completionBlock 
{ 

    NSString *selector = NSStringFromSelector(method); 

    [self.dictionaryOfBlocks setObject:completionBlock forKey:selector]; 

    NSString *swizzleSelector = [NSString stringWithFormat:@"SWZ%@", selector]; 

    // add a new method to the swizzled class 
    Method origMethod = class_getInstanceMethod(self.mainClass, NSSelectorFromString(selector)); 
    const char *encoding = method_getTypeEncoding(origMethod); 

    [self addSelector:NSSelectorFromString(swizzleSelector) toClass:self.mainClass methodTypeEncoding:encoding]; 
    SwizzleMe(self.mainClass, NSSelectorFromString(selector), NSSelectorFromString(swizzleSelector)); 

} 

-(void)addSelector:(SEL)selector toClass:(Class)aClass methodTypeEncoding:(const char *)encoding 
{ 
    class_addMethod(aClass, 
        selector, 
        (IMP)genericFunction, encoding); 
} 

Fondamentalmente, io uso class_addMethod di aggiungere il falso/swizzle metodo per la classe di destinazione, quindi fare il swizzle. L'implementazione del metodo è impostato su una funzione come questa:

id genericFunction(id self, SEL cmd, ...) { 
    // call the block to inject 
    ... 
    // now forward the message to the right method, since method are swizzled 
    // I need to forward to the "fake" selector SWZxxx 

    NSString *actualSelector = NSStringFromSelector(cmd); 
    NSString *newSelector = [NSString stringWithFormat:@"SWZ%@", actualSelector]; 
    SEL finalSelector = NSSelectorFromString(newSelector); 

    // forward the argument list 
    va_list arguments; 
    va_start (arguments, cmd); 

    return objc_msgSend(self, finalSelector, arguments); 
} 

ora il problema: ho un EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error()) nell'ultima riga. Il problema si verifica se inoltro gli argomenti va_list al selettore falso. Se cambio l'ultima linea di

return objc_msgSend(self, cmd, arguments); 

nessun errore, ma ovviamente un infinito inizia ricorsione.

Ho cercato di:

  • uso va_copy
  • rimuovere lo swizzle prima di inviare il messaggio

ma senza risultati. Penso che il problema sia legato a questo fatto: va_list non è un semplice puntatore, può essere qualcosa di simile a un offset relativo all'indirizzo dello stack del metodo. Quindi, non posso chiamare objc_msgsend di una funzione (la funzione swizzled) con la lista arg di un'altra funzione (quella non swizzled).

Ho provato a cambiare approccio e copiare tutti gli argomenti in un NSInvocation, ma ho avuto altri problemi nella gestione del valore di ritorno dell'invocazione, e anche copiare gli argomenti uno per uno (gestendo tutti i diversi tipi) richiede un sacco di codice, quindi Ho preferito tornare a questo approccio, mi sembra più pulito (imho)

Hai qualche suggerimento? Grazie

risposta

3

il problema principale qui è come la variabile gli argomenti vengono passati alla funzione.

Di solito, vengono passati in pila, ma per quanto ne so, non è il caso dell'ARM ABI, dove vengono utilizzati i registri, almeno quando possibile.

Quindi hai due problemi qui.
In primo luogo, il compilatore può rovinare quei registri, mentre esegue il codice del proprio metodo.
Non ne sono sicuro, poiché non conosco molto l'ARM ABI, quindi dovresti controllare il riferimento.

Secondo problema, ancora più importante, si passa effettivamente un singolo argomento variabile a obj_msgSend (va_list). Quindi il metodo target non riceverà ciò che si aspetta, ovviamente.

Immaginate la seguente:

void bar(int x, ...) 
{} 

void foo(void) 
{ 
    bar(1, 2, 3, 4); 
} 

Su ARM, questo significherà, per la funzione foo:

movs r0, #1 
movt r0, #0 
movs r1, #2 
movt r1, #0 
movs r2, #3 
movt r2, #0 
movs r3, #4 
movt r3, #0 
bl  _bar 

Variabili argomenti vengono passati in R1, R2 e R3, e il int argomento R0.

Quindi nel tuo caso, come una chiamata a objc_msdSend è stato utilizzato per chiamare il metodo, dovrebbe essere R0 puntatore dell'oggetto target, R1 puntatore del selettore, e argomenti variabili dovrebbe iniziare il R2.

E quando si effettua la propria chiamata a objc_msdSend, si sta almeno sovrascrivendo il contenuto di R2, con il numero va_list.

Si dovrebbe cercare di non preoccuparsi degli argomenti variabili. Con un po 'di fortuna, se il codice precedente alla chiamata objc_msgSend (dove si ottiene il selettore finale) non rovina quei registri, i valori corretti dovrebbero essere ancora lì, rendendoli disponibili per il metodo di destinazione.

Questo ovviamente funziona solo sul dispositivo reale e non nel simulatore (il simulatore è x86, quindi i parametri variadici sono passati in pila).

+1

grazie a entrambi, entrambi rispondono molto utile, contrassegnerò questo come corretto perché c'è una spiegazione più un esempio interessante. A causa di queste risposte, ho deciso di riprovare con NSInvocation, pubblicherò un'altra domanda al riguardo. Grazie ancora – LombaX

+1

Se qualcuno è interessato, ecco un link per la prima versione della classe su github: https://github.com/lombax85/FLCodeInjector – LombaX

1

Sfortunatamente, no. Questo è il motivo per cui le funzioni che accettano argomenti variabili dovrebbero fornire implementazioni identiche che prendono va_list s. API che hanno fornito questa funzionalità (ad esempio objc_msgSendv) sono state contrassegnate 'non disponibile" in quanto objC 2. Forse sarebbe anche bene a capire perché questa funzionalità è stata rimossa.

Variadic Macros sarebbe spesso risolvere il problema, ma non nel tuo caso perché avete bisogno di un puntatore a funzione a Swizzle.

Quindi penso che hai bisogno di guardare al vostro implementazione del sistema per capire i meccanismi della Lista VA sulle implementazioni mirati.

Problemi correlati