LKS_MethodTraceManager.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. #ifdef SHOULD_COMPILE_LOOKIN_SERVER
  2. //
  3. // LKS_MethodTraceManager.m
  4. // LookinServer
  5. //
  6. // Created by Li Kai on 2019/5/22.
  7. // https://lookin.work
  8. //
  9. #import "LKS_MethodTraceManager.h"
  10. #import <objc/message.h>
  11. #import <objc/runtime.h>
  12. #import "LKS_ConnectionManager.h"
  13. #import "LookinMethodTraceRecord.h"
  14. #import "LookinServerDefines.h"
  15. static NSString * const kActiveListKey_Class = @"class";
  16. static NSString * const kActiveListKey_Sels = @"sels";
  17. static NSArray<NSString *> *LKS_ArgumentsDescriptionsFromInvocation(NSInvocation *invocation) {
  18. NSMethodSignature *signature = [invocation methodSignature];
  19. NSUInteger argsCount = signature.numberOfArguments;
  20. NSArray<NSString *> *strings = [NSArray lookin_arrayWithCount:(argsCount - 2) block:^id(NSUInteger idx) {
  21. NSUInteger argIdx = idx + 2;
  22. const char *argType = [signature getArgumentTypeAtIndex:argIdx];
  23. ///TODO:v, *, , [array type], {name=type...}, (name=type...), bnum, ^type, ?
  24. // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100
  25. if (strcmp(argType, @encode(char)) == 0) {
  26. char charValue;
  27. [invocation getArgument:&charValue atIndex:argIdx];
  28. return [NSString stringWithFormat:@"%@", @(charValue)];
  29. } else if (strcmp(argType, @encode(int)) == 0) {
  30. int intValue;
  31. [invocation getArgument:&intValue atIndex:argIdx];
  32. if (intValue == INT_MAX) {
  33. return @"INT_MAX";
  34. } else if (intValue == INT_MIN) {
  35. return @"INT_MIN";
  36. } else {
  37. return [NSString stringWithFormat:@"%@", @(intValue)];
  38. }
  39. } else if (strcmp(argType, @encode(short)) == 0) {
  40. short shortValue;
  41. [invocation getArgument:&shortValue atIndex:argIdx];
  42. if (shortValue == SHRT_MAX) {
  43. return @"SHRT_MAX";
  44. } else if (shortValue == SHRT_MIN) {
  45. return @"SHRT_MIN";
  46. } else {
  47. return [NSString stringWithFormat:@"%@", @(shortValue)];
  48. }
  49. } else if (strcmp(argType, @encode(long)) == 0) {
  50. long longValue;
  51. [invocation getArgument:&longValue atIndex:argIdx];
  52. if (longValue == NSNotFound) {
  53. return @"NSNotFound";
  54. } else if (longValue == LONG_MAX) {
  55. return @"LONG_MAX";
  56. } else if (longValue == LONG_MIN) {
  57. return @"LONG_MAX";
  58. } else {
  59. return [NSString stringWithFormat:@"%@", @(longValue)];
  60. }
  61. } else if (strcmp(argType, @encode(long long)) == 0) {
  62. long long longLongValue;
  63. [invocation getArgument:&longLongValue atIndex:argIdx];
  64. if (longLongValue == LLONG_MAX) {
  65. return @"LLONG_MAX";
  66. } else if (longLongValue == LLONG_MIN) {
  67. return @"LLONG_MIN";
  68. } else {
  69. return [NSString stringWithFormat:@"%@", @(longLongValue)];
  70. }
  71. } else if (strcmp(argType, @encode(unsigned char)) == 0) {
  72. unsigned char ucharValue;
  73. [invocation getArgument:&ucharValue atIndex:argIdx];
  74. if (ucharValue == UCHAR_MAX) {
  75. return @"UCHAR_MAX";
  76. } else {
  77. return [NSString stringWithFormat:@"%@", @(ucharValue)];
  78. }
  79. } else if (strcmp(argType, @encode(unsigned int)) == 0) {
  80. unsigned int uintValue;
  81. [invocation getArgument:&uintValue atIndex:argIdx];
  82. if (uintValue == UINT_MAX) {
  83. return @"UINT_MAX";
  84. } else {
  85. return [NSString stringWithFormat:@"%@", @(uintValue)];
  86. }
  87. } else if (strcmp(argType, @encode(unsigned short)) == 0) {
  88. unsigned short ushortValue;
  89. [invocation getArgument:&ushortValue atIndex:argIdx];
  90. if (ushortValue == USHRT_MAX) {
  91. return @"USHRT_MAX";
  92. } else {
  93. return [NSString stringWithFormat:@"%@", @(ushortValue)];
  94. }
  95. } else if (strcmp(argType, @encode(unsigned long)) == 0) {
  96. unsigned long ulongValue;
  97. [invocation getArgument:&ulongValue atIndex:argIdx];
  98. if (ulongValue == ULONG_MAX) {
  99. return @"ULONG_MAX";
  100. } else {
  101. return [NSString stringWithFormat:@"%@", @(ulongValue)];
  102. }
  103. } else if (strcmp(argType, @encode(unsigned long long)) == 0) {
  104. unsigned long long ulongLongValue;
  105. [invocation getArgument:&ulongLongValue atIndex:argIdx];
  106. if (ulongLongValue == ULONG_LONG_MAX) {
  107. return @"ULONG_LONG_MAX";
  108. } else {
  109. return [NSString stringWithFormat:@"%@", @(ulongLongValue)];
  110. }
  111. } else if (strcmp(argType, @encode(float)) == 0) {
  112. float floatValue;
  113. [invocation getArgument:&floatValue atIndex:argIdx];
  114. if (floatValue == FLT_MAX) {
  115. return @"FLT_MAX";
  116. } else if (floatValue == FLT_MIN) {
  117. return @"FLT_MIN";
  118. } else {
  119. return [NSString stringWithFormat:@"%@", @(floatValue)];
  120. }
  121. } else if (strcmp(argType, @encode(double)) == 0) {
  122. double doubleValue;
  123. [invocation getArgument:&doubleValue atIndex:argIdx];
  124. if (doubleValue == DBL_MAX) {
  125. return @"DBL_MAX";
  126. } else if (doubleValue == DBL_MIN) {
  127. return @"DBL_MIN";
  128. } else {
  129. return [NSString stringWithFormat:@"%@", @(doubleValue)];
  130. }
  131. } else if (strcmp(argType, @encode(BOOL)) == 0) {
  132. BOOL boolValue;
  133. [invocation getArgument:&boolValue atIndex:argIdx];
  134. return boolValue ? @"YES" : @"NO";
  135. } else if (strcmp(argType, @encode(SEL)) == 0) {
  136. SEL selValue;
  137. [invocation getArgument:&selValue atIndex:argIdx];
  138. return [NSString stringWithFormat:@"SEL(%@)", NSStringFromSelector(selValue)];
  139. } else if (strcmp(argType, @encode(Class)) == 0) {
  140. Class classValue;
  141. [invocation getArgument:&classValue atIndex:argIdx];
  142. return [NSString stringWithFormat:@"<%@>", NSStringFromClass(classValue)];
  143. } else if (strcmp(argType, @encode(CGPoint)) == 0) {
  144. CGPoint targetValue;
  145. [invocation getArgument:&targetValue atIndex:argIdx];
  146. return NSStringFromCGPoint(targetValue);
  147. } else if (strcmp(argType, @encode(CGVector)) == 0) {
  148. CGVector targetValue;
  149. [invocation getArgument:&targetValue atIndex:argIdx];
  150. return NSStringFromCGVector(targetValue);
  151. } else if (strcmp(argType, @encode(CGSize)) == 0) {
  152. CGSize targetValue;
  153. [invocation getArgument:&targetValue atIndex:argIdx];
  154. return NSStringFromCGSize(targetValue);
  155. } else if (strcmp(argType, @encode(CGRect)) == 0) {
  156. CGRect targetValue;
  157. [invocation getArgument:&targetValue atIndex:argIdx];
  158. return NSStringFromCGRect(targetValue);
  159. } else if (strcmp(argType, @encode(CGAffineTransform)) == 0) {
  160. CGAffineTransform targetValue;
  161. [invocation getArgument:&targetValue atIndex:argIdx];
  162. return NSStringFromCGAffineTransform(targetValue);
  163. } else if (strcmp(argType, @encode(UIEdgeInsets)) == 0) {
  164. UIEdgeInsets targetValue;
  165. [invocation getArgument:&targetValue atIndex:argIdx];
  166. return NSStringFromUIEdgeInsets(targetValue);
  167. } else if (strcmp(argType, @encode(UIOffset)) == 0) {
  168. UIOffset targetValue;
  169. [invocation getArgument:&targetValue atIndex:argIdx];
  170. return NSStringFromUIOffset(targetValue);
  171. } else if (strcmp(argType, @encode(NSRange)) == 0) {
  172. NSRange targetValue;
  173. [invocation getArgument:&targetValue atIndex:argIdx];
  174. return NSStringFromRange(targetValue);
  175. } else {
  176. if (@available(iOS 11.0, tvOS 11.0, *)) {
  177. if (strcmp(argType, @encode(NSDirectionalEdgeInsets)) == 0) {
  178. NSDirectionalEdgeInsets targetValue;
  179. [invocation getArgument:&targetValue atIndex:argIdx];
  180. return NSStringFromDirectionalEdgeInsets(targetValue);
  181. }
  182. }
  183. NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:argType];
  184. if ([argType_string hasPrefix:@"@"]) {
  185. __unsafe_unretained id objValue;
  186. [invocation getArgument:&objValue atIndex:argIdx];
  187. if (objValue) {
  188. if ([objValue isKindOfClass:[NSString class]]) {
  189. return [NSString stringWithFormat:@"\"%@\"", objValue];
  190. }
  191. NSString *objDescription = [objValue description];
  192. if (objDescription.length > 20) {
  193. return [NSString stringWithFormat:@"(%@ *)%p", NSStringFromClass([objValue class]), objValue];
  194. } else {
  195. return objDescription;
  196. }
  197. } else {
  198. return @"nil";
  199. }
  200. }
  201. }
  202. return @"?";
  203. }];
  204. return strings.copy;
  205. }
  206. static SEL LKS_AltSelectorFromSelector(SEL originalSelector) {
  207. NSString *selectorName = NSStringFromSelector(originalSelector);
  208. return NSSelectorFromString([@"lks_alt_" stringByAppendingString:selectorName]);
  209. }
  210. static NSMutableDictionary<NSString *, NSMutableSet<NSString *> *> *LKS_HookedDict() {
  211. static NSMutableDictionary *dict;
  212. static dispatch_once_t onceToken;
  213. dispatch_once(&onceToken, ^{
  214. dict = [NSMutableDictionary dictionary];
  215. });
  216. return dict;
  217. }
  218. static NSMutableArray<NSDictionary<NSString *, id> *> *LKS_ActiveList() {
  219. static NSMutableArray *list;
  220. static dispatch_once_t onceToken;
  221. dispatch_once(&onceToken, ^{
  222. list = [NSMutableArray array];
  223. });
  224. return list;
  225. }
  226. static void Lookin_PleaseRemoveMethodTraceInLookinAppIfCrashHere(Class targetClass) {
  227. SEL forwardInvocationSel = @selector(forwardInvocation:);
  228. Method forwardInvocationMethod = class_getInstanceMethod(targetClass, forwardInvocationSel);
  229. void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL;
  230. if (forwardInvocationMethod != NULL) {
  231. originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod);
  232. }
  233. id newForwardInvocation = ^(id target, NSInvocation *invocation) {
  234. __block BOOL isHookedSel = NO;
  235. __block BOOL shouldNotify = NO;
  236. [LKS_HookedDict() enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull enumeratedClassName, NSMutableSet<NSString *> * _Nonnull obj, BOOL * _Nonnull stop) {
  237. if ([target isKindOfClass:NSClassFromString(enumeratedClassName)]) {
  238. NSString *invocationSelName = NSStringFromSelector(invocation.selector);
  239. isHookedSel = [obj containsObject:invocationSelName];
  240. NSArray<NSString *> *activeSels = [[LKS_ActiveList() lookin_firstFiltered:^BOOL(NSDictionary<NSString *,id> *obj) {
  241. return [obj[kActiveListKey_Class] isEqualToString:enumeratedClassName];
  242. }] objectForKey:kActiveListKey_Sels];
  243. shouldNotify = [activeSels lookin_any:^BOOL(NSString *obj) {
  244. return [obj isEqualToString:invocationSelName];
  245. }];
  246. *stop = YES;
  247. }
  248. }];
  249. if (isHookedSel) {
  250. if (shouldNotify) {
  251. LookinMethodTraceRecord *record = [LookinMethodTraceRecord new];
  252. record.targetAddress = [NSString stringWithFormat:@"%p", invocation.target];
  253. record.selClassName = NSStringFromClass([invocation.target class]);
  254. record.selName = NSStringFromSelector(invocation.selector);
  255. record.callStacks = [NSThread callStackSymbols];
  256. record.args = LKS_ArgumentsDescriptionsFromInvocation(invocation);
  257. record.date = [NSDate date];
  258. [[LKS_ConnectionManager sharedInstance] pushData:record type:LookinPush_MethodTraceRecord];
  259. }
  260. invocation.selector = LKS_AltSelectorFromSelector(invocation.selector);
  261. [invocation invoke];
  262. return;
  263. }
  264. if (originalForwardInvocation == NULL) {
  265. [target doesNotRecognizeSelector:invocation.selector];
  266. } else {
  267. originalForwardInvocation(target, forwardInvocationSel, invocation);
  268. }
  269. };
  270. class_replaceMethod(targetClass, forwardInvocationSel, imp_implementationWithBlock(newForwardInvocation), "v@:@");
  271. [LKS_HookedDict() setValue:[NSMutableSet set] forKey:NSStringFromClass(targetClass)];
  272. }
  273. @interface LKS_MethodTraceManager ()
  274. @end
  275. @implementation LKS_MethodTraceManager
  276. + (instancetype)sharedInstance {
  277. static dispatch_once_t onceToken;
  278. static LKS_MethodTraceManager *instance = nil;
  279. dispatch_once(&onceToken,^{
  280. instance = [[super allocWithZone:NULL] init];
  281. });
  282. return instance;
  283. }
  284. + (id)allocWithZone:(struct _NSZone *)zone{
  285. return [self sharedInstance];
  286. }
  287. - (void)removeWithClassName:(NSString *)className selName:(NSString *)selName {
  288. if (!className.length) {
  289. return;
  290. }
  291. NSUInteger classIdx = [LKS_ActiveList() indexOfObjectPassingTest:^BOOL(NSDictionary<NSString *,id> * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  292. return [obj[kActiveListKey_Class] isEqualToString:className];
  293. }];
  294. if (classIdx == NSNotFound) {
  295. return;
  296. }
  297. if (selName) {
  298. NSDictionary<NSString *, id> *classDict = [LKS_ActiveList() objectAtIndex:classIdx];
  299. NSMutableArray<NSString *> *sels = classDict[kActiveListKey_Sels];
  300. [sels removeObject:selName];
  301. if (sels.count == 0) {
  302. [LKS_ActiveList() removeObjectAtIndex:classIdx];
  303. }
  304. } else {
  305. [LKS_ActiveList() removeObjectAtIndex:classIdx];
  306. }
  307. }
  308. - (void)addWithClassName:(NSString *)targetClassName selName:(NSString *)targetSelName {
  309. BOOL isValid = [self _isValidWithClassName:targetClassName selName:targetSelName];
  310. if (!isValid) {
  311. return;
  312. }
  313. BOOL addSucc = [self _addToActiveListWithClassName:targetClassName selName:targetSelName];
  314. if (!addSucc) {
  315. return;
  316. }
  317. Class targetClass = NSClassFromString(targetClassName);
  318. SEL targetSel = NSSelectorFromString(targetSelName);
  319. Method targetMethod = class_getInstanceMethod(targetClass, targetSel);
  320. @synchronized (self) {
  321. if (![LKS_HookedDict() valueForKey:targetClassName]) {
  322. Lookin_PleaseRemoveMethodTraceInLookinAppIfCrashHere(targetClass);
  323. }
  324. NSMutableSet<NSString *> *hookedSelNames = [LKS_HookedDict() objectForKey:targetClassName];
  325. if ([hookedSelNames containsObject:targetSelName]) {
  326. return;
  327. }
  328. class_addMethod(targetClass, LKS_AltSelectorFromSelector(targetSel), method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
  329. if (method_getImplementation(targetMethod) != _objc_msgForward) {
  330. class_replaceMethod(targetClass, targetSel, _objc_msgForward, method_getTypeEncoding(targetMethod));
  331. }
  332. [hookedSelNames addObject:targetSelName];
  333. }
  334. }
  335. - (BOOL)_addToActiveListWithClassName:(NSString *)targetClassName selName:(NSString *)targetSelName {
  336. __block BOOL addSuccessfully = YES;
  337. NSDictionary *activeList_dict = [LKS_ActiveList() lookin_firstFiltered:^BOOL(NSDictionary<NSString *,id> *obj) {
  338. return [obj[kActiveListKey_Class] isEqualToString:targetClassName];
  339. }];
  340. if (activeList_dict) {
  341. NSMutableArray *sels = activeList_dict[kActiveListKey_Sels];
  342. if ([sels containsObject:targetSelName]) {
  343. addSuccessfully = NO;
  344. } else {
  345. [sels addObject:targetSelName];
  346. }
  347. } else {
  348. activeList_dict = @{kActiveListKey_Class:targetClassName, kActiveListKey_Sels: @[targetSelName].mutableCopy};
  349. [LKS_ActiveList() addObject:activeList_dict];
  350. }
  351. return addSuccessfully;
  352. }
  353. - (BOOL)_isValidWithClassName:(NSString *)targetClassName selName:(NSString *)targetSelName {
  354. if ([targetSelName isEqualToString:@"dealloc"]) {
  355. return NO;
  356. }
  357. Class targetClass = NSClassFromString(targetClassName);
  358. if (!targetClass) {
  359. return NO;
  360. }
  361. SEL targetSel = NSSelectorFromString(targetSelName);
  362. Method targetMethod = class_getInstanceMethod(targetClass, targetSel);
  363. if (targetSel == NULL || targetMethod == NULL) {
  364. return NO;
  365. }
  366. return YES;
  367. }
  368. - (NSArray<NSDictionary<NSString *, id> *> *)currentActiveTraceList {
  369. return LKS_ActiveList();
  370. }
  371. - (NSArray<NSString *> *)allClassesListInApp {
  372. NSSet<NSString *> *prefixesToAvoid = [NSSet setWithObjects:@"OS_", @"IBA", @"SKUI", @"HM", @"WBS", @"CDP", @"DMF", @"TimerSupport", @"Swift.", @"Foundation", @"CEM", @"PSUI", @"CPL", @"IPA", @"NSKeyValue", @"ICS", @"INIntent", @"NWConcrete", @"NSSQL", @"SASetting", @"SAM", @"GEO", @"PBBProto", @"AWD", @"MTL", @"PKPhysics", @"TIKeyEvent", @"TITypologyRecord", @"IDS", @"AVCapture", @"AVAsset", @"AVContent", nil];
  373. int numClasses;
  374. Class * classes = NULL;
  375. classes = NULL;
  376. numClasses = objc_getClassList(NULL, 0);
  377. NSMutableArray<NSString *> *array = [NSMutableArray array];
  378. if (numClasses > 0) {
  379. classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
  380. numClasses = objc_getClassList(classes, numClasses);
  381. for (int i = 0; i < numClasses; i++) {
  382. Class c = classes[i];
  383. NSString *className = NSStringFromClass(c);
  384. if (className) {
  385. BOOL shouldAvoid = [prefixesToAvoid lookin_any:^BOOL(NSString *prefix) {
  386. return [className hasPrefix:prefix];
  387. }];
  388. if (!shouldAvoid) {
  389. [array addObject:className];
  390. }
  391. }
  392. }
  393. free(classes);
  394. }
  395. return array.copy;
  396. }
  397. @end
  398. #endif /* SHOULD_COMPILE_LOOKIN_SERVER */