Есть ли способ перечислить все методы Swizzled в приложении для iOS?

По сути, я ищу способ определить, когда / какие сторонние библиотеки переполнены. Недавно я столкнулся с ситуацией, когда в рекламной библиотеке использовался странный форк AFNetworking. AFNetworking кружит NSURLSessionTask, и эти два кружка не играли хорошо при определенных обстоятельствах. Мне бы очень хотелось иметь возможность обнаруживать и проверять подобные вещи, а в идеале даже сохранять версионный дамп каждого изворотливого метода в приложении, чтобы у нас было некоторое представление о том, кто из мартышек исправляет, что и каковы риски. Google и поиск переполнения стека не нашли ничего, кроме как кучу уроков о том, как быстро справиться. Кто-нибудь сталкивался с этой проблемой или есть решение? Похоже, я мог бы что-то кодировать с помощью objc/runtime.h, но я не могу себе представить, что я первый, кому это нужно.

1 ответ

Решение

Вот самое близкое, что мне удалось получить, с несколькими часами переделок. Он включает в себя использование форка mach_override, пару изысков DYLD относительно порядка загрузки и желудок для сумасшедших хаков.

Он будет работать только на симуляторе, но этого должно быть достаточно для варианта использования, который у вас есть (я, конечно, надеюсь, что у вас нет зависимостей только от устройства).

Мясо кода выглядит примерно так:

#include <objc/runtime.h>
#include <mach_override/mach_override.h>

// It is extremely important that we have DYLD run this constructor as soon as the binary loads. If we were to hook
// this at any other point (for example, another category on NSObject, in the main application), what could potentially
// happen is other `+load` implementations get invoked before we have a chance to hook `method_exchangeImplementation`,
// and we don't get to see those swizzles.
// It is also extremely important that this exists inside its own dylib, which will be loaded by DYLD before _main() is
// initialized. You must also make sure that this gets loaded BEFORE any other userland dylibs, which can be enforced by
// looking at the order of the 'link binary with libraries' phase.
__attribute__((constructor))
static void _hook_objc_runtime() {
  kern_return_t err;
  MACH_OVERRIDE(void, method_exchangeImplementations, (Method m1, Method m2), &err) {
    printf("Exchanging implementations for method %s and %s.\n", sel_getName(method_getName(m1)), sel_getName(method_getName(m2)));

    method_exchangeImplementations_reenter(m1, m2);
  }
  END_MACH_OVERRIDE(method_exchangeImplementations);

  MACH_OVERRIDE(void, method_setImplementation, (Method method, IMP imp), &err) {
    printf("Setting new implementation for method %s.\n", sel_getName(method_getName(method)));

    method_setImplementation_reenter(method, imp);
  }
  END_MACH_OVERRIDE(method_setImplementation);
}

Который на удивление прост и выдает результат примерно так:

Обмен реализациями для описания метода и custom_description.

Нет хорошего способа (без использования точки останова и просмотра трассировки стека) выяснить, какой класс подвергается Swizzling, но в большинстве случаев просто взглянуть на селекторы должны дать вам подсказку, куда идти дальше.

Похоже, что он работает с парой категорий, которые я создал +loadи из моего прочтения mach_override и внутренности DYLD, если у вас правильно настроен порядок загрузки библиотеки, вы можете ожидать, что он будет инициализирован раньше, чем любой другой код пользователя, если вы поместите его в свою собственную динамическую библиотеку.

Теперь я не могу ручаться за безопасность этого, но кажется полезным использовать его как инструмент отладки, поэтому я опубликовал мой пример на github:

https://github.com/richardjrossiii/mach_override_example

Это лицензия MIT, так что не стесняйтесь использовать по своему усмотрению.

Другие вопросы по тегам