LKS_MethodTraceManager.m 19 KB

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