5

ich einen „Code-Injektor Klasse“ bin Umsetzung, dass durch Verfahren Swizzling kann Ihnen die Möglichkeit, so etwas zu tun:Swizzling ein Verfahren mit variablen Argumenten und die Nachricht weiterleiten - Bad Zugang

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

aSelector kann eine Methode mit einer variablen Anzahl von Argumenten und einem variablen Rückgabetyp sein. Argumente/und Rückgabetyp können Objekte oder primitive Typen sein.

Zuerst lege ich den Code von injectCodeBeforeSelector:, damit Sie verstehen, was ich tue (ich nicht interessant Teile des Codes entfernt):

- (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); 
} 

Grundsätzlich verwende ich class_addMethod die gefälschte hinzufügen/swizzle Methode zur Zielklasse, dann swizzle. Die Durchführung des Verfahrens ist mit einer Funktion wie folgt festgelegt:

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); 
} 

jetzt das Problem: Ich habe eine EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error()) in der letzten Zeile. Das Problem tritt auf, wenn ich die Argumente va_list an den falschen Selektor weiterleiten. Wenn ich ändern die letzte Zeile zu

return objc_msgSend(self, cmd, arguments); 

kein Fehler, aber offensichtlich eine unendliche Rekursion beginnt.

Ich habe versucht, zu:

  • Verwendung va_copy
  • die swizzle entfernen, bevor die Nachricht

aber keine Ergebnisse zu senden. Ich denke, dass das Problem mit dieser Tatsache verbunden ist: va_list ist kein einfacher Zeiger, es kann etwas sein, das einem Offset relativ zur Stapeladresse der Methode ähnlich ist. Also kann ich objc_msgsend einer Funktion (die swizzled-Funktion) nicht mit der Arg-Liste einer anderen Funktion aufrufen (die nicht swizzed).

Ich versuchte Ansatz zu ändern und alle Argumente in einer NSInvocation zu kopieren, aber ich hatte andere Probleme, den Rückgabewert des Aufrufs zu verwalten, und sogar Argumente nacheinander kopieren (alle unterschiedlichen Typen verwalten) erfordert viel Code Ich zog es vor, zu diesem Ansatz zurückzukehren, der mir sauberer scheint (imho)

Haben Sie einen Vorschlag? Danke

Antwort

3

das Hauptproblem hier ist, wie die Variable Argumente werden an die Funktion übergeben.

Normalerweise werden sie auf dem Stack übergeben, aber soweit ich weiß, ist es bei ARM ABI nicht der Fall, wo Register verwendet werden, zumindest wenn es möglich ist.

Sie haben also zwei Probleme hier.
Erstens kann der Compiler diese Register durcheinander bringen, während er den Code Ihrer eigenen Methode ausführt.
Ich bin mir nicht sicher, da ich nicht viel über die ARM ABI weiß, also sollten Sie in der Referenz einchecken.

Das zweite Problem, wichtiger, übergeben Sie tatsächlich ein einzelnes Argument der Variablen obj_msgSend (va_list). Die Zielmethode wird also offensichtlich nicht das empfangen, was sie erwartet.

Stellen Sie sich folgendes:

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

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

Auf ARM, bedeutet dies für die foo Funktion:

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 

Variablen Argumente übergeben werden in R1, R2 und R3 und dem int Argument R0.

Also in Ihrem Fall, wie ein Aufruf an objc_msdSend verwendet wurde Ihre Methode aufrufen, sollte R0 der Zielzeiger des Objekts sein R1 der Zeiger Wähler und variable Argumente auf R2 beginnen soll.

Und wenn Sie Ihren eigenen Anruf an objc_msdSend abgeben, überschreiben Sie mindestens den Inhalt von R2 mit Ihrem va_list.

Sie sollten versuchen, sich nicht um die variablen Argumente zu kümmern. Mit ein wenig Glück, wenn der Code vor dem objc_msgSend Aufruf (wo Sie den endgültigen Selektor erhalten) diese Register nicht durcheinander bringt, sollten die korrekten Werte immer noch da sein und sie für die Zielmethode verfügbar machen.

Das würde natürlich nur auf dem realen Gerät funktionieren, und nicht im Simulator (Simulator ist x86, also werden variadische Parameter hier auf dem Stack übergeben).

+1

danke beide, beide antworten sehr nützlich, ich werde diese als richtig markieren, weil es eine Erklärung plus ein interessantes Beispiel gibt. Aufgrund dieser Antworten habe ich mich dazu entschieden, mit NSInvocation erneut zu versuchen. Ich werde eine weitere Frage dazu stellen. Nochmals vielen Dank – LombaX

+1

Wenn jemand interessiert ist, hier ist ein Link für die erste Version der Klasse auf GitHub: https://github.com/lombax85/FLCodeInjector – LombaX

1

Leider nein. Aus diesem Grund sollten Funktionen, die variable Argumente annehmen, identische Implementierungen bereitstellen, die va_lists annehmen. APIs, die diese Funktionalität (zB objc_msgSendv) zur Verfügung gestellt hat Aufschrift ‚nicht verfügbar“ seit ObjC 2. Vielleicht auch gut aus, es würde auf Figur warum diese Funktionalität wurde entfernt.

Variadic Macros würde das Problem, aber nicht in Ihrem Fall oft lösen weil Sie einen Funktionszeiger auf swizzle benötigen.

Also ich denke, Sie zu Ihrer System-Implementierung aussehen müssen, um die Mechanik der VA-Liste auf Ihren gezielten Implementierungen zu verstehen.