5

Tôi đang thực hiện một "Mã Injector Class", mà thông qua phương pháp swizzling có thể cung cấp cho bạn khả năng để làm điều gì đó như thế này:Swizzling một phương pháp với đối số thay đổi và chuyển tiếp tin nhắn - Bad Truy cập

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

aSelector có thể là một phương thức có số lượng đối số biến và kiểu trả về biến. Đối số/và kiểu trả về có thể là các đối tượng hoặc kiểu nguyên thủy.

Trước tiên, tôi đính kèm mã của injectCodeBeforeSelector: để cho bạn hiểu những gì tôi đang làm (Tôi đã gỡ bỏ không phần thú vị của mã):

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

Về cơ bản, tôi sử dụng class_addMethod để thêm giả/sự gian lận phương pháp đến lớp đích, sau đó thực hiện sự lộn xộn. Việc thực hiện phương pháp này được thiết lập để một chức năng như thế này:

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

bây giờ vấn đề: Tôi có một EXC_BAD_INSTRUCTION (objc_msgSend_corrupt_cache_error()) trên dòng cuối cùng. Sự cố xảy ra nếu tôi chuyển tiếp đối số va_list tới bộ chọn giả. Nếu tôi thay đổi dòng cuối cùng thành

return objc_msgSend(self, cmd, arguments); 

không có lỗi, nhưng rõ ràng là một đệ quy vô hạn bắt đầu.

Tôi đã cố gắng để:

  • sử dụng va_copy
  • loại bỏ các sự gian lận trước khi gửi thông điệp

nhưng không có kết quả. Tôi nghĩ rằng vấn đề có liên quan đến thực tế này: va_list không phải là một con trỏ đơn giản, nó có thể là một cái gì đó tương tự như một bù đắp tương đối với địa chỉ ngăn xếp của phương pháp. Vì vậy, tôi không thể gọi objc_msgsend của một hàm (hàm swizzled) với danh sách arg của một hàm khác (cái không bị quấy rối).

Tôi đã cố gắng thay đổi cách tiếp cận và sao chép tất cả đối số trong NSInvocation, nhưng tôi đã gặp phải các vấn đề khác quản lý giá trị trả về của lời gọi và thậm chí sao chép từng đối số (quản lý tất cả các loại khác nhau). Tôi thích quay lại phương pháp này, điều đó có vẻ sạch hơn đối với tôi (imho)

Bạn có gợi ý gì không? Cảm ơn

Trả lời

3

vấn đề chính ở đây là như thế nào biến các đối số được truyền cho hàm.

Thông thường, chúng được chuyển vào ngăn xếp, nhưng theo như tôi biết, nó không phải là trường hợp với ARM ABI, nơi đăng ký được sử dụng, ít nhất là khi có thể.

Vì vậy, bạn có hai vấn đề ở đây.
Đầu tiên, trình biên dịch có thể làm hỏng các thanh ghi đó, trong khi thực thi mã của phương thức của riêng bạn.
Tôi không chắc chắn về điều này, vì tôi không biết nhiều về ARM ABI, vì vậy bạn nên kiểm tra trong tài liệu tham khảo.

Vấn đề thứ hai, quan trọng hơn, bạn đang thực sự chuyển một đối số biến duy nhất tới obj_msgSend (số va_list). Vì vậy, phương pháp mục tiêu sẽ không nhận được những gì nó mong đợi, rõ ràng.

Hãy tưởng tượng như sau:

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

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

On ARM, điều này sẽ có nghĩa là, cho foo chức năng:

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 

biến đối số được truyền trong R1, R2R3, và int lập luận trong R0.

Vì vậy, trong trường hợp của bạn, như một lời kêu gọi objc_msdSend được sử dụng để gọi phương thức của bạn, R0 nên con trỏ đối tượng mục tiêu, R1 con trỏ của selector, và đối số biến nên bắt đầu từ ngày R2.

Và khi thực hiện cuộc gọi của riêng bạn tới objc_msdSend, ít nhất bạn sẽ ghi đè nội dung của R2, với số va_list.

Bạn nên cố gắng không quan tâm đến các đối số biến. Với một chút may mắn, nếu mã trước cuộc gọi objc_msgSend (nơi bạn nhận được bộ chọn cuối cùng) không làm hỏng các thanh ghi đó, các giá trị chính xác sẽ vẫn ở đó, làm cho chúng có sẵn cho phương pháp đích.

Điều này tất nhiên chỉ hoạt động trên thiết bị thực và không có trong trình mô phỏng (trình mô phỏng là x86, vì vậy các tham số variadic ở đây được truyền trên ngăn xếp).

+1

cảm ơn cả hai, cả hai câu trả lời rất hữu ích, tôi sẽ đánh dấu điều này là chính xác vì có một lời giải thích cộng với một ví dụ thú vị. Vì những câu trả lời này, tôi đã quyết định thử lại với NSInvocation, tôi sẽ đăng một câu hỏi khác về nó. Cảm ơn một lần nữa – LombaX

+1

Nếu có ai quan tâm, đây là liên kết cho phiên bản đầu tiên của lớp trên github: https://github.com/lombax85/FLCodeInjector – LombaX

1

Thật không may, không. Đây là lý do tại sao các hàm lấy đối số biến sẽ cung cấp các triển khai giống hệt nhau, mất va_list s. Các API cung cấp chức năng này (ví dụ: objc_msgSendv) đã được đánh dấu là 'Không khả dụng' kể từ ObjC 2. Có lẽ cũng nên tìm ra lý do tại sao chức năng này bị xóa.

Variadic Macros thường giải quyết được vấn đề, nhưng không phải trong trường hợp của bạn bởi vì bạn cần một con trỏ hàm để sự gian lận.

vì vậy, tôi nghĩ rằng bạn sẽ cần phải xem xét để thực hiện hệ thống của bạn để hiểu được cơ chế của Danh mục VA vào triển khai nhắm mục tiêu của bạn.

Các vấn đề liên quan