5

Jestem wykonawczych „kod wtryskiwacza Class”, który dzięki metodzie swizzling może dać Ci możliwość zrobienia czegoś takiego:Swizzling metody ze zmiennymi argumentami i przekazać wiadomość - Bad dostęp

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

aSelector może być metodą ze zmienną liczbą argumentów i zmiennym typem zwracanym. Argumenty/i typ zwracany mogą być obiektami lub typem pierwotnym.

pierwsze, załączam kod injectCodeBeforeSelector: pozwolić Ci zrozumieć, co robię (nie usuwa interesujących części kodu):

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

Zasadniczo używam class_addMethod dodać fałszywy/swizzle do klasy docelowej, a następnie wykonaj zawroty. Wdrożenie metody jest ustawiony na funkcję tak:

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

Teraz problem: Mam EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error()) na ostatniej linii. Problem występuje, jeśli przekazuję argumenty va_list do fałszywego selektora. Jeśli zmienię ostatnią linię do

return objc_msgSend(self, cmd, arguments); 

żaden błąd, ale oczywiście nieskończona rozpoczyna rekursji.

Próbowałem:

  • użytku va_copy
  • usunąć swizzle przed wysłaniem wiadomości

ale bez rezultatów. Myślę, że problem jest związany z tym faktem: va_list nie jest prostym wskaźnikiem, może być czymś podobnym do offsetu względem adresu stosu metody. Tak więc, nie mogę nazwać obiektu objc_msgsend (funkcja swizzled) z listą argumentów innej funkcji (ta, która nie jest przekierowana).

Próbowałem zmienić podejście i skopiować wszystkie argumenty w NSInvocation, ale miałem inne problemy z zarządzaniem zwracaną wartością wywołania, a nawet kopiowanie argumentów jedna po drugiej (zarządzanie wszystkimi różnymi typami) wymaga dużej ilości kodu, więc Wolałem wrócić do tego podejścia, które wydaje mi się bardziej czyste (imho)

Czy masz jakieś sugestie? Dzięki

Odpowiedz

3

głównym problemem jest to, jak zmienna argumenty są przekazywane do funkcji.

Zwykle są one przekazywane na stosie, ale z tego co wiem, nie ma to miejsca w przypadku ARM ABI, w którym używane są rejestry, przynajmniej wtedy, gdy jest to możliwe.

Masz tu dwa problemy.
Po pierwsze, kompilator może zepsuć te rejestry, wykonując kod własnej metody.
Nie jestem tego pewien, ponieważ nie znam się na ABM ABI, więc powinieneś sprawdzić w referencji.

Po drugie, ważniejsze jest, że podajesz pojedynczą argument zmiennej do obj_msgSend (va_list). Tak więc metoda docelowa nie otrzyma oczywiście tego, czego oczekuje.

Wyobraźmy sobie następujący:

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

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

Na ARM, będzie to oznaczać, dla funkcji 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 

Zmienne argumenty są przekazywane w R1, R2 i R3 oraz int argument R0.

Więc w twoim przypadku, jako wezwanie do objc_msdSend użyto wywołać metodę, R0 powinien być wskaźnik obiektu docelowego, R1 wskaźnik selektora, a zmienne argumenty powinny rozpocząć się na R2.

Podczas wysyłania własnego połączenia z numerem objc_msdSend co najmniej zastępujesz zawartość R2, używając swojego numeru va_list.

Powinieneś starać się nie przejmować zmiennymi argumentami. Przy odrobinie szczęścia, jeśli kod poprzedzający wywołanie objc_msgSend (gdzie otrzymasz ostateczny selektor) nie zepsuł tych rejestrów, prawidłowe wartości powinny nadal tam być, czyniąc je dostępnymi dla metody docelowej.

To oczywiście działa tylko na prawdziwym urządzeniu, a nie na symulatorze (symulator to x86, więc parametry wariasu są tutaj przekazywane na stosie).

+1

dziękuję wam obojgu, obaj odpowiadają bardzo pożytecznym, ja będę oznaczyć ten jeden poprawny ponieważ jest objaśnienie plus ciekawy przykład. Z powodu tych odpowiedzi zdecydowałem się spróbować ponownie przy pomocy NSInvocation, opublikuję kolejne pytanie na ten temat. Jeszcze raz dziękuję – LombaX

+1

Jeśli ktoś jest zainteresowany, oto link do pierwszej wersji klasy na github: https://github.com/lombax85/FLCodeInjector – LombaX

1

Niestety, nie. Dlatego funkcje, które pobierają zmienne argumenty, powinny zapewniać identyczne implementacje, które wymagają va_list s. API, które zapewniły tej funkcji (np objc_msgSendv) zostały oznaczone jako „niedostępne” od ObjC 2. Może to też być dobry, aby dowiedzieć się, dlaczego ta funkcja została usunięta.

Variadic Macros często rozwiązać ten problem, ale nie w przypadku bo trzeba wskaźnik funkcji do swizzle.

Więc myślę, że trzeba spojrzeć do wdrożenia systemu, aby zrozumieć mechanikę listy VA na docelowych wdrożeń.

Powiązane problemy