Repository: facebook/chisel Branch: main Commit: b28507a68daa Files: 50 Total size: 262.5 KB Directory structure: gitextract_gmf84zuc/ ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Chisel/ │ ├── Chisel/ │ │ ├── CHLAllocations.c │ │ ├── CHLAllocations.h │ │ ├── CHLObjcInstanceCommands.h │ │ ├── CHLObjcInstanceCommands.mm │ │ ├── CHLObjcInstances.h │ │ ├── CHLObjcInstances.mm │ │ ├── CHLPredicateTools.h │ │ ├── CHLPredicateTools.m │ │ ├── Chisel.h │ │ ├── Info.plist │ │ └── zone_allocator.h │ ├── Chisel-macOS/ │ │ ├── Chisel_macOS.h │ │ └── Info.plist │ ├── Chisel.xcodeproj/ │ │ ├── project.pbxproj │ │ ├── project.xcworkspace/ │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata/ │ │ └── xcschemes/ │ │ └── Chisel.xcscheme │ ├── ChiselTests/ │ │ ├── ChiselTests.m │ │ └── Info.plist │ └── Makefile ├── LICENSE ├── README.md ├── commands/ │ ├── FBAccessibilityCommands.py │ ├── FBAutoLayoutCommands.py │ ├── FBClassDump.py │ ├── FBComponentCommands.py │ ├── FBCopyCommands.py │ ├── FBCounterCommands.py │ ├── FBDebugCommands.py │ ├── FBDelay.py │ ├── FBDisplayCommands.py │ ├── FBFindCommands.py │ ├── FBFlickerCommands.py │ ├── FBImportCommands.py │ ├── FBInvocationCommands.py │ ├── FBPrintCommands.py │ ├── FBTextInputCommands.py │ ├── FBVisualizationCommands.py │ └── FBXCTestCommands.py ├── fbchisellldb.py ├── fbchisellldbbase.py ├── fbchisellldbinputhelpers.py ├── fbchisellldbobjcruntimehelpers.py ├── fbchisellldbobjecthelpers.py ├── fbchisellldbviewcontrollerhelpers.py └── fbchisellldbviewhelpers.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.pyc ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct) so that you can understand what actions will and will not be tolerated. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Chisel We want to make contributing to this project as easy and transparent as possible. ## Pull Requests We actively welcome your pull requests. 1. Fork the repo and create your branch from `master`. 2. If you've added code that should be tested, add tests 3. If you've changed APIs, update the documentation. 4. Ensure the test suite passes. 5. Make sure your code lints. 6. If you haven't already, complete the Contributor License Agreement ("CLA"). ## Contributor License Agreement ("CLA") In order to accept your pull request, we need you to submit a CLA. You only need to do this once to work on any of Facebook's open source projects. Complete your CLA here: ## Issues We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe disclosure of security bugs. In those cases, please go through the process outlined on that page and do not file a public issue. ## Updating Chisel in Brew (for maintainers) Most users have Chisel installed via Homebrew. In order to update the version they'll receive when using `brew install` or `brew update`, we have to make some manual changes. 1. Create a new release in the GitHub web interface. 2. Download the `tar.gz` for that release and run `shasum -a 256 `. 3. Copy the URL for the `.tar.gz` on the release page. Run: ``` brew bump-formula-pr --strict chisel \ --url= \ --sha256= ``` More docs on the process are available on the [Homebrew site](https://docs.brew.sh/How-To-Open-a-Homebrew-Pull-Request). Example PRs: - [Bump to 2.0.1](https://github.com/Homebrew/homebrew-core/pull/59799) - [Bump to 2.0.0](https://github.com/Homebrew/homebrew-core/pull/50571) ## License By contributing to Chisel, you agree that your contributions will be licensed under its MIT license. ================================================ FILE: Chisel/Chisel/CHLAllocations.c ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #include "CHLAllocations.h" static kern_return_t reader(__unused task_t remote_task, vm_address_t remote_address, __unused vm_size_t size, void **local_memory) { *local_memory = (void *)remote_address; return KERN_SUCCESS; } typedef struct { CHLRangeHandler handler; void *context; } RangeEnumeratorArgs; static void rangeEnumerator(__unused task_t task, void *context, __unused unsigned type, vm_range_t *ranges, unsigned int count) { const RangeEnumeratorArgs *args = (RangeEnumeratorArgs *)context; for (unsigned int i = 0; i < count; ++i) { args->handler(ranges[i], args->context); } } void CHLScanAllocations(CHLRangeHandler handler, void *context, const malloc_zone_t *sideZone) { vm_address_t *zones; unsigned int count; malloc_get_all_zones(TASK_NULL, &reader, &zones, &count); RangeEnumeratorArgs args = {handler, context}; for (unsigned int i = 0; i < count; ++i) { malloc_zone_t *zone = (malloc_zone_t *)zones[i]; if (zone != sideZone) { zone->introspect->enumerator(TASK_NULL, &args, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t)zone, reader, rangeEnumerator); } } } ================================================ FILE: Chisel/Chisel/CHLAllocations.h ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #include #if defined(__cplusplus) extern "C" { #endif typedef void (*CHLRangeHandler)(vm_range_t range, void *context); // Enumerate live allocations in all malloc zones. If callers allocate memory in the handler, those // allocations should be within the given `sideZone`. void CHLScanAllocations(CHLRangeHandler handler, void *context, const malloc_zone_t *sideZone); #if defined(__cplusplus) } #endif ================================================ FILE: Chisel/Chisel/CHLObjcInstanceCommands.h ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. @class NSPredicate; #if defined(__cplusplus) extern "C" { #endif // Debugger interface for finding and printing instances of a type, with an optional predicate. // The predicate format is anything supported by NSPredicate. void PrintInstances(const char *type, const char *pred); #if defined(__cplusplus) } #endif ================================================ FILE: Chisel/Chisel/CHLObjcInstanceCommands.mm ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #import "CHLObjcInstanceCommands.h" #include #include #import #import #import "CHLObjcInstances.h" #import "CHLPredicateTools.h" #include "zone_allocator.h" #if __has_feature(objc_arc) #error Disable ARC for this file #endif struct IsValidArgs { const std::unordered_set &classSet; bool isValid = true; }; static void isValidObject(const void *value, void *context) { const auto args = reinterpret_cast(context); if (!args->isValid) { return; } vm_range_t range = {(vm_address_t)value, malloc_size(value)}; if (CHLViableObjcInstance(range, args->classSet) == nil) { args->isValid = false; } } static void isValidKeyValue(const void *key, const void *value, void *context) { const auto args = reinterpret_cast(context); isValidObject(key, context); if (args->isValid) { isValidObject(value, context); } } static bool predicatePrecheck(id obj, const std::unordered_set &classSet) { IsValidArgs args{classSet}; if ([obj isKindOfClass:objc_getClass("__NSCFDictionary")]) { CFDictionaryApplyFunction((CFDictionaryRef)obj, &isValidKeyValue, &args); } else if ([obj isKindOfClass:objc_getClass("__NSCFSet")]) { CFSetApplyFunction((CFSetRef)obj, &isValidObject, &args); } else { // Skip classes containing NSPlaceholder. // TODO: Figure out better way to ignore invalid instances. char *name = (char *)object_getClassName(obj); while (*name == '_') ++name; if (strncmp(name, "NSPlaceholder", sizeof("NSPlaceholder") - 1) == 0) { args.isValid = false; } } if (!args.isValid && getenv("FINDINSTANCES_DEBUG")) { printf("%p has class %s but contains non objc data\n", obj, object_getClassName(obj)); } return args.isValid; } static void printObject(id obj, NSSet *keyPaths) { printf("<%s: %p", object_getClassName(obj), obj); for (NSString *keyPath in keyPaths) { printf("; %s = %s", keyPath.UTF8String, [[obj valueForKeyPath:keyPath] description].UTF8String); } printf(">\n"); } static bool objectIsMatch(NSPredicate *predicate, id obj, const std::unordered_set &classSet) { if (!predicate) { return true; } bool debug = getenv("FINDINSTANCES_DEBUG"); if (!predicatePrecheck(obj, classSet)) { if (debug) { printf("%p has class %s but has non objc contents\n", obj, object_getClassName(obj)); } return false; } @try { return [predicate evaluateWithObject:obj]; } @catch (...) { if (debug) { printf("%p has class %s but failed predicate evaluation\n", obj, object_getClassName(obj)); } return false; } } // Function reimplementation of +[NSObject isSubclassOf:] to avoid the objc runtime side // effects that can happen when calling methods, like realizing classes, +initialize, etc. static bool isSubclassOfClass(Class self, Class target) { for (auto cls = self; cls != Nil; cls = class_getSuperclass(cls)) { if (cls == target) { return true; } } return false; } // Function reimplementation of +[NSObject conformsToProtocol:] to avoid the objc runtime side // effects that can happen when calling methods, like realizing classes, +initialize, etc. static bool conformsToProtocol(Class self, Protocol *protocol) { for (auto cls = self; cls != Nil; cls = class_getSuperclass(cls)) { if (class_conformsToProtocol(cls, protocol)) { return true; } } return false; } void PrintInstances(const char *type, const char *pred) { NSPredicate *predicate = nil; if (pred != nullptr && *pred != '\0') { @try { predicate = [NSPredicate predicateWithFormat:@(pred)]; } @catch (NSException *e) { printf("Error: Invalid predicate; %s\n", [e reason].UTF8String); return; } } const std::unordered_set objcClasses = CHLObjcClassSet(); std::unordered_set matchClasses; Protocol *protocol = objc_getProtocol(type); if (protocol != nullptr && strcmp("NSObject", type) != 0) { for (auto cls : objcClasses) { if (conformsToProtocol(cls, protocol)) { matchClasses.insert(cls); } } } bool exactClass = false; if (type[0] == '*') { exactClass = true; ++type; } // Helper lambda that only exists so that it can be called more than once, as in the // rare case where `type` corresponds to more than one Swift class. auto addMatch = [&](Class baseClass) { if (exactClass) { matchClasses.insert(baseClass); } else { for (auto cls : objcClasses) { if (isSubclassOfClass(cls, baseClass)) { matchClasses.insert(cls); } } } }; Class baseClass = objc_getClass(type); if (baseClass != Nil) { addMatch(baseClass); } else { // The given class name hasn't been found, this could be a Swift class which has // a module name prefix. Loop over all classes to look for matching class names. for (auto cls : objcClasses) { // SwiftModule.ClassName // ^- dot + 1 auto dot = strchr(class_getName(cls), '.'); if (dot && strcmp(type, dot + 1) == 0) { addMatch(cls); } } } if (matchClasses.empty()) { // TODO: Accept name of library/module, and list instances of classes defined there. printf("Unknown type: %s\n", type); return; } NSSet *keyPaths = CHLVariableKeyPaths(predicate); auto instances = CHLScanObjcInstances(matchClasses); unsigned int matches = 0; for (id obj : instances) { if (objectIsMatch(predicate, obj, objcClasses)) { ++matches; printObject(obj, keyPaths); } } if (matches > 1) { printf("%d matches\n", matches); } } ================================================ FILE: Chisel/Chisel/CHLObjcInstances.h ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #if defined(__cplusplus) #include #include #include #include "zone_allocator.h" // Create a set containing all known Classes. std::unordered_set CHLObjcClassSet(); // Enumerates the heap and returns all objects that appear to be legitimate. std::vector> CHLScanObjcInstances(const std::unordered_set &classSet); // Performs a number of heuristic checks on the memory range, to determine if the memory appears to // be a viable Objective-C object. id CHLViableObjcInstance(vm_range_t range, const std::unordered_set &classSet); #endif ================================================ FILE: Chisel/Chisel/CHLObjcInstances.mm ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #import "CHLObjcInstances.h" #import #include "CHLAllocations.h" #include #include #include #include #if !defined(__LP64__) using mach_header_t = mach_header; #else using mach_header_t = mach_header_64; #endif #include #include #if __has_feature(objc_arc) #error Disable ARC for this file #endif // Informal protocol to make it easy to call -_isDeallocating @interface NSObject (Private) - (BOOL)_isDeallocating; @end static id embeddedObjcInstance(vm_range_t range) { Dl_info info; bool aligned = range.address % alignof(void *) == 0; uint8_t *pointer = (uint8_t *)range.address; if (aligned && dladdr(pointer, &info)) { unsigned long size = 0; uint8_t *start = getsectiondata((mach_header_t *)info.dli_fbase, SEG_DATA, "__cfstring", &size); uint8_t *end = start + size; if (start <= pointer || pointer < end) { // Found NSString/CFString constant. return reinterpret_cast(range.address); } } return nil; } // TODO: Should this cache results instead of repeated lookups. bool isFixedSizeClass(Class cls) { const auto meta = object_getClass(cls); const auto root = objc_getMetaClass("NSObject"); SEL allocs[] = { @selector(allocWithZone:), @selector(alloc) }; for (const auto &sel : allocs) { IMP imp = class_getMethodImplementation(meta, sel); if (imp != class_getMethodImplementation(root, sel)) { // Class overrides NSObject alloc method, may not have fixed sizes. return false; } } return true; } // Runs a number of heuristics on the given address. Returns nil if any heuristic fails, otherwise // returns that address casted as an object. // // Currently the heuristics don't fully guarantee that the returned object is an actual object, but // when using MallocScribble=1, false positives are unlikely. Further, callers will generally do // higher level filtering, for example checking a value on the object, which can further eliminate // false positives. // // There's also one known false negative case, NSConcreteValue can store data in the malloc memory // beyond its instance size. Currently this false negative is allowed. id CHLViableObjcInstance(vm_range_t range, const std::unordered_set &classSet) { // Check if this address points to an object embedded into Mach-O. if (range.size == 0) { return embeddedObjcInstance(range); } id obj = reinterpret_cast(range.address); // It's safe to call object_getClass on memory that isn't objc objects. // Check that the returned Class points to an expected class. Class cls = object_getClass(obj); if (classSet.find(cls) == classSet.end()) { return nil; } // Instance size is the byte count needed for an object's ivars, plus any padding. // Allocation size is the byte count that malloc will actually allocate for instances of a Class. const auto instanceSize = class_getInstanceSize(cls); const auto expectedAllocationSize = malloc_good_size(instanceSize); const auto extraSize = expectedAllocationSize - instanceSize; const bool debug = getenv("FINDINSTANCES_DEBUG") != NULL; if (range.size < expectedAllocationSize) { if (debug) { printf("%p has class %s but is too small\n", obj, class_getName(cls)); } return nil; } if (range.size > expectedAllocationSize && isFixedSizeClass(cls)) { // Range is too big and the class has no way of allocating larger instances. if (debug) { printf("%p has fixed size class %s but is too large\n", obj, class_getName(cls)); } return nil; } if (range.size == expectedAllocationSize && extraSize) { // ObjC instances are allocated with calloc, memory beyond the instance size should be zeros. // Some classes have been known to store data in the extra space, ex NSConcreteValue. static const unsigned char ZEROS[1024] = {0}; auto extra = object_getIndexedIvars(obj); auto compareSize = std::min(extraSize, sizeof(ZEROS)); if (memcmp(extra, &ZEROS, compareSize) != 0) { if (debug) { printf("%p has class %s but has non-zero memory\n", obj, class_getName(cls)); } return nil; } } // Ignore deallocating objects. if ([obj _isDeallocating]) { return nil; } return obj; } struct FindViableObjcInstancesArgs { const std::unordered_set &classSet; std::vector> &instances; }; static void findViableObjcInstances(vm_range_t range, void *context) { const auto args = reinterpret_cast(context); id obj = CHLViableObjcInstance(range, args->classSet); if (obj != nil) { args->instances.push_back(obj); } } std::vector> CHLScanObjcInstances(const std::unordered_set &classSet) { std::vector> instances; FindViableObjcInstancesArgs args{classSet, instances}; CHLScanAllocations(&findViableObjcInstances, &args, instances.get_allocator().zone()); return instances; } std::unordered_set CHLObjcClassSet() { unsigned int count = 0; auto classList = objc_copyClassList(&count); std::unordered_set classSet{classList, classList + count, count}; free(classList); return classSet; } ================================================ FILE: Chisel/Chisel/CHLPredicateTools.h ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #import #if defined(__cplusplus) extern "C" { #endif NSSet *CHLVariableKeyPaths(NSPredicate *predicate); #if defined(__cplusplus) } #endif ================================================ FILE: Chisel/Chisel/CHLPredicateTools.m ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #import "CHLPredicateTools.h" static bool isEqualToConstantComparison(NSComparisonPredicate *predicate) { bool equality = predicate.predicateOperatorType == NSEqualToPredicateOperatorType; bool direct = predicate.comparisonPredicateModifier == NSDirectPredicateModifier; bool constantLeft = predicate.leftExpression.expressionType == NSConstantValueExpressionType; bool constantRight = predicate.rightExpression.expressionType == NSConstantValueExpressionType; return equality && direct && (constantLeft || constantRight); } NSSet *CHLVariableKeyPaths(NSPredicate *predicate) { if (predicate == nil) { return nil; } NSMutableSet *keyPaths = [NSMutableSet new]; NSMutableArray *predicateStack = [NSMutableArray arrayWithObject:predicate]; while (predicateStack.count > 0) { NSPredicate *subpredicate = [predicateStack lastObject]; [predicateStack removeLastObject]; if ([subpredicate isKindOfClass:[NSCompoundPredicate class]]) { NSCompoundPredicate *compoundPredicate = (NSCompoundPredicate *)subpredicate; [predicateStack addObjectsFromArray:compoundPredicate.subpredicates]; continue; } if ([subpredicate isKindOfClass:[NSComparisonPredicate class]]) { NSComparisonPredicate *comparisonPredicate = (NSComparisonPredicate *)subpredicate; if (isEqualToConstantComparison(comparisonPredicate)) { // Keypaths equal to constants are not variable. Skip these to not be noisy. // ex `username == "jonalan"` or `alpha == 0` continue; } // TODO: Handle NSFunctionExpressionType if (comparisonPredicate.leftExpression.expressionType == NSKeyPathExpressionType) { [keyPaths addObject:comparisonPredicate.leftExpression.keyPath]; } if (comparisonPredicate.rightExpression.expressionType == NSKeyPathExpressionType) { [keyPaths addObject:comparisonPredicate.rightExpression.keyPath]; } } } return keyPaths; } ================================================ FILE: Chisel/Chisel/Chisel.h ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #import //! Project version number for Chisel. FOUNDATION_EXPORT double ChiselVersionNumber; //! Project version string for Chisel. FOUNDATION_EXPORT const unsigned char ChiselVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: Chisel/Chisel/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass ================================================ FILE: Chisel/Chisel/zone_allocator.h ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #pragma once #include #include template struct zone_allocator { using value_type = T; T *allocate(std::size_t n) { auto allocation = malloc_zone_malloc(_zone.get(), n * sizeof(T)); return reinterpret_cast(allocation); } void deallocate(T *p, __unused std::size_t n) { malloc_zone_free(_zone.get(), p); } const malloc_zone_t *zone() const { return _zone.get(); } private: std::shared_ptr _zone{malloc_create_zone(0x200, 0), &malloc_destroy_zone}; }; template bool operator==(const zone_allocator &a, const zone_allocator &b) noexcept { return a.zone() == b.zone(); } template bool operator!=(const zone_allocator &a, const zone_allocator &b) noexcept { return !(a == b); } ================================================ FILE: Chisel/Chisel-macOS/Chisel_macOS.h ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #import //! Project version number for Chisel_macOS. FOUNDATION_EXPORT double Chisel_macOSVersionNumber; //! Project version string for Chisel_macOS. FOUNDATION_EXPORT const unsigned char Chisel_macOSVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import ================================================ FILE: Chisel/Chisel-macOS/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass ================================================ FILE: Chisel/Chisel.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 70D1EACB20FD9D4A004CDB3D /* Chisel_macOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 70D1EAC920FD9D4A004CDB3D /* Chisel_macOS.h */; settings = {ATTRIBUTES = (Public, ); }; }; 70D1EACF20FD9D82004CDB3D /* CHLPredicateTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A20C4B21DFDB8D200C89959 /* CHLPredicateTools.m */; }; 70D1EAD020FD9D85004CDB3D /* CHLObjcInstanceCommands.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A04088B1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.mm */; }; 70D1EAD120FD9D88004CDB3D /* CHLObjcInstances.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7ABD17AA1DF7FCF9006118F8 /* CHLObjcInstances.mm */; }; 70D1EAD220FD9D8C004CDB3D /* CHLAllocations.c in Sources */ = {isa = PBXBuildFile; fileRef = 7ABD17A61DF7F9FD006118F8 /* CHLAllocations.c */; }; 7A04088C1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A04088A1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.h */; }; 7A04088D1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7A04088B1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.mm */; }; 7A20C4B31DFDB8D200C89959 /* CHLPredicateTools.h in Headers */ = {isa = PBXBuildFile; fileRef = 7A20C4B11DFDB8D200C89959 /* CHLPredicateTools.h */; }; 7A20C4B41DFDB8D200C89959 /* CHLPredicateTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A20C4B21DFDB8D200C89959 /* CHLPredicateTools.m */; }; 7ABD17951DF7F998006118F8 /* Chisel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ABD178B1DF7F998006118F8 /* Chisel.framework */; }; 7ABD179A1DF7F998006118F8 /* ChiselTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7ABD17991DF7F998006118F8 /* ChiselTests.m */; }; 7ABD179C1DF7F998006118F8 /* Chisel.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ABD178E1DF7F998006118F8 /* Chisel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 7ABD17A71DF7F9FD006118F8 /* CHLAllocations.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ABD17A51DF7F9FD006118F8 /* CHLAllocations.h */; }; 7ABD17A81DF7F9FD006118F8 /* CHLAllocations.c in Sources */ = {isa = PBXBuildFile; fileRef = 7ABD17A61DF7F9FD006118F8 /* CHLAllocations.c */; }; 7ABD17AB1DF7FCF9006118F8 /* CHLObjcInstances.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ABD17A91DF7FCF9006118F8 /* CHLObjcInstances.h */; }; 7ABD17AC1DF7FCF9006118F8 /* CHLObjcInstances.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7ABD17AA1DF7FCF9006118F8 /* CHLObjcInstances.mm */; }; 7ABD17AF1DF88520006118F8 /* zone_allocator.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ABD17AD1DF88520006118F8 /* zone_allocator.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 7ABD17961DF7F998006118F8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7ABD17821DF7F998006118F8 /* Project object */; proxyType = 1; remoteGlobalIDString = 7ABD178A1DF7F998006118F8; remoteInfo = Chisel; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 70D1EAC720FD9D4A004CDB3D /* Chisel_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Chisel_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 70D1EAC920FD9D4A004CDB3D /* Chisel_macOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Chisel_macOS.h; sourceTree = ""; }; 70D1EACA20FD9D4A004CDB3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7A04088A1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHLObjcInstanceCommands.h; sourceTree = ""; }; 7A04088B1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CHLObjcInstanceCommands.mm; sourceTree = ""; }; 7A20C4B11DFDB8D200C89959 /* CHLPredicateTools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHLPredicateTools.h; sourceTree = ""; }; 7A20C4B21DFDB8D200C89959 /* CHLPredicateTools.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CHLPredicateTools.m; sourceTree = ""; }; 7ABD178B1DF7F998006118F8 /* Chisel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Chisel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7ABD178E1DF7F998006118F8 /* Chisel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Chisel.h; sourceTree = ""; }; 7ABD178F1DF7F998006118F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7ABD17941DF7F998006118F8 /* ChiselTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChiselTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 7ABD17991DF7F998006118F8 /* ChiselTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ChiselTests.m; sourceTree = ""; }; 7ABD179B1DF7F998006118F8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7ABD17A51DF7F9FD006118F8 /* CHLAllocations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHLAllocations.h; sourceTree = ""; }; 7ABD17A61DF7F9FD006118F8 /* CHLAllocations.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = CHLAllocations.c; sourceTree = ""; }; 7ABD17A91DF7FCF9006118F8 /* CHLObjcInstances.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CHLObjcInstances.h; sourceTree = ""; }; 7ABD17AA1DF7FCF9006118F8 /* CHLObjcInstances.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CHLObjcInstances.mm; sourceTree = ""; }; 7ABD17AD1DF88520006118F8 /* zone_allocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zone_allocator.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 70D1EAC320FD9D4A004CDB3D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 7ABD17871DF7F998006118F8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 7ABD17911DF7F998006118F8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 7ABD17951DF7F998006118F8 /* Chisel.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 70D1EAC820FD9D4A004CDB3D /* Chisel-macOS */ = { isa = PBXGroup; children = ( 70D1EAC920FD9D4A004CDB3D /* Chisel_macOS.h */, 70D1EACA20FD9D4A004CDB3D /* Info.plist */, ); path = "Chisel-macOS"; sourceTree = ""; }; 7ABD17811DF7F998006118F8 = { isa = PBXGroup; children = ( 7ABD178D1DF7F998006118F8 /* Chisel */, 7ABD17981DF7F998006118F8 /* ChiselTests */, 70D1EAC820FD9D4A004CDB3D /* Chisel-macOS */, 7ABD178C1DF7F998006118F8 /* Products */, ); indentWidth = 2; sourceTree = ""; }; 7ABD178C1DF7F998006118F8 /* Products */ = { isa = PBXGroup; children = ( 7ABD178B1DF7F998006118F8 /* Chisel.framework */, 7ABD17941DF7F998006118F8 /* ChiselTests.xctest */, 70D1EAC720FD9D4A004CDB3D /* Chisel_macOS.framework */, ); name = Products; sourceTree = ""; }; 7ABD178D1DF7F998006118F8 /* Chisel */ = { isa = PBXGroup; children = ( 7ABD178E1DF7F998006118F8 /* Chisel.h */, 7ABD178F1DF7F998006118F8 /* Info.plist */, 7ABD17AD1DF88520006118F8 /* zone_allocator.h */, 7ABD17A51DF7F9FD006118F8 /* CHLAllocations.h */, 7ABD17A61DF7F9FD006118F8 /* CHLAllocations.c */, 7ABD17A91DF7FCF9006118F8 /* CHLObjcInstances.h */, 7ABD17AA1DF7FCF9006118F8 /* CHLObjcInstances.mm */, 7A04088A1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.h */, 7A04088B1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.mm */, 7A20C4B11DFDB8D200C89959 /* CHLPredicateTools.h */, 7A20C4B21DFDB8D200C89959 /* CHLPredicateTools.m */, ); path = Chisel; sourceTree = ""; }; 7ABD17981DF7F998006118F8 /* ChiselTests */ = { isa = PBXGroup; children = ( 7ABD17991DF7F998006118F8 /* ChiselTests.m */, 7ABD179B1DF7F998006118F8 /* Info.plist */, ); path = ChiselTests; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 70D1EAC420FD9D4A004CDB3D /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 70D1EACB20FD9D4A004CDB3D /* Chisel_macOS.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 7ABD17881DF7F998006118F8 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 7ABD17A71DF7F9FD006118F8 /* CHLAllocations.h in Headers */, 7ABD179C1DF7F998006118F8 /* Chisel.h in Headers */, 7A20C4B31DFDB8D200C89959 /* CHLPredicateTools.h in Headers */, 7A04088C1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.h in Headers */, 7ABD17AF1DF88520006118F8 /* zone_allocator.h in Headers */, 7ABD17AB1DF7FCF9006118F8 /* CHLObjcInstances.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 70D1EAC620FD9D4A004CDB3D /* Chisel-macOS */ = { isa = PBXNativeTarget; buildConfigurationList = 70D1EACE20FD9D4A004CDB3D /* Build configuration list for PBXNativeTarget "Chisel-macOS" */; buildPhases = ( 70D1EAC220FD9D4A004CDB3D /* Sources */, 70D1EAC320FD9D4A004CDB3D /* Frameworks */, 70D1EAC420FD9D4A004CDB3D /* Headers */, 70D1EAC520FD9D4A004CDB3D /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "Chisel-macOS"; productName = "Chisel-macOS"; productReference = 70D1EAC720FD9D4A004CDB3D /* Chisel_macOS.framework */; productType = "com.apple.product-type.framework"; }; 7ABD178A1DF7F998006118F8 /* Chisel */ = { isa = PBXNativeTarget; buildConfigurationList = 7ABD179F1DF7F998006118F8 /* Build configuration list for PBXNativeTarget "Chisel" */; buildPhases = ( 7ABD17861DF7F998006118F8 /* Sources */, 7ABD17871DF7F998006118F8 /* Frameworks */, 7ABD17881DF7F998006118F8 /* Headers */, 7ABD17891DF7F998006118F8 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = Chisel; productName = Chisel; productReference = 7ABD178B1DF7F998006118F8 /* Chisel.framework */; productType = "com.apple.product-type.framework"; }; 7ABD17931DF7F998006118F8 /* ChiselTests */ = { isa = PBXNativeTarget; buildConfigurationList = 7ABD17A21DF7F998006118F8 /* Build configuration list for PBXNativeTarget "ChiselTests" */; buildPhases = ( 7ABD17901DF7F998006118F8 /* Sources */, 7ABD17911DF7F998006118F8 /* Frameworks */, 7ABD17921DF7F998006118F8 /* Resources */, ); buildRules = ( ); dependencies = ( 7ABD17971DF7F998006118F8 /* PBXTargetDependency */, ); name = ChiselTests; productName = ChiselTests; productReference = 7ABD17941DF7F998006118F8 /* ChiselTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 7ABD17821DF7F998006118F8 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0810; ORGANIZATIONNAME = Facebook; TargetAttributes = { 70D1EAC620FD9D4A004CDB3D = { CreatedOnToolsVersion = 9.4.1; ProvisioningStyle = Automatic; }; 7ABD178A1DF7F998006118F8 = { CreatedOnToolsVersion = 8.1; ProvisioningStyle = Automatic; }; 7ABD17931DF7F998006118F8 = { CreatedOnToolsVersion = 8.1; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 7ABD17851DF7F998006118F8 /* Build configuration list for PBXProject "Chisel" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 7ABD17811DF7F998006118F8; productRefGroup = 7ABD178C1DF7F998006118F8 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 7ABD178A1DF7F998006118F8 /* Chisel */, 7ABD17931DF7F998006118F8 /* ChiselTests */, 70D1EAC620FD9D4A004CDB3D /* Chisel-macOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 70D1EAC520FD9D4A004CDB3D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 7ABD17891DF7F998006118F8 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 7ABD17921DF7F998006118F8 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 70D1EAC220FD9D4A004CDB3D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 70D1EAD120FD9D88004CDB3D /* CHLObjcInstances.mm in Sources */, 70D1EAD220FD9D8C004CDB3D /* CHLAllocations.c in Sources */, 70D1EAD020FD9D85004CDB3D /* CHLObjcInstanceCommands.mm in Sources */, 70D1EACF20FD9D82004CDB3D /* CHLPredicateTools.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 7ABD17861DF7F998006118F8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7ABD17A81DF7F9FD006118F8 /* CHLAllocations.c in Sources */, 7ABD17AC1DF7FCF9006118F8 /* CHLObjcInstances.mm in Sources */, 7A04088D1DF9A2C7009C5BFA /* CHLObjcInstanceCommands.mm in Sources */, 7A20C4B41DFDB8D200C89959 /* CHLPredicateTools.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 7ABD17901DF7F998006118F8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7ABD179A1DF7F998006118F8 /* ChiselTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 7ABD17971DF7F998006118F8 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7ABD178A1DF7F998006118F8 /* Chisel */; targetProxy = 7ABD17961DF7F998006118F8 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 70D1EACC20FD9D4A004CDB3D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Chisel-macOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.Chisel-macOS"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; }; name = Debug; }; 70D1EACD20FD9D4A004CDB3D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Chisel-macOS/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.13; PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.Chisel-macOS"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 4.0; }; name = Release; }; 7ABD179D1DF7F998006118F8 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = NO; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = c11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.1; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 7ABD179E1DF7F998006118F8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "c++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = NO; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = c11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 7ABD17A01DF7F998006118F8 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Chisel/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.Chisel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = NO; }; name = Debug; }; 7ABD17A11DF7F998006118F8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Chisel/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.Chisel; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = NO; }; name = Release; }; 7ABD17A31DF7F998006118F8 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = ChiselTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.ChiselTests; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 7ABD17A41DF7F998006118F8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = ChiselTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.ChiselTests; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 70D1EACE20FD9D4A004CDB3D /* Build configuration list for PBXNativeTarget "Chisel-macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 70D1EACC20FD9D4A004CDB3D /* Debug */, 70D1EACD20FD9D4A004CDB3D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 7ABD17851DF7F998006118F8 /* Build configuration list for PBXProject "Chisel" */ = { isa = XCConfigurationList; buildConfigurations = ( 7ABD179D1DF7F998006118F8 /* Debug */, 7ABD179E1DF7F998006118F8 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 7ABD179F1DF7F998006118F8 /* Build configuration list for PBXNativeTarget "Chisel" */ = { isa = XCConfigurationList; buildConfigurations = ( 7ABD17A01DF7F998006118F8 /* Debug */, 7ABD17A11DF7F998006118F8 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 7ABD17A21DF7F998006118F8 /* Build configuration list for PBXNativeTarget "ChiselTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 7ABD17A31DF7F998006118F8 /* Debug */, 7ABD17A41DF7F998006118F8 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 7ABD17821DF7F998006118F8 /* Project object */; } ================================================ FILE: Chisel/Chisel.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Chisel/Chisel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: Chisel/Chisel.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ BuildSystemType Original ================================================ FILE: Chisel/Chisel.xcodeproj/xcshareddata/xcschemes/Chisel.xcscheme ================================================ ================================================ FILE: Chisel/ChiselTests/ChiselTests.m ================================================ // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #import @interface ChiselTests : XCTestCase @end @implementation ChiselTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } - (void)testPerformanceExample { // This is an example of a performance test case. [self measureBlock:^{ // Put the code you want to measure the time of here. }]; } @end ================================================ FILE: Chisel/ChiselTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: Chisel/Makefile ================================================ PREFIX ?= /usr/local/lib export INSTALL_NAME = ifneq ($(LD_DYLIB_INSTALL_NAME),) INSTALL_NAME = "LD_DYLIB_INSTALL_NAME=$(LD_DYLIB_INSTALL_NAME)" endif install: xcodebuild \ -scheme Chisel \ -configuration Release \ -sdk iphonesimulator \ install \ $(INSTALL_NAME) \ DSTROOT=/ \ INSTALL_PATH="$(PREFIX)" ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Facebook, Inc. and its affiliates. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Chisel `Chisel` is a collection of `LLDB` commands to assist in the debugging of iOS apps. [[Installation](#installation) • [Commands](#commands) • [Custom Commands](#custom-commands) • [Development Workflow](#development-workflow) [Contributing](#contributing) • [License](#license)] For a comprehensive overview of LLDB, and how Chisel complements it, read Ari Grant's [Dancing in the Debugger — A Waltz with LLDB](http://www.objc.io/issue-19/lldb-debugging.html) in issue 19 of [objc.io](http://www.objc.io/). ## Installation ```shell brew update brew install chisel ``` if `.lldbinit` file doesn't exist you can create it & open it by tapping on the terminal ```shell touch .lldbinit open .lldbinit ``` Then add the following line to your `~/.lldbinit` file. ```Python # ~/.lldbinit ... command script import /usr/local/opt/chisel/libexec/fbchisellldb.py ``` * Note that if you are installing on an M1 Mac, the path above should be `/opt/homebrew/opt/chisel/libexec/fbchisellldb.py` instead. Alternatively, download chisel and add the following line to your _~/.lldbinit_ file. ```Python # ~/.lldbinit ... command script import /path/to/fbchisellldb.py ``` The commands will be available the next time `Xcode` starts. ## Commands There are many commands; here's a few: *(Compatibility with iOS/Mac indicated at right)* |Command |Description |iOS |OS X | |-----------------|----------------|-------|-------| |pviews |Print the recursive view description for the key window.|Yes|Yes| |pvc |Print the recursive view controller description for the key window.|Yes|No| |visualize |Open a `UIImage`, `CGImageRef`, `UIView`, `CALayer`, `NSData` (of an image), `UIColor`, `CIColor`, `CIImage`, `CGColorRef` or `CVPixelBuffer` in Preview.app on your Mac.|Yes|No| |fv |Find a view in the hierarchy whose class name matches the provided regex.|Yes|No| |fvc |Find a view controller in the hierarchy whose class name matches the provided regex.|Yes|No| |show/hide |Show or hide the given view or layer. You don't even have to continue the process to see the changes!|Yes|Yes| |mask/unmask |Overlay a view or layer with a transparent rectangle to visualize where it is.|Yes|No| |border/unborder |Add a border to a view or layer to visualize where it is.|Yes|Yes| |caflush |Flush the render server (equivalent to a "repaint" if no animations are in-flight).|Yes|Yes| |bmessage |Set a symbolic breakpoint on the method of a class or the method of an instance without worrying which class in the hierarchy actually implements the method.|Yes|Yes| |wivar |Set a watchpoint on an instance variable of an object.|Yes|Yes| |presponder |Print the responder chain starting from the given object.|Yes|Yes| |... |... and many more!| To see the list of **all** of the commands execute the help command in `LLDB` or go to the [Wiki](https://github.com/facebook/chisel/wiki). ```Python (lldb) help The following is a list of built-in, permanent debugger commands: ... The following is a list of your current user-defined commands: ... ``` The bottom list contains all the commands sourced from `Chisel`. You can also inspect a specific command by passing its name as an argument to the help command (as with all other `LLDB` commands). ``` (lldb) help border Draws a border around . Color and width can be optionally provided. Arguments: ; Type: UIView*; The view to border. Options: --color/-c ; Type: string; A color name such as 'red', 'green', 'magenta', etc. --width/-w ; Type: CGFloat; Desired width of border. Syntax: border [--color=color] [--width=width] ``` All of the commands provided by `Chisel` come with verbose help. Be sure to read it when in doubt! ## Custom Commands You can add local, custom commands. Here's a contrived example. ```python #!/usr/bin/python # Example file with custom commands, located at /magical/commands/example.py import lldb import fbchisellldbbase as fb def lldbcommands(): return [ PrintKeyWindowLevel() ] class PrintKeyWindowLevel(fb.FBCommand): def name(self): return 'pkeywinlevel' def description(self): return 'An incredibly contrived command that prints the window level of the key window.' def run(self, arguments, options): # It's a good habit to explicitly cast the type of all return # values and arguments. LLDB can't always find them on its own. lldb.debugger.HandleCommand('p (CGFloat)[(id)[(id)[UIApplication sharedApplication] keyWindow] windowLevel]') ``` Then all that's left is to source the commands in lldbinit. `Chisel` has a python function just for this, _loadCommandsInDirectory_ in the _fbobjclldb.py_ module. ```Python # ~/.lldbinit ... command script import /path/to/fbobjclldb.py script fbobjclldb.loadCommandsInDirectory('/magical/commands/') ``` There's also builtin support to make it super easy to specify the arguments and options that a command takes. See the _border_ and _pinvocation_ commands for example use. ## Development Workflow Developing commands, whether for local use or contributing to `Chisel` directly, both follow the same workflow. Create a command as described in the [Custom Commands](#custom-commands) section and then 1. Start `LLDB` 2. Reach a breakpoint (or simply pause execution via the pause button in `Xcode`'s debug bar or `process interrupt` if attached directly) 3. Execute `command source ~/.lldbinit` in LLDB to source the commands 4. Run the command you are working on 5. Modify the command 6. Optionally run `script reload(modulename)` 7. Repeat steps 3-6 until the command becomes a source of happiness ## Contributing Please contribute any generic commands that you make. If it helps you then it will likely help many others! :D See `CONTRIBUTING.md` to learn how to contribute. ## License `Chisel` is MIT-licensed. See `LICENSE`. ================================================ FILE: commands/FBAccessibilityCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import os import re import fbchisellldbbase as fb import fbchisellldbobjecthelpers as objHelpers # This is the key corresponding to accessibility label in # _accessibilityElementsInContainer: ACCESSIBILITY_LABEL_KEY = 2001 def lldbcommands(): return [ FBPrintAccessibilityLabels(), FBPrintAccessibilityIdentifiers(), FBFindViewByAccessibilityLabelCommand(), ] class FBPrintAccessibilityLabels(fb.FBCommand): def name(self): return "pa11y" def description(self): return "Print accessibility labels of all views in hierarchy of " def args(self): return [ fb.FBCommandArgument( arg="aView", type="UIView*", help="The view to print the hierarchy of.", default="(id)[[UIApplication sharedApplication] keyWindow]", ) ] def run(self, arguments, options): forceStartAccessibilityServer() printAccessibilityHierarchy(arguments[0]) class FBPrintAccessibilityIdentifiers(fb.FBCommand): def name(self): return "pa11yi" def description(self): return "Print accessibility identifiers of all views in hierarchy of " def args(self): return [ fb.FBCommandArgument( arg="aView", type="UIView*", help="The view to print the hierarchy of.", default="(id)[[UIApplication sharedApplication] keyWindow]", ) ] def run(self, arguments, option): forceStartAccessibilityServer() printAccessibilityIdentifiersHierarchy(arguments[0]) class FBFindViewByAccessibilityLabelCommand(fb.FBCommand): def name(self): return "fa11y" def description(self): return ( "Find the views whose accessibility labels match labelRegex " "and puts the address of the first result on the clipboard." ) def args(self): return [ fb.FBCommandArgument( arg="labelRegex", type="string", help="The accessibility label regex to search the view hierarchy for.", ) ] def accessibilityGrepHierarchy(self, view, needle): a11yLabel = accessibilityLabel(view) # if we don't have any accessibility string - we should have some children if int(a11yLabel.GetValue(), 16) == 0: # We call private method that gives back all visible accessibility children # for view iOS 10 and higher if fb.evaluateBooleanExpression( "[UIView respondsToSelector:@selector(_accessibilityElementsAndContainersDescendingFromViews:options:sorted:)]" ): accessibilityElements = fb.evaluateObjectExpression( "[UIView _accessibilityElementsAndContainersDescendingFromViews:@[(id)%s] options:0 sorted:NO]" % view ) else: accessibilityElements = fb.evaluateObjectExpression( "[[[UIApplication sharedApplication] keyWindow] _accessibilityElementsInContainer:0 topLevel:%s includeKB:0]" % view ) accessibilityElementsCount = fb.evaluateIntegerExpression( "[%s count]" % accessibilityElements ) for index in range(0, accessibilityElementsCount): subview = fb.evaluateObjectExpression( "[%s objectAtIndex:%i]" % (accessibilityElements, index) ) self.accessibilityGrepHierarchy(subview, needle) elif re.match( r".*" + needle + ".*", a11yLabel.GetObjectDescription(), re.IGNORECASE ): classDesc = objHelpers.className(view) print( "({} {}) {}".format(classDesc, view, a11yLabel.GetObjectDescription()) ) # First element that is found is copied to clipboard if not self.foundElement: self.foundElement = True cmd = 'echo %s | tr -d "\n" | pbcopy' % view os.system(cmd) def run(self, arguments, options): forceStartAccessibilityServer() rootView = fb.evaluateObjectExpression( "[[UIApplication sharedApplication] keyWindow]" ) self.foundElement = False self.accessibilityGrepHierarchy(rootView, arguments[0]) def isRunningInSimulator(): return ( fb.evaluateExpressionValue("(id)[[UIDevice currentDevice] model]") .GetObjectDescription() .lower() .find("simulator") >= 0 ) or ( fb.evaluateExpressionValue("(id)[[UIDevice currentDevice] name]") .GetObjectDescription() .lower() .find("simulator") >= 0 ) def forceStartAccessibilityServer(): # We try to start accessibility server only if we don't have needed method active if not fb.evaluateBooleanExpression( "[UIView instancesRespondToSelector:@selector(_accessibilityElementsInContainer:)]" ): # Starting accessibility server is different for simulator and device if isRunningInSimulator(): fb.evaluateEffect( "[[UIApplication sharedApplication] accessibilityActivate]" ) else: fb.evaluateEffect( "[[[UIApplication sharedApplication] _accessibilityBundlePrincipalClass] _accessibilityStartServer]" ) def accessibilityLabel(view): # using Apple private API to get real value of accessibility string for element. return fb.evaluateExpressionValue( "(id)[%s accessibilityAttributeValue:%i]" % (view, ACCESSIBILITY_LABEL_KEY), False, ) def accessibilityIdentifier(view): return fb.evaluateExpressionValue( "(id)[{} accessibilityIdentifier]".format(view), False ) def accessibilityElements(view): if fb.evaluateBooleanExpression( "[UIView instancesRespondToSelector:@selector(accessibilityElements)]" ): a11yElements = fb.evaluateExpression( "(id)[%s accessibilityElements]" % view, False ) if int(a11yElements, 16) != 0: return a11yElements if fb.evaluateBooleanExpression( "[%s respondsToSelector:@selector(_accessibleSubviews)]" % view ): return fb.evaluateExpression("(id)[%s _accessibleSubviews]" % (view), False) else: return fb.evaluateObjectExpression( "[[[UIApplication sharedApplication] keyWindow] _accessibilityElementsInContainer:0 topLevel:%s includeKB:0]" % view ) def printAccessibilityHierarchy(view, indent=0): a11yLabel = accessibilityLabel(view) classDesc = objHelpers.className(view) indentString = " | " * indent # if we don't have any accessibility string - we should have some children if int(a11yLabel.GetValue(), 16) == 0: print(indentString + ("{} {}".format(classDesc, view))) # We call private method that gives back all visible accessibility children # for view a11yElements = accessibilityElements(view) accessibilityElementsCount = int( fb.evaluateExpression("(int)[%s count]" % a11yElements) ) for index in range(0, accessibilityElementsCount): subview = fb.evaluateObjectExpression( "[%s objectAtIndex:%i]" % (a11yElements, index) ) printAccessibilityHierarchy(subview, indent + 1) else: print( indentString + ("({} {}) {}".format(classDesc, view, a11yLabel.GetObjectDescription())) ) def printAccessibilityIdentifiersHierarchy(view, indent=0): a11yIdentifier = accessibilityIdentifier(view) classDesc = objHelpers.className(view) indentString = " | " * indent # if we don't have any accessibility identifier - we should have some children if int(a11yIdentifier.GetValue(), 16) == 0: print(indentString + ("{} {}".format(classDesc, view))) # We call private method that gives back all visible accessibility children # for view a11yElements = accessibilityElements(view) accessibilityElementsCount = int( fb.evaluateExpression("(int)[%s count]" % a11yElements) ) for index in range(0, accessibilityElementsCount): subview = fb.evaluateObjectExpression( "[%s objectAtIndex:%i]" % (a11yElements, index) ) printAccessibilityIdentifiersHierarchy(subview, indent + 1) else: print( indentString + ( "({} {}) {}".format( classDesc, view, a11yIdentifier.GetObjectDescription() ) ) ) ================================================ FILE: commands/FBAutoLayoutCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import fbchisellldbbase as fb import fbchisellldbviewhelpers as viewHelpers import lldb def lldbcommands(): return [ FBPrintAutolayoutTrace(), FBAutolayoutBorderAmbiguous(), FBAutolayoutUnborderAmbiguous(), ] class FBPrintAutolayoutTrace(fb.FBCommand): def name(self): return "paltrace" def description(self): return ( "Print the Auto Layout trace for the given view. " "Defaults to the key window." ) def args(self): return [ fb.FBCommandArgument( arg="view", type="UIView *", help="The view to print the Auto Layout trace for.", default="(id)[[UIApplication sharedApplication] keyWindow]", ) ] def run(self, arguments, options): view = fb.evaluateInputExpression(arguments[0]) opt = fb.evaluateBooleanExpression( "[UIView instancesRespondToSelector:@selector(_autolayoutTraceRecursively:)]" ) traceCall = "_autolayoutTraceRecursively:1" if opt else "_autolayoutTrace" print(fb.describeObject("[{} {}]".format(view, traceCall))) def setBorderOnAmbiguousViewRecursive(view, width, color): if not fb.evaluateBooleanExpression( "[(id)%s isKindOfClass:(Class)[UIView class]]" % view ): return isAmbiguous = fb.evaluateBooleanExpression("(BOOL)[%s hasAmbiguousLayout]" % view) if isAmbiguous: layer = viewHelpers.convertToLayer(view) fb.evaluateEffect("[%s setBorderWidth:(CGFloat)%s]" % (layer, width)) fb.evaluateEffect( "[%s setBorderColor:(CGColorRef)[(id)[UIColor %sColor] CGColor]]" % (layer, color) ) subviews = fb.evaluateExpression("(id)[%s subviews]" % view) subviewsCount = int(fb.evaluateExpression("(int)[(id)%s count]" % subviews)) if subviewsCount > 0: for i in range(0, subviewsCount): subview = fb.evaluateExpression("(id)[%s objectAtIndex:%i]" % (subviews, i)) setBorderOnAmbiguousViewRecursive(subview, width, color) class FBAutolayoutBorderAmbiguous(fb.FBCommand): def name(self): return "alamborder" def description(self): return "Put a border around views with an ambiguous layout" def options(self): return [ fb.FBCommandArgument( short="-c", long="--color", arg="color", type="string", default="red", help="A color name such as 'red', 'green', 'magenta', etc.", ), fb.FBCommandArgument( short="-w", long="--width", arg="width", type="CGFloat", default=2.0, help="Desired width of border.", ), ] def run(self, arguments, options): keyWindow = fb.evaluateExpression( "(id)[[UIApplication sharedApplication] keyWindow]" ) setBorderOnAmbiguousViewRecursive(keyWindow, options.width, options.color) lldb.debugger.HandleCommand("caflush") class FBAutolayoutUnborderAmbiguous(fb.FBCommand): def name(self): return "alamunborder" def description(self): return "Removes the border around views with an ambiguous layout" def run(self, arguments, options): keyWindow = fb.evaluateExpression( "(id)[[UIApplication sharedApplication] keyWindow]" ) setBorderOnAmbiguousViewRecursive(keyWindow, 0, "red") lldb.debugger.HandleCommand("caflush") ================================================ FILE: commands/FBClassDump.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import string import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as runtimeHelpers def lldbcommands(): return [FBPrintMethods(), FBPrintProperties(), FBPrintBlock()] class FBPrintMethods(fb.FBCommand): def name(self): return "pmethods" def description(self): return "Print the class and instance methods of a class." def options(self): return [ fb.FBCommandArgument( short="-a", long="--address", arg="showaddr", help="Print the implementation address of the method", default=False, boolean=True, ), fb.FBCommandArgument( short="-i", long="--instance", arg="insmethod", help="Print the instance methods", default=False, boolean=True, ), fb.FBCommandArgument( short="-c", long="--class", arg="clsmethod", help="Print the class methods", default=False, boolean=True, ), fb.FBCommandArgument( short="-n", long="--name", arg="clsname", help="Take the argument as class name", default=False, boolean=True, ), ] def args(self): return [ fb.FBCommandArgument( arg="instance or class", type="instance or Class", help="an Objective-C Class.", ) ] def run(self, arguments, options): cls = getClassFromArgument(arguments[0], options.clsname) if options.clsmethod: print("Class Methods:") printClassMethods(cls, options.showaddr) if options.insmethod: print("\nInstance Methods:") printInstanceMethods(cls, options.showaddr) if not options.clsmethod and not options.insmethod: print("Class Methods:") printClassMethods(cls, options.showaddr) print("\nInstance Methods:") printInstanceMethods(cls, options.showaddr) class FBPrintProperties(fb.FBCommand): def name(self): return "pproperties" def description(self): return "Print the properties of an instance or Class" def options(self): return [ fb.FBCommandArgument( short="-n", long="--name", arg="clsname", help="Take the argument as class name", default=False, boolean=True, ) ] def args(self): return [ fb.FBCommandArgument( arg="instance or class", type="instance or Class", help="an Objective-C Class.", ) ] def run(self, arguments, options): cls = getClassFromArgument(arguments[0], options.clsname) printProperties(cls) class FBPrintBlock(fb.FBCommand): def name(self): return "pblock" def description(self): return "Print the block`s implementation address and signature" def args(self): return [ fb.FBCommandArgument(arg="block", help="The block object you want to print") ] def run(self, arguments, options): block = arguments[0] # http://clang.llvm.org/docs/Block-ABI-Apple.html tmpString = """ enum { BLOCK_HAS_COPY_DISPOSE = (1 << 25), BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code BLOCK_IS_GLOBAL = (1 << 28), BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE BLOCK_HAS_SIGNATURE = (1 << 30), }; struct Block_literal_1 { void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor_1 { unsigned long int reserved; // NULL unsigned long int size; // sizeof(struct Block_literal_1) // optional helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) void (*dispose_helper)(void *src); // IFF (1<<25) // required ABI.2010.3.16 const char *signature; // IFF (1<<30) } *descriptor; // imported variables }; struct Block_literal_1 real = *((__bridge struct Block_literal_1 *)$block); NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary]; [dict setObject:(id)[NSNumber numberWithLong:(long)real.invoke] forKey:@"invoke"]; if (real.flags & BLOCK_HAS_SIGNATURE) { char *signature; if (real.flags & BLOCK_HAS_COPY_DISPOSE) { signature = (char *)(real.descriptor)->signature; } else { signature = (char *)(real.descriptor)->copy_helper; } NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:signature]; NSMutableArray *types = [NSMutableArray array]; [types addObject:(id)[NSString stringWithUTF8String:(char *)[sig methodReturnType]]]; for (NSUInteger i = 0; i < sig.numberOfArguments; i++) { char *type = (char *)[sig getArgumentTypeAtIndex:i]; [types addObject:(id)[NSString stringWithUTF8String:type]]; } [dict setObject:types forKey:@"signature"]; } RETURN(dict); """ command = string.Template(tmpString).substitute(block=block) json = fb.evaluate(command) signature = json["signature"] if not signature: print("Imp: " + hex(json["invoke"])) return sigStr = "{} ^(".format(decode(signature[0])) # the block`s implementation always take the block as it`s first argument, so we ignore it sigStr += ", ".join([decode(m) for m in signature[2:]]) sigStr += ");" print("Imp: " + hex(json["invoke"]) + " Signature: " + sigStr) # helpers def isClassObject(arg): return runtimeHelpers.class_isMetaClass(runtimeHelpers.object_getClass(arg)) def getClassFromArgument(arg, is_classname): cls = arg if is_classname: cls = runtimeHelpers.objc_getClass(cls) if not int(cls, 16): raise Exception('Class "{}" not found'.format(arg)) else: if not isClassObject(cls): cls = runtimeHelpers.object_getClass(cls) if not isClassObject(cls): raise Exception( "Invalid argument. Please specify an instance or a Class." ) return cls def printInstanceMethods(cls, showaddr=False, prefix="-"): methods = getMethods(cls) if not methods: print("No methods were found") for m in methods: if showaddr: print(prefix + " " + m.prettyPrintString() + " " + str(m.imp)) else: print(prefix + " " + m.prettyPrintString()) def printClassMethods(cls, showaddr=False): printInstanceMethods(runtimeHelpers.object_getClass(cls), showaddr, "+") def printProperties(cls, showvalue=False): props = getProperties(cls) for p in props: print(p.prettyPrintString()) def decode(code): encodeMap = { "c": "char", "i": "int", "s": "short", "l": "long", "q": "long long", "C": "unsigned char", "I": "unsigned int", "S": "unsigned short", "L": "unsigned long", "Q": "unsigned long long", "f": "float", "d": "double", "B": "bool", "v": "void", "*": "char *", "@": "id", "#": "Class", ":": "SEL", } ret = code if code in encodeMap: ret = encodeMap[code] elif ret[0:1] == "@": if ret[1:2] == "?": # @? represent a block ret = code elif ret[2:3] == "<": # @"" ret = "id" + ret[2:-1].replace("><", ", ") else: ret = ret[2:-1] + " *" elif ret[0:1] == "^": ret = decode(ret[1:]) + " *" return ret # Notice that evaluateExpression doesn't work with variable arguments. such as -[NSString stringWithFormat:] # I remove the "free(methods)" because it would cause evaluateExpressionValue to raise exception some time. def getMethods(klass): tmpString = """ unsigned int outCount; Method *methods = (Method *)class_copyMethodList((Class)$cls, &outCount); NSMutableArray *result = (id)[NSMutableArray array]; for (int i = 0; i < outCount; i++) { NSMutableDictionary *m = (id)[NSMutableDictionary dictionary]; SEL name = (SEL)method_getName(methods[i]); [m setObject:(id)NSStringFromSelector(name) forKey:@"name"]; char * encoding = (char *)method_getTypeEncoding(methods[i]); [m setObject:(id)[NSString stringWithUTF8String:encoding] forKey:@"type_encoding"]; NSMutableArray *types = (id)[NSMutableArray array]; NSInteger args = (NSInteger)method_getNumberOfArguments(methods[i]); for (int idx = 0; idx < args; idx++) { char *type = (char *)method_copyArgumentType(methods[i], idx); [types addObject:(id)[NSString stringWithUTF8String:type]]; } [m setObject:types forKey:@"parameters_type"]; char *ret_type = (char *)method_copyReturnType(methods[i]); [m setObject:(id)[NSString stringWithUTF8String:ret_type] forKey:@"return_type"]; long imp = (long)method_getImplementation(methods[i]); [m setObject:[NSNumber numberWithLongLong:imp] forKey:@"implementation"]; [result addObject:m]; } RETURN(result); """ command = string.Template(tmpString).substitute(cls=klass) methods = fb.evaluate(command) return [Method(m) for m in methods] class Method: def __init__(self, json): self.name = json["name"] self.type_encoding = json["type_encoding"] self.parameters_type = json["parameters_type"] self.return_type = json["return_type"] self.imp = self.toHex(json["implementation"]) def prettyPrintString(self): argnum = len(self.parameters_type) names = self.name.split(":") # the argnum count must be bigger then 2, index 0 for self, index 1 for SEL for i in range(2, argnum): arg_type = self.parameters_type[i] names[i - 2] = names[i - 2] + ":(" + decode(arg_type) + ")arg" + str(i - 2) string = " ".join(names) return "({}){}".format(decode(self.return_type), string) def toHex(self, addr): return hex(addr) def __str__(self): return ( " " + self.name + " --- " + self.type + " --- " + self.imp ) def getProperties(klass): tmpString = """ NSMutableArray *result = (id)[NSMutableArray array]; unsigned int count; objc_property_t *props = (objc_property_t *)class_copyPropertyList((Class)$cls, &count); for (int i = 0; i < count; i++) { NSMutableDictionary *dict = (id)[NSMutableDictionary dictionary]; char *name = (char *)property_getName(props[i]); [dict setObject:(id)[NSString stringWithUTF8String:name] forKey:@"name"]; char *attrstr = (char *)property_getAttributes(props[i]); [dict setObject:(id)[NSString stringWithUTF8String:attrstr] forKey:@"attributes_string"]; NSMutableDictionary *attrsDict = (id)[NSMutableDictionary dictionary]; unsigned int pcount; objc_property_attribute_t *attrs = (objc_property_attribute_t *)property_copyAttributeList(props[i], &pcount); for (int i = 0; i < pcount; i++) { NSString *name = (id)[NSString stringWithUTF8String:(char *)attrs[i].name]; NSString *value = (id)[NSString stringWithUTF8String:(char *)attrs[i].value]; [attrsDict setObject:value forKey:name]; } [dict setObject:attrsDict forKey:@"attributes"]; [result addObject:dict]; } RETURN(result); """ command = string.Template(tmpString).substitute(cls=klass) propsJson = fb.evaluate(command) return [Property(m) for m in propsJson] class Property: def __init__(self, json): self.name = json["name"] self.attributes_string = json["attributes_string"] self.attributes = json["attributes"] # https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW1 def prettyPrintString(self): attrs = [] if "N" in self.attributes: attrs.append("nonatomic") else: attrs.append("atomic") if "&" in self.attributes: attrs.append("strong") elif "C" in self.attributes: attrs.append("copy") elif "W" in self.attributes: attrs.append("weak") else: attrs.append("assign") if "R" in self.attributes: attrs.append("readonly") if "G" in self.attributes: attrs.append("getter={}".format(self.attributes["G"])) if "S" in self.attributes: attrs.append("setter={}".format(self.attributes["S"])) return "@property ({}) {} {};".format( ", ".join(attrs), decode(self.attributes["T"]), self.name ) ================================================ FILE: commands/FBComponentCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import fbchisellldbbase as fb import fbchisellldbviewhelpers as viewHelpers def lldbcommands(): return [ FBComponentsDebugCommand(), FBComponentsPrintCommand(), FBComponentsReflowCommand(), ] class FBComponentsDebugCommand(fb.FBCommand): def name(self): return "dcomponents" def description(self): return "Set debugging options for components." def options(self): return [ fb.FBCommandArgument( short="-s", long="--set", arg="set", help="Set debug mode for components", boolean=True, ), fb.FBCommandArgument( short="-u", long="--unset", arg="unset", help="Unset debug mode for components", boolean=True, ), ] def run(self, arguments, options): print("Debug mode for ComponentKit is deprecated; use Flipper instead.") class FBComponentsPrintCommand(fb.FBCommand): def name(self): return "pcomponents" def description(self): return ( "Print a recursive description of components found starting from ." ) def args(self): return [ fb.FBCommandArgument( arg="aView", type="UIView* or CKComponent*", help="The view or component from which the search for components begins.", default="(id)[[UIApplication sharedApplication] keyWindow]", ) ] def run(self, arguments, options): view = fb.evaluateInputExpression(arguments[0]) if not viewHelpers.isView(view): # assume it's a CKComponent view = fb.evaluateExpression("((CKComponent *)%s).viewContext.view" % view) print( fb.describeObject( "[CKComponentHierarchyDebugHelper componentHierarchyDescriptionForView:(UIView *)" + view + "]" ) ) class FBComponentsReflowCommand(fb.FBCommand): def name(self): return "rcomponents" def description(self): return "Synchronously reflow and update all components." def run(self, arguments, options): fb.evaluateEffect("[CKComponentDebugController reflowComponents]") ================================================ FILE: commands/FBCopyCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. from __future__ import print_function import errno import os import time import fbchisellldbbase as fb import fbchisellldbobjecthelpers as objectHelpers import lldb def lldbcommands(): return [FBCopyCommand()] def _copyFromURL(url, preferredFilename, noOpen): data = fb.evaluateObjectExpression( "(id)[NSData dataWithContentsOfURL:(id){}]".format(url) ) defaultFilename = fb.describeObject( "(id)[[{} pathComponents] lastObject]".format(url) ) _copyFromData(data, defaultFilename, preferredFilename, noOpen) def _copyFromData(data, defaultFilename, preferredFilename, noOpen): directory = "/tmp/chisel_copy/" path = directory + (preferredFilename or defaultFilename) try: os.makedirs(directory) except OSError as e: if e.errno == errno.EEXIST and os.path.isdir(directory): pass else: raise startAddress = fb.evaluateExpression("(void *)[(id)" + data + " bytes]") length = fb.evaluateExpression("(NSUInteger)[(id)" + data + " length]") address = int(startAddress, 16) length = int(length) if not (address or length): print("Could not get data.") return process = lldb.debugger.GetSelectedTarget().GetProcess() error = lldb.SBError() mem = process.ReadMemory(address, length, error) if error is not None and str(error) != "success": print(error) else: with open(path, "wb") as file: file.write(mem) file.close() print(path) if not noOpen: os.system("open " + path) def _copy(target, preferredFilename, noOpen): target = "(" + target + ")" if objectHelpers.isKindOfClass(target, "NSURL"): _copyFromURL(target, preferredFilename, noOpen) elif objectHelpers.isKindOfClass(target, "NSData"): _copyFromData( target, time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime()) + ".data", preferredFilename, noOpen, ) else: print( "{} isn't supported. You can copy an NSURL or NSData.".format( objectHelpers.className(target) ) ) class FBCopyCommand(fb.FBCommand): def name(self): return "copy" def description(self): return "Copy data to your Mac." def options(self): return [ fb.FBCommandArgument( short="-f", long="--filename", arg="filename", help="The output filename.", ), fb.FBCommandArgument( short="-n", long="--no-open", arg="noOpen", boolean=True, default=False, help="Do not open the file.", ), ] def args(self): return [ fb.FBCommandArgument(arg="target", type="(id)", help="The object to copy.") ] def run(self, arguments, options): _copy(arguments[0], options.filename, options.noOpen) ================================================ FILE: commands/FBCounterCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # These set of commands provide a way to use counters in debug time. By using these counters, # you can track how many times your program takes a specific path. # # Sample Use Case: # Let's say you have a function that logs some messages from various parts of your code. # And you want to learn how many times logMessage is called on startup. # # 1. Add a breakpoint to the entry point of your program (e.g. main). # a. Add `zzz 10 printcounter` as an action. # b. Check "Automatically continue after evaluating actions" # 2. Add a breakpoint to the logMessage function. # a. Add `incrementcounter log` as an action. # b. Add `incrementcounter log_{} message` as an action. # c. Check "Automatically continue after evaluating actions" # 3. Run the program # # Format String: # It uses Python's string.Formatter to format strings. You can use placeholders here as you can in Python: # https://docs.python.org/3.4/library/string.html#string.Formatter.format # # Sample key_format_string: # "key_{}" (int)5 -> Will build the key string as "key_5" # Can be removed when Python 2 support is removed. from __future__ import print_function import fbchisellldbbase as fb counters = {} def lldbcommands(): return [ FBIncrementCounterCommand(), FBPrintCounterCommand(), FBPrintCountersCommand(), FBResetCounterCommand(), FBResetCountersCommand(), ] def generateKey(arguments): keyFormatString = arguments[1] keyArgs = [] for argument in arguments[2:]: if argument.startswith("("): value = fb.evaluateExpression(argument) else: value = fb.evaluateExpressionValue(argument).GetObjectDescription() if not value: value = fb.evaluateExpression(argument) keyArgs.append(value) return keyFormatString.format(*keyArgs).strip() # Increments the counter for the key. # (lldb) incrementcounter key_format_string key_args class FBIncrementCounterCommand(fb.FBCommand): def name(self): return "incrementcounter" def description(self): return "Increments the counter for the key." def run(self, arguments, options): key = generateKey(arguments) counters[key] = counters.get(key, 0) + 1 # Prints the counter for the key. # (lldb) printcounter key_format_string key_args # 0 class FBPrintCounterCommand(fb.FBCommand): def name(self): return "printcounter" def description(self): return "Prints the counter for the key." def run(self, arguments, options): key = generateKey(arguments) print(str(counters[key])) # Prints all the counters sorted by the keys. # (lldb) printcounters # key_1: 0 class FBPrintCountersCommand(fb.FBCommand): def name(self): return "printcounters" def description(self): return "Prints all the counters sorted by the keys." def run(self, arguments, options): keys = sorted(counters.keys()) for key in keys: print(key + ": " + str(counters[key])) # Resets the counter for the key. # (lldb) resetcounter key_format_string key_args class FBResetCounterCommand(fb.FBCommand): def name(self): return "resetcounter" def description(self): return "Resets the counter for the key." def run(self, arguments, options): key = generateKey(arguments) counters[key] = 0 # Resets all the counters. # (lldb) resetcounters class FBResetCountersCommand(fb.FBCommand): def name(self): return "resetcounters" def description(self): return "Resets all the counters." def run(self, arguments, options): counters.clear() ================================================ FILE: commands/FBDebugCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. # Can be removed when Python 2 support is removed. from __future__ import print_function import os import re import sys import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as objc import lldb def lldbcommands(): return [ FBWatchInstanceVariableCommand(), FBFrameworkAddressBreakpointCommand(), FBMethodBreakpointCommand(), FBMemoryWarningCommand(), FBFindInstancesCommand(), FBMethodBreakpointEnableCommand(), FBMethodBreakpointDisableCommand(), FBHeapFromCommand(), FBSequenceCommand(), ] class FBWatchInstanceVariableCommand(fb.FBCommand): def name(self): return "wivar" def description(self): return "Set a watchpoint for an object's instance variable." def args(self): return [ fb.FBCommandArgument( arg="object", type="id", help="Object expression to be evaluated." ), fb.FBCommandArgument( arg="ivarName", help="Name of the instance variable to watch." ), ] def run(self, arguments, options): commandForObject, ivarName = arguments objectAddress = int(fb.evaluateObjectExpression(commandForObject), 0) ivarOffsetCommand = '(ptrdiff_t)ivar_getOffset((void*)object_getInstanceVariable((id){}, "{}", 0))'.format( objectAddress, ivarName ) ivarOffset = int(fb.evaluateExpression(ivarOffsetCommand), 0) # A multi-statement command allows for variables scoped to the command, # not permanent in the session like $variables. ivarSizeCommand = ( "unsigned int size = 0;" 'char *typeEncoding = (char *)ivar_getTypeEncoding((void*)class_getInstanceVariable((Class)object_getClass((id){}), "{}"));' "(char *)NSGetSizeAndAlignment(typeEncoding, &size, 0);" "size" ).format(objectAddress, ivarName) ivarSize = int(fb.evaluateExpression(ivarSizeCommand), 0) error = lldb.SBError() watchpoint = lldb.debugger.GetSelectedTarget().WatchAddress( objectAddress + ivarOffset, ivarSize, False, True, error ) if error.Success(): print( "Remember to delete the watchpoint using: watchpoint delete {}".format( watchpoint.GetID() ) ) else: print("Could not create the watchpoint: {}".format(error.GetCString())) class FBFrameworkAddressBreakpointCommand(fb.FBCommand): def name(self): return "binside" def description(self): return "Set a breakpoint for a relative address within the framework/library that's currently running. This does the work of finding the offset for the framework/library and sliding your address accordingly." def args(self): return [ fb.FBCommandArgument( arg="address", type="string", help="Address within the currently running framework to set a breakpoint on.", ) ] def run(self, arguments, options): library_address = int(arguments[0], 0) address = int( lldb.debugger.GetSelectedTarget() .GetProcess() .GetSelectedThread() .GetSelectedFrame() .GetModule() .ResolveFileAddress(library_address) ) lldb.debugger.HandleCommand("breakpoint set --address {}".format(address)) class FBMethodBreakpointCommand(fb.FBCommand): def name(self): return "bmessage" def description(self): return "Set a breakpoint for a selector on a class, even if the class itself doesn't override that selector. It walks the hierarchy until it finds a class that does implement the selector and sets a conditional breakpoint there." def args(self): return [ fb.FBCommandArgument( arg="expression", type="string", help='Expression to set a breakpoint on, e.g. "-[MyView setFrame:]", "+[MyView awesomeClassMethod]" or "-[0xabcd1234 setFrame:]"', ) ] def run(self, arguments, options): expression = arguments[0] methodPattern = re.compile( r""" (?P[-+])? \[ (?P.*?) (?P\(.+\))? \s+ (?P.*) \] """, re.VERBOSE, ) match = methodPattern.match(expression) if not match: print("Failed to parse expression. Do you even Objective-C?!") return expressionForSelf = objc.functionPreambleExpressionForSelf() if not expressionForSelf: arch = objc.currentArch() print( "Your architecture, {}, is truly fantastic. However, I don't currently support it.".format( arch ) ) return methodTypeCharacter = match.group("scope") classNameOrExpression = match.group("target") category = match.group("category") selector = match.group("selector") methodIsClassMethod = methodTypeCharacter == "+" if methodIsClassMethod: methodTypeCharacter = "\+" else: # The default is instance method, and methodTypeCharacter # may not actually be '-'. methodTypeCharacter = "-" targetIsClass = False targetObject = fb.evaluateObjectExpression( "({})".format(classNameOrExpression), False ) if not targetObject: # If the expression didn't yield anything then it's likely a class. # Assume it is. We will check again that the class does actually # exist anyway. targetIsClass = True targetObject = fb.evaluateObjectExpression( "[{} class]".format(classNameOrExpression), False ) targetClass = fb.evaluateObjectExpression( "[{} class]".format(targetObject), False ) if not targetClass or int(targetClass, 0) == 0: print( 'Couldn\'t find a class from the expression "{}". Did you typo?'.format( classNameOrExpression ) ) return if methodIsClassMethod: targetClass = objc.object_getClass(targetClass) found = False nextClass = targetClass while not found and int(nextClass, 0) > 0: if classItselfImplementsSelector(nextClass, selector): found = True else: nextClass = objc.class_getSuperclass(nextClass) if not found: print( "There doesn't seem to be an implementation of {} in the class hierarchy. Made a boo boo with the selector name?".format( selector ) ) return breakpointClassName = objc.class_getName(nextClass) formattedCategory = category if category else "" breakpointFullName = "{}[{}{} {}]".format( methodTypeCharacter, breakpointClassName, formattedCategory, selector ) if targetIsClass: breakpointCondition = "(void*)object_getClass({}) == {}".format( expressionForSelf, targetClass ) else: breakpointCondition = "(void*){} == {}".format( expressionForSelf, targetObject ) print( "Setting a breakpoint at {} with condition {}".format( breakpointFullName, breakpointCondition ) ) if category: lldb.debugger.HandleCommand( 'breakpoint set --skip-prologue false --fullname "{}" --condition "{}"'.format( breakpointFullName, breakpointCondition ) ) else: breakpointPattern = r"{}\[{}(\(.+\))? {}\]".format( methodTypeCharacter, breakpointClassName, selector ) lldb.debugger.HandleCommand( 'breakpoint set --skip-prologue false --func-regex "{}" --condition "{}"'.format( breakpointPattern, breakpointCondition ) ) def classItselfImplementsSelector(klass, selector): thisMethod = objc.class_getInstanceMethod(klass, selector) if thisMethod == 0: return False superklass = objc.class_getSuperclass(klass) superMethod = objc.class_getInstanceMethod(superklass, selector) if thisMethod == superMethod: return False else: return True class FBMemoryWarningCommand(fb.FBCommand): def name(self): return "mwarning" def description(self): return "simulate a memory warning" def run(self, arguments, options): fb.evaluateEffect( "[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)]" ) def switchBreakpointState(expression, on): expression_pattern = re.compile(r"{}".format(expression), re.I) target = lldb.debugger.GetSelectedTarget() for breakpoint in target.breakpoint_iter(): if breakpoint.IsEnabled() != on and ( expression_pattern.search(str(breakpoint)) ): print(str(breakpoint)) breakpoint.SetEnabled(on) for location in breakpoint: if location.IsEnabled() != on and ( expression_pattern.search(str(location)) or expression == hex(location.GetLoadAddress()) ): print(str(location)) location.SetEnabled(on) class FBMethodBreakpointEnableCommand(fb.FBCommand): def name(self): return "benable" def description(self): return """ Enable a set of breakpoints for a regular expression Examples: * benable ***address*** benable 0x0000000104514dfc benable 0x183e23564 #use `benable *filename*` to switch all breakpoints in this file to `enable` benable SUNNetService.m #use `benable ***module(AppName)***` to switch all breakpoints in this module to `enable` benable UIKit benable Foundation """ def args(self): return [ fb.FBCommandArgument( arg="expression", type="string", help="Expression to enable breakpoint" ) ] def run(self, arguments, options): expression = arguments[0] switchBreakpointState(expression, True) class FBMethodBreakpointDisableCommand(fb.FBCommand): def name(self): return "bdisable" def description(self): return """ Disable a set of breakpoints for a regular expression Examples: * bdisable ***address*** bdisable 0x0000000104514dfc bdisable 0x183e23564 #use `bdisable *filename*` to switch all breakpoints in this file to `disable` bdisable SUNNetService.m #use `bdisable ***module(AppName)***` to switch all breakpoints in this module to `disable` bdisable UIKit bdisable Foundation """ def args(self): return [ fb.FBCommandArgument( arg="expression", type="string", help="Expression to disable breakpoint" ) ] def run(self, arguments, options): expression = arguments[0] switchBreakpointState(expression, False) class FBFindInstancesCommand(fb.FBCommand): def name(self): return "findinstances" def args(self): return [ fb.FBCommandArgument(arg="type", help="Class or protocol name"), fb.FBCommandArgument( arg="query", default=" ", # space is a hack to mark optional help="Query expression, uses NSPredicate syntax", ), ] def description(self): return """ Find instances of specified ObjC classes. This command scans memory and uses heuristics to identify instances of Objective-C classes. This includes Swift classes that descend from NSObject. Basic examples: findinstances UIScrollView findinstances *UIScrollView findinstances UIScrollViewDelegate These basic searches find instances of the given class or protocol. By default, subclasses of the class or protocol are included in the results. To find exact class instances, add a `*` prefix, for example: *UIScrollView. Advanced examples: # Find views that are either: hidden, invisible, or not in a window findinstances UIView hidden == true || alpha == 0 || window == nil # Find views that have either a zero width or zero height findinstances UIView layer.bounds.#size.width == 0 || layer.bounds.#size.height == 0 # Find leaf views that have no subviews findinstances UIView subviews.@count == 0 # Find dictionaries that have keys that might be passwords or passphrases findinstances NSDictionary any @allKeys beginswith 'pass' These examples make use of a filter. The filter is implemented with NSPredicate, see its documentaiton for more details. Basic NSPredicate expressions have relatively predicatable syntax. There are some exceptions as seen above, see https://github.com/facebook/chisel/wiki/findinstances. """ def lex(self, commandLine): # Can't use default shlex splitting because it strips quotes, which results # in invalid NSPredicate syntax. Split the input into type and rest (query). return commandLine.split(" ", 1) def run(self, arguments, options): if not self.loadChiselIfNecessary(): return if len(arguments) == 0 or not arguments[0].strip(): print( "Usage: findinstances []; Run `help findinstances`" ) return query = arguments[0] predicate = arguments[1].strip() # Escape double quotes and backslashes. predicate = re.sub('([\\"])', r"\\\1", predicate) call = '(void)PrintInstances("{}", "{}")'.format(query, predicate) fb.evaluateExpressionValue(call) def loadChiselIfNecessary(self): target = lldb.debugger.GetSelectedTarget() symbol_contexts = target.FindSymbols("PrintInstances", lldb.eSymbolTypeCode) if any(ctx.symbol.IsValid() for ctx in symbol_contexts): return True path = self.chiselLibraryPath() if not os.path.exists(path): print("Chisel library missing: " + path) return False module = fb.evaluateExpressionValue('(void*)dlopen("{}", 2)'.format(path)) if module.unsigned != 0 or target.module["Chisel"]: return True # `errno` is a macro that expands to a call to __error(). In development, # lldb was not getting a correct value for `errno`, so `__error()` is used. errno = fb.evaluateExpressionValue("*(int*)__error()").value error = fb.evaluateExpressionValue("(char*)dlerror()") if errno == 50: # KERN_CODESIGN_ERROR from print("Error loading Chisel: Code signing failure; Must re-run codesign") elif error.unsigned != 0: print("Error loading Chisel: " + error.summary) elif errno != 0: error = fb.evaluateExpressionValue("(char*)strerror({})".format(errno)) if error.unsigned != 0: print("Error loading Chisel: " + error.summary) else: print("Error loading Chisel (errno {})".format(errno)) else: print("Unknown error loading Chisel") return False def chiselLibraryPath(self): # script os.environ['CHISEL_LIBRARY_PATH'] = '/path/to/custom/Chisel' path = os.getenv("CHISEL_LIBRARY_PATH") if path and os.path.exists(path): return path source_path = sys.modules[__name__].__file__ source_dir = os.path.dirname(source_path) # ugh: ../.. is to back out of commands/, then back out of libexec/ return os.path.join(source_dir, "..", "..", "lib", "Chisel.framework", "Chisel") class FBHeapFromCommand(fb.FBCommand): def name(self): return "heapfrom" def description(self): return "Show all nested heap pointers contained within a given variable." def run(self, arguments, options): # This command is like `expression --synthetic-type false`, # except only showing nested heap references. var = self.context.frame.var(arguments[0]) if not var or not var.IsValid(): self.result.SetError('No variable named "{}"'.format(arguments[0])) return # Use the actual underlying structure of the variable, # not the human friendly (synthetic) one. root = var.GetNonSyntheticValue() # Traversal of SBValue tree to get leaf nodes, which is where heap # pointers will be. leafs = [] queue = [root] while queue: node = queue.pop(0) if node.num_children == 0: leafs.append(node) else: queue += [node.GetChildAtIndex(i) for i in range(node.num_children)] pointers = {} for node in leafs: # Assumption: an addr that has no value means a pointer. if node.addr and not node.value: pointers[node.load_addr] = node.path options = lldb.SBExpressionOptions() options.SetLanguage(lldb.eLanguageTypeC) def isHeap(addr): lookup = "(int)malloc_size({})".format(addr) return self.context.frame.EvaluateExpression(lookup, options).unsigned != 0 allocations = (addr for addr in pointers if isHeap(addr)) for addr in allocations: print( "0x{addr:x} {path}".format(addr=addr, path=pointers[addr]), file=self.result, ) if not allocations: print("No heap addresses found", file=self.result) class FBSequenceCommand(fb.FBCommand): def name(self): return "sequence" def description(self): return "Run commands in sequence, stopping on any error." def lex(self, commandLine): return [command.strip() for command in commandLine.split(";")] def run(self, arguments, options): # arguments contains the raw command first, followed by the split commands. if len(arguments) == 1: return commands = filter(None, arguments[1:]) interpreter = lldb.debugger.GetCommandInterpreter() # Complete one command before running the next one in the sequence. Disable # async to do this. Also, save the current async value to restore it later. asyncFlag = lldb.debugger.GetAsync() lldb.debugger.SetAsync(False) for command in commands[:-1]: success = self.run_command(interpreter, command) if not success: lldb.debugger.SetAsync(asyncFlag) return # Restore original async value. lldb.debugger.SetAsync(asyncFlag) # If the last command is `continue`, call Continue() on the process # instead. This is done because HandleCommand('continue') has strange # behavior, while calling Continue() works as expected. last = commands[-1] if self.is_continue(interpreter, last): self.context.process.Continue() else: self.run_command(interpreter, last) def run_command(self, interpreter, command): ret = lldb.SBCommandReturnObject() interpreter.HandleCommand(command, ret) if ret.GetOutput(): print(ret.GetOutput().strip(), file=self.result) if ret.Succeeded(): return True self.result.SetError(ret.GetError()) self.result.SetStatus(ret.GetStatus()) return False def is_continue(self, interpreter, command): ret = lldb.SBCommandReturnObject() interpreter.ResolveCommand(command, ret) return ret.GetOutput() == "process continue" ================================================ FILE: commands/FBDelay.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. from threading import Timer import fbchisellldbbase as fb import lldb def lldbcommands(): return [FBDelay()] class FBDelay(fb.FBCommand): def name(self): return "zzz" def description(self): return "Executes specified lldb command after delay." def args(self): return [ fb.FBCommandArgument( arg="delay in seconds", type="float", help="time to wait before executing specified command", ), fb.FBCommandArgument( arg="lldb command", type="string", help="another lldb command to execute after specified delay", default="process interrupt", ), ] def run(self, arguments, options): lldb.debugger.SetAsync(True) lldb.debugger.HandleCommand("process continue") delay = float(arguments[0]) command = str(arguments[1]) t = Timer(delay, lambda: self.runDelayed(command)) t.start() def runDelayed(self, command): lldb.debugger.HandleCommand("process interrupt") lldb.debugger.HandleCommand(command) ================================================ FILE: commands/FBDisplayCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as runtimeHelpers import fbchisellldbviewcontrollerhelpers as viewControllerHelpers import fbchisellldbviewhelpers as viewHelpers import lldb def lldbcommands(): return [ FBCoreAnimationFlushCommand(), FBDrawBorderCommand(), FBRemoveBorderCommand(), FBMaskViewCommand(), FBUnmaskViewCommand(), FBShowViewCommand(), FBHideViewCommand(), FBPresentViewControllerCommand(), FBDismissViewControllerCommand(), FBSlowAnimationCommand(), FBUnslowAnimationCommand(), ] class FBDrawBorderCommand(fb.FBCommand): colors = [ "black", "gray", "red", "green", "blue", "cyan", "yellow", "magenta", "orange", "purple", "brown", ] def name(self): return "border" def description(self): return "Draws a border around . Color and width can be optionally provided. Additionally depth can be provided in order to recursively border subviews." def args(self): return [ fb.FBCommandArgument( arg="viewOrLayer", type="UIView/NSView/CALayer *", help="The view/layer to border. NSViews must be layer-backed.", ) ] def options(self): return [ fb.FBCommandArgument( short="-c", long="--color", arg="color", type="string", default="red", help="A color name such as 'red', 'green', 'magenta', etc.", ), fb.FBCommandArgument( short="-w", long="--width", arg="width", type="CGFloat", default=2.0, help="Desired width of border.", ), fb.FBCommandArgument( short="-d", long="--depth", arg="depth", type="int", default=0, help="Number of levels of subviews to border. Each level gets a different color beginning with the provided or default color", ), ] def run(self, args, options): def setBorder(layer, width, color, colorClass): fb.evaluateEffect("[%s setBorderWidth:(CGFloat)%s]" % (layer, width)) fb.evaluateEffect( "[%s setBorderColor:(CGColorRef)[(id)[%s %sColor] CGColor]]" % (layer, colorClass, color) ) obj = fb.evaluateInputExpression(args[0]) depth = int(options.depth) isMac = runtimeHelpers.isMacintoshArch() color = options.color assert color in self.colors, "Color must be one of the following: {}".format( " ".join(self.colors) ) colorClassName = "UIColor" if isMac: colorClassName = "NSColor" if viewHelpers.isView(obj): prevLevel = 0 for view, level in viewHelpers.subviewsOfView(obj): if level > depth: break if prevLevel != level: color = self.nextColorAfterColor(color) prevLevel = level layer = viewHelpers.convertToLayer(view) setBorder(layer, options.width, color, colorClassName) else: # `obj` is not a view, make sure recursive bordering is not requested assert depth <= 0, ( "Recursive bordering is only supported for UIViews or NSViews" ) layer = viewHelpers.convertToLayer(obj) setBorder(layer, options.width, color, colorClassName) lldb.debugger.HandleCommand("caflush") def nextColorAfterColor(self, color): assert color in self.colors, "{} is not a supported color".format(color) return self.colors[(self.colors.index(color) + 1) % len(self.colors)] class FBRemoveBorderCommand(fb.FBCommand): def name(self): return "unborder" def description(self): return "Removes border around ." def options(self): return [ fb.FBCommandArgument( short="-d", long="--depth", arg="depth", type="int", default=0, help="Number of levels of subviews to unborder.", ) ] def args(self): return [ fb.FBCommandArgument( arg="viewOrLayer", type="UIView/NSView/CALayer *", help="The view/layer to unborder.", ) ] def run(self, args, options): def setUnborder(layer): fb.evaluateEffect("[%s setBorderWidth:(CGFloat)%s]" % (layer, 0)) obj = args[0] depth = int(options.depth) if viewHelpers.isView(obj): for view, level in viewHelpers.subviewsOfView(obj): if level > depth: break layer = viewHelpers.convertToLayer(view) setUnborder(layer) else: # `obj` is not a view, make sure recursive unbordering is not requested assert depth <= 0, ( "Recursive unbordering is only supported for UIViews or NSViews" ) layer = viewHelpers.convertToLayer(obj) setUnborder(layer) lldb.debugger.HandleCommand("caflush") class FBMaskViewCommand(fb.FBCommand): def name(self): return "mask" def description(self): return "Add a transparent rectangle to the window to reveal a possibly obscured or hidden view or layer's bounds" def args(self): return [ fb.FBCommandArgument( arg="viewOrLayer", type="UIView/NSView/CALayer *", help="The view/layer to mask.", ) ] def options(self): return [ fb.FBCommandArgument( short="-c", long="--color", arg="color", type="string", default="red", help="A color name such as 'red', 'green', 'magenta', etc.", ), fb.FBCommandArgument( short="-a", long="--alpha", arg="alpha", type="CGFloat", default=0.5, help="Desired alpha of mask.", ), ] def run(self, args, options): viewOrLayer = fb.evaluateObjectExpression(args[0]) viewHelpers.maskView(viewOrLayer, options.color, options.alpha) class FBUnmaskViewCommand(fb.FBCommand): def name(self): return "unmask" def description(self): return "Remove mask from a view or layer" def args(self): return [ fb.FBCommandArgument( arg="viewOrLayer", type="UIView/CALayer *", help="The view/layer to mask.", ) ] def run(self, args, options): viewOrLayer = fb.evaluateObjectExpression(args[0]) viewHelpers.unmaskView(viewOrLayer) class FBCoreAnimationFlushCommand(fb.FBCommand): def name(self): return "caflush" def description(self): return "Force Core Animation to flush. This will 'repaint' the UI but also may mess with ongoing animations." def run(self, arguments, options): viewHelpers.flushCoreAnimationTransaction() class FBShowViewCommand(fb.FBCommand): def name(self): return "show" def description(self): return "Show a view or layer." def args(self): return [ fb.FBCommandArgument( arg="viewOrLayer", type="UIView/NSView/CALayer *", help="The view/layer to show.", ) ] def run(self, args, options): viewHelpers.setViewHidden(args[0], False) class FBHideViewCommand(fb.FBCommand): def name(self): return "hide" def description(self): return "Hide a view or layer." def args(self): return [ fb.FBCommandArgument( arg="viewOrLayer", type="UIView/NSView/CALayer *", help="The view/layer to hide.", ) ] def run(self, args, options): viewHelpers.setViewHidden(args[0], True) class FBPresentViewControllerCommand(fb.FBCommand): def name(self): return "present" def description(self): return "Present a view controller." def args(self): return [ fb.FBCommandArgument( arg="viewController", type="UIViewController *", help="The view controller to present.", ) ] def run(self, args, option): viewControllerHelpers.presentViewController(args[0]) class FBDismissViewControllerCommand(fb.FBCommand): def name(self): return "dismiss" def description(self): return "Dismiss a presented view controller." def args(self): return [ fb.FBCommandArgument( arg="viewController", type="UIViewController *", help="The view controller to dismiss.", ) ] def run(self, args, option): viewControllerHelpers.dismissViewController(args[0]) class FBSlowAnimationCommand(fb.FBCommand): def name(self): return "slowanim" def description(self): return "Slows down animations. Works on the iOS Simulator and a device." def args(self): return [ fb.FBCommandArgument( arg="speed", type="float", default=0.1, help="Animation speed (default 0.1).", ) ] def run(self, args, option): viewHelpers.slowAnimation(args[0]) class FBUnslowAnimationCommand(fb.FBCommand): def name(self): return "unslowanim" def description(self): return "Turn off slow animations." def run(self, args, option): viewHelpers.slowAnimation() ================================================ FILE: commands/FBFindCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import os import re import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as objc import fbchisellldbviewcontrollerhelpers as vcHelpers import lldb def lldbcommands(): return [FBFindViewControllerCommand(), FBFindViewCommand(), FBTapLoggerCommand()] class FBFindViewControllerCommand(fb.FBCommand): def name(self): return "fvc" def description(self): return "Find the view controllers whose class names match classNameRegex and puts the address of first on the clipboard." def options(self): return [ fb.FBCommandArgument( short="-n", long="--name", arg="classNameRegex", type="string", help="The view-controller-class regex to search the view controller hierarchy for.", ), fb.FBCommandArgument( short="-v", long="--view", arg="view", type="UIView", help="This function will print the View Controller that owns this view.", ), ] def run(self, arguments, options): if options.classNameRegex and options.view: print("Do not set both the --name and --view flags") elif options.view: self.findOwningViewController(options.view) else: output = vcHelpers.viewControllerRecursiveDescription( "(id)[[[UIApplication sharedApplication] keyWindow] rootViewController]" ) searchString = ( options.classNameRegex if options.classNameRegex else arguments[0] ) printMatchesInViewOutputStringAndCopyFirstToClipboard(searchString, output) def findOwningViewController(self, object): while object: if self.isViewController(object): description = fb.evaluateExpressionValue(object).GetObjectDescription() print("Found the owning view controller.\n{}".format(description)) cmd = 'echo {} | tr -d "\n" | pbcopy'.format(object) os.system(cmd) return else: object = self.nextResponder(object) print("Could not find an owning view controller") @staticmethod def isViewController(object): command = "[(id){} isKindOfClass:[UIViewController class]]".format(object) isVC = fb.evaluateBooleanExpression(command) return isVC @staticmethod def nextResponder(object): command = "[((id){}) nextResponder]".format(object) nextResponder = fb.evaluateObjectExpression(command) try: if int(nextResponder, 0): return nextResponder else: return None except Exception: return None class FBFindViewCommand(fb.FBCommand): def name(self): return "fv" def description(self): return "Find the views whose class names match classNameRegex and puts the address of first on the clipboard." def args(self): return [ fb.FBCommandArgument( arg="classNameRegex", type="string", help="The view-class regex to search the view hierarchy for.", ) ] def run(self, arguments, options): output = fb.evaluateExpressionValue( "(id)[[[UIApplication sharedApplication] keyWindow] recursiveDescription]" ).GetObjectDescription() printMatchesInViewOutputStringAndCopyFirstToClipboard(arguments[0], output) def printMatchesInViewOutputStringAndCopyFirstToClipboard(needle, haystack): first = None for match in re.finditer( ".*<.*(" + needle + ")\\S*: (0x[0-9a-fA-F]*);.*", haystack, re.IGNORECASE ): view = match.groups()[-1] className = fb.evaluateExpressionValue( "(id)[(" + view + ") class]" ).GetObjectDescription() print("{} {}".format(view, className)) if first is None: first = view cmd = 'echo %s | tr -d "\n" | pbcopy' % view os.system(cmd) class FBTapLoggerCommand(fb.FBCommand): def name(self): return "taplog" def description(self): return "Log tapped view to the console." def run(self, arguments, options): parameterExpr = objc.functionPreambleExpressionForObjectParameterAtIndex(0) breakpoint = lldb.debugger.GetSelectedTarget().BreakpointCreateByName( "-[UIApplication sendEvent:]" ) breakpoint.SetCondition( "(int)[" + parameterExpr + " type] == 0 && (int)[[[" + parameterExpr + " allTouches] anyObject] phase] == 0" ) breakpoint.SetOneShot(True) callback_name = taplog_callback.__qualname__ # Import the callback so LLDB can see it lldb.debugger.HandleCommand( "script from %s import %s" % (__name__, callback_name) ) breakpoint.SetScriptCallbackFunction(callback_name) lldb.debugger.SetAsync(True) lldb.debugger.HandleCommand("continue") def taplog_callback(frame, bp_loc, internal_dict): parameterExpr = objc.functionPreambleExpressionForObjectParameterAtIndex(0) print( "Gesture Recognizers:\n{}".format( fb.describeObject( "[[[%s allTouches] anyObject] gestureRecognizers]" % (parameterExpr) ) ) ) print( "View:\n{}".format( fb.describeObject("[[[%s allTouches] anyObject] view]" % (parameterExpr)) ) ) # We don't want to proceed event (click on button for example), so we just skip it lldb.debugger.HandleCommand("thread return") ================================================ FILE: commands/FBFlickerCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import os import sys import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as runtimeHelpers import fbchisellldbviewhelpers as viewHelpers import lldb def lldbcommands(): return [FBFlickerViewCommand(), FBViewSearchCommand()] class FBFlickerViewCommand(fb.FBCommand): def name(self): return "flicker" def description(self): return "Quickly show and hide a view to quickly help visualize where it is." def args(self): return [ fb.FBCommandArgument( arg="viewOrLayer", type="UIView/NSView*", help="The view to flicker." ) ] def run(self, arguments, options): object = fb.evaluateObjectExpression(arguments[0]) isHidden = fb.evaluateBooleanExpression("[" + object + " isHidden]") shouldHide = not isHidden for _ in range(0, 2): viewHelpers.setViewHidden(object, shouldHide) viewHelpers.setViewHidden(object, isHidden) class FBViewSearchCommand(fb.FBCommand): def name(self): return "vs" def description(self): return "Interactively search for a view by walking the hierarchy." def args(self): return [ fb.FBCommandArgument(arg="view", type="UIView*", help="The view to border.") ] def run(self, arguments, options): print( "\nUse the following and (q) to quit.\n(w) move to superview\n(s) move to first subview\n(a) move to previous sibling\n(d) move to next sibling\n(p) print the hierarchy\n" ) object = fb.evaluateInputExpression(arguments[0]) walker = FlickerWalker(object) walker.run() class FlickerWalker: def __init__(self, startView): self.setCurrentView(startView) def run(self): self.keepRunning = True initialAsync = lldb.debugger.GetAsync() # Needed so XCode doesn't hang if tap on Continue while lldb # is waiting for user input in 'vs' mode lldb.debugger.SetAsync(True) while self.keepRunning: charRead = sys.stdin.readline().rstrip("\n") self.inputCallback(charRead) else: lldb.debugger.SetAsync(initialAsync) def inputCallback(self, input): oldView = self.currentView if input == "q": cmd = 'echo %s | tr -d "\n" | pbcopy' % oldView os.system(cmd) print( "\nI hope " + oldView + " was what you were looking for. I put it on your clipboard." ) viewHelpers.unmaskView(oldView) self.keepRunning = False elif input == "w": v = superviewOfView(self.currentView) if not v: print("There is no superview. Where are you trying to go?!") self.setCurrentView(v, oldView) elif input == "s": v = firstSubviewOfView(self.currentView) if not v: print("\nThe view has no subviews.\n") self.setCurrentView(v, oldView) elif input == "d": v = nthSiblingOfView(self.currentView, -1) if v == oldView: print("\nThere are no sibling views to this view.\n") self.setCurrentView(v, oldView) elif input == "a": v = nthSiblingOfView(self.currentView, 1) if v == oldView: print("\nThere are no sibling views to this view.\n") self.setCurrentView(v, oldView) elif input == "p": recursionName = "recursiveDescription" isMac = runtimeHelpers.isMacintoshArch() if isMac: recursionName = "_subtreeDescription" print(fb.describeObject("[(id){} {}]".format(oldView, recursionName))) else: print("\nI really have no idea what you meant by '" + input + "'... =\\\n") def setCurrentView(self, view, oldView=None): if view: self.currentView = view if oldView: viewHelpers.unmaskView(oldView) viewHelpers.maskView(self.currentView, "red", "0.4") print(fb.describeObject(view)) def superviewOfView(view): superview = fb.evaluateObjectExpression("[" + view + " superview]") if int(superview, 16) == 0: return None return superview def subviewsOfView(view): return fb.evaluateObjectExpression("[" + view + " subviews]") def firstSubviewOfView(view): subviews = subviewsOfView(view) numViews = fb.evaluateIntegerExpression("[(id)" + subviews + " count]") if numViews == 0: return None else: return fb.evaluateObjectExpression("[" + subviews + " objectAtIndex:0]") def nthSiblingOfView(view, n): subviews = subviewsOfView(superviewOfView(view)) numViews = fb.evaluateIntegerExpression("[(id)" + subviews + " count]") idx = fb.evaluateIntegerExpression( "[(id)" + subviews + " indexOfObject:" + view + "]" ) newIdx = idx + n while newIdx < 0: newIdx += numViews newIdx = newIdx % numViews return fb.evaluateObjectExpression( "[(id)" + subviews + " objectAtIndex:" + str(newIdx) + "]" ) ================================================ FILE: commands/FBImportCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import fbchisellldbbase as fb import lldb def lldbcommands(): return [ImportUIKitModule()] class ImportUIKitModule(fb.FBCommand): def name(self): return "uikit" def description(self): return "Imports the UIKit module to get access to the types while in lldb." def run(self, arguments, options): frame = ( lldb.debugger.GetSelectedTarget() .GetProcess() .GetSelectedThread() .GetSelectedFrame() ) fb.importModule(frame, "UIKit") ================================================ FILE: commands/FBInvocationCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import re import fbchisellldbbase as fb import lldb def lldbcommands(): return [FBPrintInvocation()] class FBPrintInvocation(fb.FBCommand): def name(self): return "pinvocation" def description(self): return "Print the stack frame, receiver, and arguments of the current invocation. It will fail to print all arguments if any arguments are variadic (varargs).\n\nNOTE: Sadly this is currently only implemented on x86." def options(self): return [ fb.FBCommandArgument( short="-a", long="--all", arg="all", default=False, boolean=True, help="Specify to print the entire stack instead of just the current frame.", ) ] def run(self, arguments, options): target = lldb.debugger.GetSelectedTarget() if not re.match(r".*i386.*", target.GetTriple()): print("Only x86 is currently supported (32-bit iOS Simulator or Mac OS X).") return thread = target.GetProcess().GetSelectedThread() if options.all: for frame in thread: printInvocationForFrame(frame) print("---------------------------------") else: frame = thread.GetSelectedFrame() printInvocationForFrame(frame) def printInvocationForFrame(frame): print(frame) symbolName = frame.GetSymbol().GetName() if not re.match(r"[-+]\s*\[.*\]", symbolName): return self = findArgAtIndexFromStackFrame(frame, 0) cmd = findArgAtIndexFromStackFrame(frame, 1) commandForSignature = ( "[(id)" + self + " methodSignatureForSelector:(char *)sel_getName((SEL)" + cmd + ")]" ) signatureValue = fb.evaluateExpressionValue("(id)" + commandForSignature) if ( signatureValue.GetError() is not None and str(signatureValue.GetError()) != "success" ): print( "My sincerest apologies. I couldn't find a method signature for the selector." ) return signature = signatureValue.GetValue() arg0 = stackStartAddressInSelectedFrame(frame) commandForInvocation = ( "[NSInvocation _invocationWithMethodSignature:(id)" + signature + " frame:((void *)" + str(arg0) + ")]" ) invocation = fb.evaluateExpression("(id)" + commandForInvocation) if invocation: prettyPrintInvocation(frame, invocation) else: print(frame) def stackStartAddressInSelectedFrame(frame): # Determine if the %ebp register has already had the # stack register pushed into it (always the first instruction) frameSymbol = frame.GetSymbolContext(0).GetSymbol() frameStartAddress = frameSymbol.GetStartAddress().GetLoadAddress( lldb.debugger.GetSelectedTarget() ) currentPC = frame.GetPC() offset = currentPC - frameStartAddress if offset == 0: return int(frame.EvaluateExpression("($esp + 4)").GetValue()) elif offset == 1: return int(frame.EvaluateExpression("($esp + 8)").GetValue()) else: return int(frame.EvaluateExpression("($ebp + 8)").GetValue()) def findArgAtIndexFromStackFrame(frame, index): return fb.evaluateExpression( "*(int *)" + str(findArgAdressAtIndexFromStackFrame(frame, index)) ) def findArgAdressAtIndexFromStackFrame(frame, index): arg0 = stackStartAddressInSelectedFrame(frame) arg = arg0 + 4 * index return arg def prettyPrintInvocation(frame, invocation): object = fb.evaluateExpression("(id)[(id)" + invocation + " target]") description = fb.evaluateExpressionValue("(id)" + invocation).GetObjectDescription() argDescriptions = description.splitlines(True)[4:] print("NSInvocation: " + invocation) print("self: " + fb.evaluateExpression("(id)" + object)) if len(argDescriptions) > 0: print( "\n" + str(len(argDescriptions)) + " Arguments:" if len(argDescriptions) > 1 else "\nArgument:" ) index = 2 for argDescription in argDescriptions: s = re.sub(r"argument [0-9]+: ", "", argDescription) address = findArgAdressAtIndexFromStackFrame(frame, index) encoding = s.split(" ")[0] description = " ".join(s.split(" ")[1:]) readableString = argumentAsString(frame, address, encoding) if readableString: print(readableString) else: if encoding[0] == "{": encoding = encoding[1:] print( ( hex(address) + ", address of " + encoding + " " + description ).strip() ) index += 1 def argumentAsString(frame, address, encoding): # noqa C901 if encoding[0] == "{": encoding = encoding[1:] encodingMap = { "c": "char", "i": "int", "s": "short", "l": "long", "q": "long long", "C": "unsigned char", "I": "unsigned int", "S": "unsigned short", "L": "unsigned long", "Q": "unsigned long long", "f": "float", "d": "double", "B": "bool", "v": "void", "*": "char *", "@": "id", "#": "Class", ":": "SEL", } pointers = "" while encoding[0] == "^": pointers += "*" encoding = encoding[1:] type = None if encoding in encodingMap: type = encodingMap[encoding] if type and pointers: type = type + " " + pointers if not type: # Handle simple structs: {CGPoint=ff}, {CGSize=ff}, # {CGRect={CGPoint=ff}{CGSize=ff}} if encoding[0] == "{": encoding = encoding[1:] type = re.sub(r"=.*", "", encoding) if pointers: type += " " + pointers if type: value = frame.EvaluateExpression("*(" + type + " *)" + str(address)) if value.GetError() is None or str(value.GetError()) == "success": description = None if encoding == "@": description = value.GetObjectDescription() if not description: description = value.GetValue() if not description: description = value.GetSummary() if description: return type + ": " + description return None ================================================ FILE: commands/FBPrintCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import os import re import subprocess import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as runtimeHelpers import fbchisellldbviewcontrollerhelpers as vcHelpers import fbchisellldbviewhelpers as viewHelpers import lldb def lldbcommands(): return [ FBPrintViewHierarchyCommand(), FBPrintViewControllerHierarchyCommand(), FBPrintIsExecutingInAnimationBlockCommand(), FBPrintInheritanceHierarchy(), FBPrintUpwardResponderChain(), FBPrintOnscreenTableView(), FBPrintOnscreenTableViewCells(), FBPrintInternals(), FBPrintInstanceVariable(), FBPrintKeyPath(), FBPrintApplicationDocumentsPath(), FBPrintApplicationBundlePath(), FBPrintData(), FBPrintTargetActions(), FBPrintJSON(), FBPrintSwiftJSON(), FBPrintAsCurl(), FBPrintToClipboard(), FBPrintObjectInObjc(), ] class FBPrintViewHierarchyCommand(fb.FBCommand): def name(self): return "pviews" def description(self): return "Print the recursion description of ." def options(self): return [ fb.FBCommandArgument( short="-u", long="--up", arg="upwards", boolean=True, default=False, help="Print only the hierarchy directly above the view, up to its window.", ), fb.FBCommandArgument( short="-d", long="--depth", arg="depth", type="int", default="0", help="Print only to a given depth. 0 indicates infinite depth.", ), fb.FBCommandArgument( short="-w", long="--window", arg="window", type="int", default="0", help='Specify the window to print a description of. Check which windows exist with "po (id)[[UIApplication sharedApplication] windows]".', ), fb.FBCommandArgument( short="-s", long="--short", arg="short", boolean=True, default=False, help="Print a short description of the view", ), fb.FBCommandArgument( short="-m", long="--medium", arg="medium", boolean=True, default=False, help="Print a medium description of the view", ), ] def args(self): return [ fb.FBCommandArgument( arg="aView", type="UIView*/NSView*", help="The view to print the description of.", default="__keyWindow_dynamic__", ) ] def run(self, arguments, options): maxDepth = int(options.depth) window = int(options.window) isMac = runtimeHelpers.isMacintoshArch() if window > 0: if isMac: arguments[0] = ( "(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:" + str(window) + "] contentView]" ) else: arguments[0] = ( "(id)[[[UIApplication sharedApplication] windows] objectAtIndex:" + str(window) + "]" ) elif arguments[0] == "__keyWindow_dynamic__": if isMac: arguments[0] = ( "(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentView]" ) else: arguments[0] = "(id)[[UIApplication sharedApplication] keyWindow]" if options.upwards: view = arguments[0] description = viewHelpers.upwardsRecursiveDescription(view, maxDepth) if description: print(description) else: print( "Failed to walk view hierarchy. Make sure you pass a view, not any other kind of object or expression." ) else: printingMethod = "recursiveDescription" if isMac: printingMethod = "_subtreeDescription" description = fb.evaluateExpressionValue( "(id)[" + arguments[0] + " " + printingMethod + "]" ).GetObjectDescription() if maxDepth > 0: separator = re.escape(" | ") prefixToRemove = separator * maxDepth + " " description += "\n" description = re.sub(r"%s.*\n" % (prefixToRemove), r"", description) if options.short: toRemove = ":.*(?:\n|$)" description = re.sub(toRemove, r">\n", description) elif options.medium: toRemove = ";.*(?:\n|$)" description = re.sub(toRemove, r">\n", description) print(description) class FBPrintViewControllerHierarchyCommand(fb.FBCommand): def name(self): return "pvc" def description(self): return "Print the recursion description of ." def args(self): return [ fb.FBCommandArgument( arg="aViewController", type="UIViewController*", help="The view controller to print the description of.", default="__keyWindow_rootVC_dynamic__", ) ] def run(self, arguments, options): isMac = runtimeHelpers.isMacintoshArch() if arguments[0] == "__keyWindow_rootVC_dynamic__": if fb.evaluateBooleanExpression( "[UIViewController respondsToSelector:@selector(_printHierarchy)]" ): print(fb.describeObject("[UIViewController _printHierarchy]")) return arguments[0] = ( "(id)[(id)[[UIApplication sharedApplication] keyWindow] rootViewController]" ) if isMac: arguments[0] = ( "(id)[[[[NSApplication sharedApplication] windows] objectAtIndex:0] contentViewController]" ) print(vcHelpers.viewControllerRecursiveDescription(arguments[0])) class FBPrintIsExecutingInAnimationBlockCommand(fb.FBCommand): def name(self): return "panim" def description(self): return ( "Prints if the code is currently execution with a UIView animation block." ) def run(self, arguments, options): lldb.debugger.HandleCommand("p (BOOL)[UIView _isInAnimationBlock]") def _printIterative(initialValue, generator): indent = 0 for currentValue in generator(initialValue): print(" | " * indent + currentValue) indent += 1 class FBPrintInheritanceHierarchy(fb.FBCommand): def name(self): return "pclass" def description(self): return "Print the inheritance starting from an instance of any class." def args(self): return [ fb.FBCommandArgument( arg="object", type="id", help="The instance to examine." ) ] def run(self, arguments, options): _printIterative(arguments[0], _inheritanceHierarchy) def _inheritanceHierarchy(instanceOfAClass): instanceAddress = fb.evaluateExpression(instanceOfAClass) instanceClass = fb.evaluateExpression("(id)[(id)" + instanceAddress + " class]") while int(instanceClass, 16): yield fb.evaluateExpressionValue(instanceClass).GetObjectDescription() instanceClass = fb.evaluateExpression( "(id)[(id)" + instanceClass + " superclass]" ) class FBPrintUpwardResponderChain(fb.FBCommand): def name(self): return "presponder" def description(self): return "Print the responder chain starting from a specific responder." def args(self): return [ fb.FBCommandArgument( arg="startResponder", type="UIResponder *", help="The responder to use to start walking the chain.", ) ] def run(self, arguments, options): startResponder = fb.evaluateInputExpression(arguments[0]) isMac = runtimeHelpers.isMacintoshArch() responderClass = "UIResponder" if isMac: responderClass = "NSResponder" if not fb.evaluateBooleanExpression( "(BOOL)[(id)" + startResponder + " isKindOfClass:[" + responderClass + " class]]" ): print("Whoa, " + startResponder + " is not a " + responderClass + ". =(") return _printIterative(startResponder, _responderChain) def _responderChain(startResponder): responderAddress = fb.evaluateExpression(startResponder) while int(responderAddress, 16): yield fb.evaluateExpressionValue(responderAddress).GetObjectDescription() responderAddress = fb.evaluateExpression( "(id)[(id)" + responderAddress + " nextResponder]" ) def tableViewInHierarchy(): viewDescription = fb.evaluateExpressionValue( "(id)[(id)[[UIApplication sharedApplication] keyWindow] recursiveDescription]" ).GetObjectDescription() searchView = None # Try to find an instance of classPattern = re.compile(r"UITableView: (0x[0-9a-fA-F]+);") for match in re.finditer(classPattern, viewDescription): searchView = match.group(1) break # Try to find a direct subclass if not searchView: subclassPattern = re.compile(r"(0x[0-9a-fA-F]+); baseClass = UITableView;") for match in re.finditer(subclassPattern, viewDescription): searchView = match.group(1) break # SLOW: check every pointer in town if not searchView: pattern = re.compile(r"(0x[0-9a-fA-F]+)[;>]") for view in re.findall(pattern, viewDescription): if fb.evaluateBooleanExpression( "[" + view + " isKindOfClass:(id)[UITableView class]]" ): searchView = view break return searchView class FBPrintOnscreenTableView(fb.FBCommand): def name(self): return "ptv" def description(self): return "Print the highest table view in the hierarchy." def run(self, arguments, options): tableView = tableViewInHierarchy() if tableView: viewValue = fb.evaluateExpressionValue(tableView) print(viewValue.GetObjectDescription()) cmd = 'echo %s | tr -d "\n" | pbcopy' % tableView os.system(cmd) else: print("Sorry, chump. I couldn't find a table-view. :'(") class FBPrintOnscreenTableViewCells(fb.FBCommand): def name(self): return "pcells" def description(self): return "Print the visible cells of the highest table view in the hierarchy." def run(self, arguments, options): tableView = tableViewInHierarchy() print( fb.evaluateExpressionValue( "(id)[(id)" + tableView + " visibleCells]" ).GetObjectDescription() ) class FBPrintInternals(fb.FBCommand): def name(self): return "pinternals" def description(self): return "Show the internals of an object by dereferencing it as a pointer." def args(self): return [ fb.FBCommandArgument( arg="object", type="id", help="Object expression to be evaluated." ) ] def options(self): return [ fb.FBCommandArgument( arg="appleWay", short="-a", long="--apple", boolean=True, default=False, help="Print ivars the apple way", ) ] def run(self, arguments, options): object = fb.evaluateObjectExpression(arguments[0]) if options.appleWay: if fb.evaluateBooleanExpression( "[{} respondsToSelector:@selector(_ivarDescription)]".format(object) ): command = "po [{} _ivarDescription]".format(object) else: print("Sorry, but it seems Apple dumped the _ivarDescription method") return else: objectClass = fb.evaluateExpressionValue( "(id)[(id)(" + object + ") class]" ).GetObjectDescription() command = "p *(({} *)((id){}))".format(objectClass, object) lldb.debugger.HandleCommand(command) class FBPrintInstanceVariable(fb.FBCommand): def name(self): return "pivar" def description(self): return "Print the value of an object's named instance variable." def args(self): return [ fb.FBCommandArgument( arg="object", type="id", help="Object expression to be evaluated." ), fb.FBCommandArgument( arg="ivarName", help="Name of instance variable to print." ), ] def run(self, arguments, options): object = fb.evaluateInputExpression(arguments[0]) ivarName = arguments[1] objectClass = fb.evaluateExpressionValue( "(id)[(" + object + ") class]" ).GetObjectDescription() ivarTypeCommand = '((char *)ivar_getTypeEncoding((void*)object_getInstanceVariable((id){}, "{}", 0)))[0]'.format( object, ivarName ) ivarTypeEncodingFirstChar = fb.evaluateExpression(ivarTypeCommand) result = fb.evaluateExpressionValue( "(({} *)({}))->{}".format(objectClass, object, ivarName) ) print( result.GetObjectDescription() if "@" in ivarTypeEncodingFirstChar else result ) class FBPrintKeyPath(fb.FBCommand): def name(self): return "pkp" def description(self): return "Print out the value of the key path expression using -valueForKeyPath:" def args(self): return [ fb.FBCommandArgument( arg="keypath", type="NSString *", help="The keypath to print" ) ] def run(self, arguments, options): command = arguments[0] if len(command.split(".")) == 1: lldb.debugger.HandleCommand("po " + command) else: objectToMessage, keypath = command.split(".", 1) object = fb.evaluateObjectExpression(objectToMessage) print( fb.describeObject('[{} valueForKeyPath:@"{}"]'.format(object, keypath)) ) class FBPrintApplicationDocumentsPath(fb.FBCommand): def name(self): return "pdocspath" def description(self): return "Print application's 'Documents' directory path." def options(self): return [ fb.FBCommandArgument( short="-o", long="--open", arg="open", boolean=True, default=False, help="open in Finder", ) ] def run(self, arguments, options): # in iOS SDK NSDocumentDirectory == 9 NSUserDomainMask == 1 NSDocumentDirectory = "9" NSUserDomainMask = "1" path = fb.evaluateExpressionValue( "(NSString*)[NSSearchPathForDirectoriesInDomains(" + NSDocumentDirectory + ", " + NSUserDomainMask + ", YES) lastObject]" ) pathString = "{}".format(path).split('"')[1] cmd = 'echo {} | tr -d "\n" | pbcopy'.format(pathString) os.system(cmd) print(pathString) if options.open: os.system("open " + pathString) class FBPrintApplicationBundlePath(fb.FBCommand): def name(self): return "pbundlepath" def description(self): return "Print application's bundle directory path." def options(self): return [ fb.FBCommandArgument( short="-o", long="--open", arg="open", boolean=True, default=False, help="open in Finder", ) ] def run(self, arguments, options): path = fb.evaluateExpressionValue( "(NSString*)[[NSBundle mainBundle] bundlePath]" ) pathString = "{}".format(path).split('"')[1] cmd = 'echo {} | tr -d "\n" | pbcopy'.format(pathString) os.system(cmd) print(pathString) if options.open: os.system("open " + pathString) class FBPrintData(fb.FBCommand): def name(self): return "pdata" def description(self): return ( "Print the contents of NSData object as string.\n" "Supported encodings:\n" "- ascii,\n" "- utf8,\n" "- utf16, unicode,\n" "- utf16l (Little endian),\n" "- utf16b (Big endian),\n" "- utf32,\n" "- utf32l (Little endian),\n" "- utf32b (Big endian),\n" "- latin1, iso88591 (88591),\n" "- latin2, iso88592 (88592),\n" "- cp1251 (1251),\n" "- cp1252 (1252),\n" "- cp1253 (1253),\n" "- cp1254 (1254),\n" "- cp1250 (1250)," ) def options(self): return [ fb.FBCommandArgument( arg="encoding", short="-e", long="--encoding", type="string", help="Used encoding (default utf-8).", default="utf-8", ) ] def args(self): return [ fb.FBCommandArgument(arg="data", type="NSData *", help="NSData object.") ] def run(self, arguments, option): # noqa C901 # Normalize encoding. encoding_text = option.encoding.lower().replace(" -", "") enc = 4 # Default encoding UTF-8. if encoding_text == "ascii": enc = 1 elif encoding_text == "utf8": enc = 4 elif ( encoding_text == "latin1" or encoding_text == "88591" or encoding_text == "iso88591" ): enc = 5 elif ( encoding_text == "latin2" or encoding_text == "88592" or encoding_text == "iso88592" ): enc = 9 elif encoding_text == "unicode" or encoding_text == "utf16": enc = 10 elif encoding_text == "1251" or encoding_text == "cp1251": enc = 11 elif encoding_text == "1252" or encoding_text == "cp1252": enc = 12 elif encoding_text == "1253" or encoding_text == "cp1253": enc = 13 elif encoding_text == "1254" or encoding_text == "cp1254": enc = 14 elif encoding_text == "1250" or encoding_text == "cp1250": enc = 15 elif encoding_text == "utf16b": enc = 0x90000100 elif encoding_text == "utf16l": enc = 0x94000100 elif encoding_text == "utf32": enc = 0x8C000100 elif encoding_text == "utf32b": enc = 0x98000100 elif encoding_text == "utf32l": enc = 0x9C000100 print( fb.describeObject( "[[NSString alloc] initWithData:{} encoding:{}]".format( arguments[0], enc ) ) ) class FBPrintTargetActions(fb.FBCommand): def name(self): return "pactions" def description(self): return "Print the actions and targets of a control." def args(self): return [ fb.FBCommandArgument( arg="control", type="UIControl *", help="The control to inspect the actions of.", ) ] def run(self, arguments, options): control = fb.evaluateInputExpression(arguments[0]) targets = fb.evaluateObjectExpression( "[[{control} allTargets] allObjects]".format(control=control) ) targetCount = fb.evaluateIntegerExpression( "[{targets} count]".format(targets=targets) ) for index in range(0, targetCount): target = fb.evaluateObjectExpression( "[{targets} objectAtIndex:{index}]".format(targets=targets, index=index) ) actions = fb.evaluateObjectExpression( "[{control} actionsForTarget:{target} forControlEvent:0]".format( control=control, target=target ) ) targetDescription = fb.evaluateExpressionValue( "(id){target}".format(target=target) ).GetObjectDescription() actionsDescription = fb.evaluateExpressionValue( '(id)[{actions} componentsJoinedByString:@", "]'.format(actions=actions) ).GetObjectDescription() print( "{target}: {actions}".format( target=targetDescription, actions=actionsDescription ) ) class FBPrintJSON(fb.FBCommand): def name(self): return "pjson" def description(self): return "Print JSON representation of NSDictionary or NSArray object" def options(self): return [ fb.FBCommandArgument( arg="plain", short="-p", long="--plain", boolean=True, default=False, help="Plain JSON", ) ] def args(self): return [ fb.FBCommandArgument( arg="object", type="id", help="The NSDictionary or NSArray object to print", ) ] def run(self, arguments, options): objectToPrint = fb.evaluateInputExpression(arguments[0]) pretty = 1 if options.plain is None else 0 jsonData = fb.evaluateObjectExpression( "[NSJSONSerialization dataWithJSONObject:(id){} options:{} error:nil]".format( objectToPrint, pretty ) ) jsonString = fb.evaluateExpressionValue( "(NSString*)[[NSString alloc] initWithData:(id){} encoding:4]".format( jsonData ) ).GetObjectDescription() print(jsonString) class FBPrintSwiftJSON(fb.FBCommand): def name(self): return "psjson" def description(self): return "Print JSON representation of Swift Dictionary or Swift Array object" def options(self): return [ fb.FBCommandArgument( arg="plain", short="-p", long="--plain", boolean=True, default=False, help="Plain JSON", ) ] def args(self): return [ fb.FBCommandArgument( arg="object", type="NSObject *", help="The Swift Dictionary or Swift Array to print", ) ] def run(self, arguments, options): # Convert to NSObject first to allow for objc runtime to process it objectToPrint = fb.evaluateInputExpression( "{obj} as NSObject".format(obj=arguments[0]) ) pretty = 1 if options.plain is None else 0 jsonData = fb.evaluateObjectExpression( "[NSJSONSerialization dataWithJSONObject:(NSObject*){} options:{} error:nil]".format( objectToPrint, pretty ) ) jsonString = fb.evaluateExpressionValue( "(NSString*)[[NSString alloc] initWithData:(NSObject*){} encoding:4]".format( jsonData ) ).GetObjectDescription() print(jsonString) class FBPrintAsCurl(fb.FBCommand): def name(self): return "pcurl" def description(self): return "Print the NSURLRequest (HTTP) as curl command." def options(self): return [ fb.FBCommandArgument( short="-e", long="--embed-data", arg="embed", boolean=True, default=False, help="Embed request data as base64.", ) ] def args(self): return [ fb.FBCommandArgument( arg="request", type="NSURLRequest*/NSMutableURLRequest*", help="The request to convert to the curl command.", ) ] def generateTmpFilePath(self): return "/tmp/curl_data_{}".format( fb.evaluateExpression( "(NSTimeInterval)[NSDate timeIntervalSinceReferenceDate]" ) ) def run(self, arguments, options): request = fb.evaluateInputExpression(arguments[0]) HTTPHeaderSring = "" HTTPMethod = fb.evaluateExpressionValue( "(id)[{} HTTPMethod]".format(request) ).GetObjectDescription() URL = fb.evaluateExpressionValue( "(id)[{} URL]".format(request) ).GetObjectDescription() timeout = fb.evaluateExpression( "(NSTimeInterval)[{} timeoutInterval]".format(request) ) HTTPHeaders = fb.evaluateObjectExpression( "(id)[{} allHTTPHeaderFields]".format(request) ) HTTPHeadersCount = fb.evaluateIntegerExpression( "[{} count]".format(HTTPHeaders) ) allHTTPKeys = fb.evaluateObjectExpression("[{} allKeys]".format(HTTPHeaders)) for index in range(0, HTTPHeadersCount): key = fb.evaluateObjectExpression( "[{} objectAtIndex:{}]".format(allHTTPKeys, index) ) keyDescription = fb.evaluateExpressionValue( "(id){}".format(key) ).GetObjectDescription() value = fb.evaluateExpressionValue( "(id)[(id){} objectForKey:{}]".format(HTTPHeaders, key) ).GetObjectDescription() if len(HTTPHeaderSring) > 0: HTTPHeaderSring += " " HTTPHeaderSring += '-H "{}: {}"'.format(keyDescription, value) HTTPData = fb.evaluateObjectExpression("[{} HTTPBody]".format(request)) dataFile = None dataAsString = None if fb.evaluateIntegerExpression("[{} length]".format(HTTPData)) > 0: if options.embed: if fb.evaluateIntegerExpression( "[{} respondsToSelector:@selector(base64EncodedStringWithOptions:)]".format( HTTPData ) ): dataAsString = fb.evaluateExpressionValue( "(id)[(id){} base64EncodedStringWithOptions:0]".format(HTTPData) ).GetObjectDescription() else: print("This version of OS doesn't supports base64 data encoding") return False elif not runtimeHelpers.isIOSDevice(): dataFile = self.generateTmpFilePath() if not fb.evaluateBooleanExpression( '(BOOL)[{} writeToFile:@"{}" atomically:NO]'.format( HTTPData, dataFile ) ): print("Can't write data to file {}".format(dataFile)) return False else: print( 'HTTPBody data for iOS Device is supported only with "--embed-data" flag' ) return False commandString = "" if dataAsString is not None and len(dataAsString) > 0: dataFile = self.generateTmpFilePath() commandString += 'echo "{}" | base64 -D -o "{}" && '.format( dataAsString, dataFile ) commandString += "curl -X {} --connect-timeout {}".format(HTTPMethod, timeout) if len(HTTPHeaderSring) > 0: commandString += " " + HTTPHeaderSring if dataFile is not None: commandString += ' --data-binary @"{}"'.format(dataFile) commandString += ' "{}"'.format(URL) print(commandString) class FBPrintToClipboard(fb.FBCommand): def name(self): return "pbcopy" def description(self): return "Print object and copy output to clipboard" def args(self): return [ fb.FBCommandArgument(arg="object", type="id", help="The object to print") ] def run(self, arguments, options): lldbOutput = fb.evaluateExpressionValue( "[{changeset} description]".format(changeset=arguments[0]) ).GetObjectDescription() process = subprocess.Popen( "pbcopy", env={"LANG": "en_US.UTF-8"}, stdin=subprocess.PIPE ) process.communicate(lldbOutput.encode("utf-8")) print("Object copied to clipboard") class FBPrintObjectInObjc(fb.FBCommand): def name(self): return "poobjc" def description(self): return 'Print the expression result, with the expression run in an ObjC++ context. (Shortcut for "expression -O -l ObjC++ -- " )' def args(self): return [ fb.FBCommandArgument( arg="expression", help="ObjC expression to evaluate and print." ) ] def run(self, arguments, options): expression = arguments[0] lldb.debugger.HandleCommand("expression -O -l ObjC++ -- " + expression) ================================================ FILE: commands/FBTextInputCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import fbchisellldbbase as fb import fbchisellldbviewhelpers as viewHelpers ACCESSIBILITY_ID = 0 REPLACEMENT_TEXT = 1 INPUT_TEXT = 0 def lldbcommands(): return [FBInputTexByAccessibilityIdCommand(), FBInputTexToFirstResponderCommand()] class FBInputTexByAccessibilityIdCommand(fb.FBCommand): def name(self): return "settext" def description(self): return "Set text on a view by accessibility id." def args(self): return [ fb.FBCommandArgument( arg="accessibilityId", type="string", help="The accessibility ID of the input view.", ), fb.FBCommandArgument( arg="replacementText", type="string", help="The text to set." ), ] def run(self, arguments, options): self.findView( rootView(), arguments[ACCESSIBILITY_ID], arguments[REPLACEMENT_TEXT] ) def findView(self, view, searchIdentifier, replacementText): views = subviewsOfView(view) for index in range(0, viewsCount(views)): subview = subviewAtIndex(views, index) self.findView(subview, searchIdentifier, replacementText) else: identifier = accessibilityIdentifier(view) if isEqualToString(identifier, searchIdentifier): setTextInView(view, replacementText) class FBInputTexToFirstResponderCommand(fb.FBCommand): def name(self): return "setinput" def description(self): return "Input text into text field or text view that is first responder." def args(self): return [ fb.FBCommandArgument( arg="inputText", type="string", help="The text to input." ) ] def run(self, arguments, options): self.findFirstResponder(rootView(), arguments[INPUT_TEXT]) def findFirstResponder(self, view, replacementText): views = subviewsOfView(view) if isFirstResponder(view): setTextInView(view, replacementText) else: for index in range(0, viewsCount(views)): subview = subviewAtIndex(views, index) self.findFirstResponder(subview, replacementText) # Some helpers def rootView(): return fb.evaluateObjectExpression("[[UIApplication sharedApplication] keyWindow]") def subviewsOfView(view): return fb.evaluateObjectExpression("[%s subviews]" % view) def subviewAtIndex(views, index): return fb.evaluateObjectExpression("[%s objectAtIndex:%i]" % (views, index)) def viewsCount(views): return int(fb.evaluateExpression("(int)[%s count]" % views)) def accessibilityIdentifier(view): return fb.evaluateObjectExpression("[%s accessibilityIdentifier]" % view) def isEqualToString(identifier, needle): return fb.evaluateBooleanExpression( '[%s isEqualToString:@"%s"]' % (identifier, needle) ) def setTextInView(view, text): fb.evaluateObjectExpression('[%s setText:@"%s"]' % (view, text)) viewHelpers.flushCoreAnimationTransaction() def isFirstResponder(view): return fb.evaluateBooleanExpression("[%s isFirstResponder]" % view) ================================================ FILE: commands/FBVisualizationCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import errno import os import time import fbchisellldbbase as fb import fbchisellldbobjecthelpers as objectHelpers import lldb def lldbcommands(): return [FBVisualizeCommand()] def _showImage(commandForImage): imageDirectory = "/tmp/xcode_debug_images/" imageName = time.strftime("%Y-%m-%d-%H-%M-%S", time.gmtime()) + ".png" imagePath = imageDirectory + imageName try: os.makedirs(imageDirectory) except OSError as e: if e.errno == errno.EEXIST and os.path.isdir(imageDirectory): pass else: raise toPNG = "(id)UIImagePNGRepresentation((id){})".format(commandForImage) imageDataAddress = fb.evaluateExpressionValue(toPNG, tryAllThreads=True).GetValue() imageBytesStartAddress = fb.evaluateExpression( "(void *)[(id)" + imageDataAddress + " bytes]" ) imageBytesLength = fb.evaluateExpression( "(NSUInteger)[(id)" + imageDataAddress + " length]" ) address = int(imageBytesStartAddress, 16) length = int(imageBytesLength) if not (address or length): print("Could not get image data.") return process = lldb.debugger.GetSelectedTarget().GetProcess() error = lldb.SBError() mem = process.ReadMemory(address, length, error) if error is not None and str(error) != "success": print(error) else: with open(imagePath, "wb") as imgFile: imgFile.write(mem) os.system("open " + imagePath) def _colorIsCGColorRef(color): color = "(CGColorRef)(" + color + ")" result = fb.evaluateExpressionValue( "(unsigned long)CFGetTypeID({color}) == (unsigned long)CGColorGetTypeID()".format( color=color ) ) if result.GetError() is not None and str(result.GetError()) != "success": print("got error: {}".format(result)) return False else: isCFColor = result.GetValueAsUnsigned() != 0 return isCFColor def _showColor(color): color = "(" + color + ")" colorToUse = color isCF = _colorIsCGColorRef(color) if isCF: colorToUse = "[[UIColor alloc] initWithCGColor:(CGColorRef){}]".format(color) else: isCI = objectHelpers.isKindOfClass(color, "CIColor") if isCI: colorToUse = "[UIColor colorWithCIColor:(CIColor *){}]".format(color) imageSize = 58 fb.evaluateEffect( "UIGraphicsBeginImageContextWithOptions((CGSize)CGSizeMake({imageSize}, {imageSize}), NO, 0.0)".format( imageSize=imageSize ) ) fb.evaluateEffect("[(id){} setFill]".format(colorToUse)) fb.evaluateEffect( "UIRectFill((CGRect)CGRectMake(0.0, 0.0, {imageSize}, {imageSize}))".format( imageSize=imageSize ) ) result = fb.evaluateExpressionValue( "(UIImage *)UIGraphicsGetImageFromCurrentImageContext()" ) if result.GetError() is not None and str(result.GetError()) != "success": print("got error {}".format(result)) print(result.GetError()) else: image = result.GetValue() _showImage(image) fb.evaluateEffect("UIGraphicsEndImageContext()") def _showLayer(layer): layer = "(" + layer + ")" size = "((CGRect)[(id)" + layer + " bounds]).size" width = float(fb.evaluateExpression("(CGFloat)(" + size + ".width)")) height = float(fb.evaluateExpression("(CGFloat)(" + size + ".height)")) if width == 0.0 or height == 0.0: print( "Nothing to see here - the size of this element is {} x {}.".format( width, height ) ) return fb.evaluateEffect("UIGraphicsBeginImageContextWithOptions(" + size + ", NO, 0.0)") fb.evaluateEffect( "[(id)" + layer + " renderInContext:(void *)UIGraphicsGetCurrentContext()]" ) result = fb.evaluateExpressionValue( "(UIImage *)UIGraphicsGetImageFromCurrentImageContext()" ) if result.GetError() is not None and str(result.GetError()) != "success": print(result.GetError()) else: image = result.GetValue() _showImage(image) fb.evaluateEffect("UIGraphicsEndImageContext()") def _showPixelBuffer(target): imgVar = "$imageOut" + str(round(time.time())) fb.evaluateExpression("CGImageRef " + imgVar + " = NULL") fb.evaluateExpression( "(OSStatus)VTCreateCGImageFromCVPixelBuffer((CVPixelBufferRef)" + target + ", NULL, &" + imgVar + ")" ) image = fb.evaluateExpression("[UIImage imageWithCGImage:" + imgVar + "]") _showImage(image) fb.evaluateExpression("CGImageRelease(" + imgVar + ")") def _dataIsImage(data): data = "(" + data + ")" result = fb.evaluateExpressionValue("(id)[UIImage imageWithData:" + data + "]") if result.GetError() is not None and str(result.GetError()) != "success": return False else: isImage = result.GetValueAsUnsigned() != 0 return isImage def _dataIsString(data): data = "(" + data + ")" result = fb.evaluateExpressionValue( "(NSString*)[[NSString alloc] initWithData:" + data + " encoding:4]" ) if result.GetError() is not None and str(result.GetError()) != "success": return False else: isString = result.GetValueAsUnsigned() != 0 return isString def _visualize(target): target = fb.evaluateInputExpression(target) if fb.evaluateBooleanExpression( "(unsigned long)CFGetTypeID((CFTypeRef)" + target + ") == (unsigned long)CGImageGetTypeID()" ): _showImage("(id)[UIImage imageWithCGImage:" + target + "]") elif fb.evaluateBooleanExpression( "(unsigned long)CFGetTypeID((CFTypeRef)" + target + ") == (unsigned long)CVPixelBufferGetTypeID()" ): _showPixelBuffer(target) else: if objectHelpers.isKindOfClass(target, "UIImage"): _showImage(target) elif objectHelpers.isKindOfClass(target, "UIView"): _showLayer("[(id)" + target + " layer]") elif objectHelpers.isKindOfClass(target, "CALayer"): _showLayer(target) elif ( objectHelpers.isKindOfClass(target, "UIColor") or objectHelpers.isKindOfClass(target, "CIColor") or _colorIsCGColorRef(target) ): _showColor(target) elif objectHelpers.isKindOfClass(target, "NSData"): if _dataIsImage(target): _showImage("(id)[UIImage imageWithData:" + target + "]") elif _dataIsString(target): print( fb.describeObject( "[[NSString alloc] initWithData:" + target + " encoding:4]" ) ) else: print("Data isn't an image and isn't a string.") elif objectHelpers.isKindOfClass(target, "CIImage"): _showImage("[UIImage imageWithCIImage:(id)" + target + "]") else: print( "{} isn't supported. You can visualize UIImage, CGImageRef, UIView, CALayer, NSData, UIColor, CIColor, CIImage, CGColorRef or CVPixelBuffer.".format( objectHelpers.className(target) ) ) class FBVisualizeCommand(fb.FBCommand): def name(self): return "visualize" def description(self): return "Open a UIImage, CGImageRef, UIView, CALayer, NSData, UIColor, CIColor, CIImage, CGColorRef or CVPixelBuffer in Preview.app on your Mac." def args(self): return [ fb.FBCommandArgument( arg="target", type="(id)", help="The object to visualize." ) ] def run(self, arguments, options): _visualize(arguments[0]) ================================================ FILE: commands/FBXCTestCommands.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import re import fbchisellldbbase as fb import lldb NOT_FOUND = 0xFFFFFFFF # UINT32_MAX def lldbcommands(): return [FBXCPrintDebugDescription(), FBXCPrintTree(), FBXCPrintObject(), FBXCNoId()] class FBXCPrintDebugDescription(fb.FBCommand): def name(self): return "xdebug" def description(self): return "Print debug description the XCUIElement in human readable format." def args(self): return [ fb.FBCommandArgument( arg="element", type="XCUIElement*", help="The element to print debug description.", default="__default__", ) ] def run(self, arguments, options): element = arguments[0] language = fb.currentLanguage() if element == "__default__": element = ( "XCUIApplication()" if language == lldb.eLanguageTypeSwift else "(XCUIApplication *)[[XCUIApplication alloc] init]" ) if language == lldb.eLanguageTypeSwift: print( fb.evaluateExpressionValue( "{}.debugDescription".format(element), language=language ) .GetObjectDescription() .replace("\\n", "\n") .replace("\\'", "'") .strip(' "\n\t') ) else: print( fb.evaluateExpressionValue( "[{} debugDescription]".format(element) ).GetObjectDescription() ) class FBXCPrintTree(fb.FBCommand): def name(self): return "xtree" def description(self): return "Print XCUIElement subtree." def args(self): return [ fb.FBCommandArgument( arg="element", type="XCUIElement*", help="The element to print tree.", default="__default__", ) ] def options(self): return [ fb.FBCommandArgument( arg="pointer", short="-p", long="--pointer", type="BOOL", boolean=True, default=False, help="Print pointers", ), fb.FBCommandArgument( arg="trait", short="-t", long="--traits", type="BOOL", boolean=True, default=False, help="Print traits", ), fb.FBCommandArgument( arg="frame", short="-f", long="--frame", type="BOOL", boolean=True, default=False, help="Print frames", ), ] def run(self, arguments, options): element = arguments[0] language = fb.currentLanguage() if element == "__default__": element = ( "XCUIApplication()" if language == lldb.eLanguageTypeSwift else "(XCUIApplication *)[[XCUIApplication alloc] init]" ) # Evaluate object element_sbvalue = fb.evaluateExpressionValue( "{}".format(element), language=language ) """:type: lldb.SBValue""" # Get pointer value, so it will be working in Swift and Objective-C element_pointer = int(element_sbvalue.GetValue(), 16) # Get XCElementSnapshot object snapshot = take_snapshot(element_pointer) # Print tree for snapshot element snapshot_object = XCElementSnapshot(snapshot, language=language) print( snapshot_object.tree().hierarchy_text( pointer=options.pointer, trait=options.trait, frame=options.frame ) ) class FBXCPrintObject(fb.FBCommand): def name(self): return "xobject" def description(self): return "Print XCUIElement details." def args(self): return [ fb.FBCommandArgument( arg="element", type="XCUIElement*", help="The element to print details.", default="__default__", ) ] def run(self, arguments, options): element = arguments[0] language = fb.currentLanguage() if element == "__default__": element = ( "XCUIApplication()" if language == lldb.eLanguageTypeSwift else "(XCUIApplication *)[[XCUIApplication alloc] init]" ) # Evaluate object element_sbvalue = fb.evaluateExpressionValue( "{}".format(element), language=language ) """:type: lldb.SBValue""" # Get pointer value, so it will be working in Swift and Objective-C element_pointer = int(element_sbvalue.GetValue(), 16) # Get XCElementSnapshot object snapshot = take_snapshot(element_pointer) # Print details of snapshot element snapshot_object = XCElementSnapshot(snapshot, language=language) print(snapshot_object.detail_summary()) class FBXCNoId(fb.FBCommand): def name(self): return "xnoid" def description(self): return "Print XCUIElement objects with label but without identifier." def args(self): return [ fb.FBCommandArgument( arg="element", type="XCUIElement*", help="The element from start to.", default="__default__", ) ] def options(self): return [ fb.FBCommandArgument( arg="status_bar", short="-s", long="--status-bar", type="BOOL", boolean=True, default=False, help="Print status bar items", ), fb.FBCommandArgument( arg="pointer", short="-p", long="--pointer", type="BOOL", boolean=True, default=False, help="Print pointers", ), fb.FBCommandArgument( arg="trait", short="-t", long="--traits", type="BOOL", boolean=True, default=False, help="Print traits", ), fb.FBCommandArgument( arg="frame", short="-f", long="--frame", type="BOOL", boolean=True, default=False, help="Print frames", ), ] def run(self, arguments, options): element = arguments[0] language = fb.currentLanguage() if element == "__default__": element = ( "XCUIApplication()" if language == lldb.eLanguageTypeSwift else "(XCUIApplication *)[[XCUIApplication alloc] init]" ) # Evaluate object element_sbvalue = fb.evaluateExpressionValue( "{}".format(element), language=language ) """:type: lldb.SBValue""" # Get pointer value, so it will be working in Swift and Objective-C element_pointer = int(element_sbvalue.GetValue(), 16) # Get XCElementSnapshot object snapshot = take_snapshot(element_pointer) # Print tree for snapshot element snapshot_object = XCElementSnapshot(snapshot, language=language) elements = snapshot_object.find_missing_identifiers( status_bar=options.status_bar ) if elements is not None: print( elements.hierarchy_text( pointer=options.pointer, trait=options.trait, frame=options.frame ) ) else: print("Couldn't found elements without identifier") def take_snapshot(element): """ Takes snapshot (XCElementSnapshot) from XCUIElement (as pointer) :param int element: Pointer to the XCUIElement :return: XCElementSnapshot object :rtype: lldb.SBValue """ return fb.evaluateExpressionValue( "(XCElementSnapshot *)[[[{} query] matchingSnapshotsWithError:nil] firstObject]".format( element ) ) class _ElementList(object): """ Store element and list of children :param XCElementSnapshot element: XCElementSnapshot :param list[_ElementList] children: List of XCElementSnapshot objects """ def __init__(self, element, children): self.element = element self.children = children def text(self, pointer, trait, frame, indent): """ String representation of the element :param bool pointer: Print pointers :param bool trait: Print traits :param bool frame: Print frames :param int indent: Indention :return: String representation of the element :rtype: str """ indent_string = " | " * indent return "{}{}\n".format( indent_string, self.element.summary(pointer=pointer, trait=trait, frame=frame), ) def hierarchy_text(self, pointer=False, trait=False, frame=False, indent=0): """ String representation of the hierarchy of elements :param bool pointer: Print pointers :param bool trait: Print traits :param bool frame: Print frames :param int indent: Indention :return: String representation of the hierarchy of elements :rtype: str """ s = self.text(pointer=pointer, trait=trait, frame=frame, indent=indent) for e in self.children: s += e.hierarchy_text( pointer=pointer, trait=trait, frame=frame, indent=indent + 1 ) return s class XCElementSnapshot(object): """ XCElementSnapshot wrapper :param lldb.SBValue element: XCElementSnapshot object :param str element_value: Pointer to XCElementSnapshot object :param language: Project language :param lldb.SBValue _type: XCUIElement type / XCUIElementType :param lldb.SBValue _traits: UIAccessibilityTraits :param lldb.SBValue | None _frame: XCUIElement frame :param lldb.SBValue _identifier: XCUIElement identifier :param lldb.SBValue _value: XCUIElement value :param lldb.SBValue _placeholderValue: XCUIElement placeholder value :param lldb.SBValue _label: XCUIElement label :param lldb.SBValue _title: XCUIElement title :param lldb.SBValue _children: XCUIElement children :param lldb.SBValue _enabled: XCUIElement is enabled :param lldb.SBValue _selected: XCUIElement is selected :param lldb.SBValue _isMainWindow: XCUIElement is main window :param lldb.SBValue _hasKeyboardFocus: XCUIElement has keyboard focus :param lldb.SBValue _hasFocus: XCUIElement has focus :param lldb.SBValue _generation: XCUIElement generation :param lldb.SBValue _horizontalSizeClass: XCUIElement horizontal class :param lldb.SBValue _verticalSizeClass: XCUIElement vertical class """ def __init__(self, element, language): """ :param lldb.SBValue element: XCElementSnapshot object :param language: Project language """ super(XCElementSnapshot, self).__init__() self.element = element self.element_value = self.element.GetValue() self.language = language self._type = None self._traits = None self._frame = None self._identifier = None self._value = None self._placeholderValue = None self._label = None self._title = None self._children = None self._enabled = None self._selected = None self._isMainWindow = None self._hasKeyboardFocus = None self._hasFocus = None self._generation = None self._horizontalSizeClass = None self._verticalSizeClass = None @property def is_missing_identifier(self): """ Checks if element has a label but doesn't have an identifier. :return: True if element has a label but doesn't have an identifier. :rtype: bool """ return len(self.identifier_value) == 0 and len(self.label_value) > 0 @property def type(self): """ :return: XCUIElement type / XCUIElementType :rtype: lldb.SBValue """ if self._type is None: name = "_elementType" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._type = fb.evaluateExpressionValue( "(int)[{} elementType]".format(self.element_value) ) else: self._type = self.element.GetChildMemberWithName(name) return self._type @property def type_value(self): """ :return: XCUIElementType value :rtype: int """ return int(self.type.GetValue()) @property def type_summary(self): """ :return: XCUIElementType summary :rtype: str """ return self.get_type_value_string(self.type_value) @property def traits(self): """ :return: UIAccessibilityTraits :rtype: lldb.SBValue """ if self._traits is None: name = "_traits" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._traits = fb.evaluateExpressionValue( "(int)[{} traits]".format(self.element_value) ) else: self._traits = self.element.GetChildMemberWithName(name) return self._traits @property def traits_value(self): """ :return: UIAccessibilityTraits value :rtype: int """ return int(self.traits.GetValue()) @property def traits_summary(self): """ :return: UIAccessibilityTraits summary :rtype: str """ return self.get_traits_value_string(self.traits_value) @property def frame(self): """ :return: XCUIElement frame :rtype: lldb.SBValue """ if self._frame is None: import_uikit() name = "_frame" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._frame = fb.evaluateExpressionValue( "(CGRect)[{} frame]".format(self.element_value) ) else: self._frame = self.element.GetChildMemberWithName(name) return self._frame @property def frame_summary(self): """ :return: XCUIElement frame summary :rtype: str """ return CGRect(self.frame).summary() @property def identifier(self): """ :return: XCUIElement identifier :rtype: lldb.SBValue """ if self._identifier is None: name = "_identifier" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._identifier = fb.evaluateExpressionValue( "(NSString *)[{} identifier]".format(self.element_value) ) else: self._identifier = self.element.GetChildMemberWithName(name) return self._identifier @property def identifier_value(self): """ :return: XCUIElement identifier value :rtype: str """ return normalize_summary(self.identifier.GetSummary()) @property def identifier_summary(self): """ :return: XCUIElement identifier summary :rtype: str | None """ if len(self.identifier_value) == 0: return None return "identifier: '{}'".format(self.identifier_value) @property def value(self): """ :return: XCUIElement value :rtype: lldb.SBValue """ if self._value is None: name = "_value" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._value = fb.evaluateExpressionValue( "(NSString *)[{} value]".format(self.element_value) ) else: self._value = self.element.GetChildMemberWithName(name) return self._value @property def value_value(self): """ :return: XCUIElement value value :rtype: str """ return normalize_summary(self.value.GetSummary()) @property def value_summary(self): """ :return: XCUIElement value summary :rtype: str | None """ if len(self.value_value) == 0: return None return "value: '{}'".format(self.value_value) @property def placeholder(self): """ :return: XCUIElement placeholder value :rtype: lldb.SBValue """ if self._placeholderValue is None: name = "_placeholderValue" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._placeholderValue = fb.evaluateExpressionValue( "(NSString *)[{} placeholderValue]".format(self.element_value) ) else: self._placeholderValue = self.element.GetChildMemberWithName(name) return self._placeholderValue @property def placeholder_value(self): """ :return: XCUIElement placeholderValue value :rtype: str """ return normalize_summary(self.placeholder.GetSummary()) @property def placeholder_summary(self): """ :return: XCUIElement placeholderValue summary :rtype: str | None """ if len(self.placeholder_value) == 0: return None return "placeholderValue: '{}'".format(self.placeholder_value) @property def label(self): """ :return: XCUIElement label :rtype: lldb.SBValue """ if self._label is None: name = "_label" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._label = fb.evaluateExpressionValue( "(NSString *)[{} label]".format(self.element_value) ) else: self._label = self.element.GetChildMemberWithName(name) return self._label @property def label_value(self): """ :return: XCUIElement label value :rtype: str """ return normalize_summary(self.label.GetSummary()) @property def label_summary(self): """ :return: XCUIElement label summary :rtype: str | None """ if len(self.label_value) == 0: return None return "label: '{}'".format(self.label_value) @property def title(self): """ :return: XCUIElement title :rtype: lldb.SBValue """ if self._title is None: name = "_title" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._title = fb.evaluateExpressionValue( "(NSString *)[{} title]".format(self.element_value) ) else: self._title = self.element.GetChildMemberWithName(name) return self._title @property def title_value(self): """ :return: XCUIElement title value :rtype: str """ return normalize_summary(self.title.GetSummary()) @property def title_summary(self): """ :return: XCUIElement title summary :rtype: str | None """ if len(self.title_value) == 0: return None return "title: '{}'".format(self.title_value) @property def children(self): """ :return: XCUIElement children :rtype: lldb.SBValue """ if self._children is None: name = "_children" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._children = fb.evaluateExpressionValue( "(NSArray *)[{} children]".format(self.element_value) ) else: self._children = self.element.GetChildMemberWithName(name) return self._children @property def children_count(self): """ :return: XCUIElement children count :rtype: int """ return self.children.GetNumChildren() @property def children_list(self): """ :return: XCUIElement children list :rtype: list[lldb.SBValue] """ return [self.children.GetChildAtIndex(i) for i in range(self.children_count)] @property def enabled(self): """ :return: XCUIElement is enabled :rtype: lldb.SBValue """ if self._enabled is None: name = "_enabled" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._enabled = fb.evaluateExpressionValue( "(BOOL)[{} enabled]".format(self.element_value) ) else: self._enabled = self.element.GetChildMemberWithName(name) return self._enabled @property def enabled_value(self): """ :return: XCUIElement is enabled value :rtype: bool """ return bool(self.enabled.GetValueAsSigned()) @property def enabled_summary(self): """ :return: XCUIElement is enabled summary :rtype: str | None """ if not self.enabled_value: return "enabled: {}".format(self.enabled_value) return None @property def selected(self): """ :return: XCUIElement is selected :rtype: lldb.SBValue """ if self._selected is None: name = "_selected" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._selected = fb.evaluateExpressionValue( "(BOOL)[{} selected]".format(self.element_value) ) else: self._selected = self.element.GetChildMemberWithName(name) return self._selected @property def selected_value(self): """ :return: XCUIElement is selected value :rtype: bool """ return bool(self.selected.GetValueAsSigned()) @property def selected_summary(self): """ :return: XCUIElement is selected summary :rtype: str | None """ if self.selected_value: return "selected: {}".format(self.selected_value) return None @property def is_main_window(self): """ :return: XCUIElement isMainWindow :rtype: lldb.SBValue """ if self._isMainWindow is None: name = "_isMainWindow" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._isMainWindow = fb.evaluateExpressionValue( "(BOOL)[{} isMainWindow]".format(self.element_value) ) else: self._isMainWindow = self.element.GetChildMemberWithName(name) return self._isMainWindow @property def is_main_window_value(self): """ :return: XCUIElement isMainWindow value :rtype: bool """ return bool(self.is_main_window.GetValueAsSigned()) @property def is_main_window_summary(self): """ :return: XCUIElement isMainWindow summary :rtype: str | None """ if self.is_main_window_value: return "MainWindow" return None @property def keyboard_focus(self): """ :return: XCUIElement hasKeyboardFocus :rtype: lldb.SBValue """ if self._hasKeyboardFocus is None: name = "_hasKeyboardFocus" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._hasKeyboardFocus = fb.evaluateExpressionValue( "(BOOL)[{} hasKeyboardFocus]".format(self.element_value) ) else: self._hasKeyboardFocus = self.element.GetChildMemberWithName(name) return self._hasKeyboardFocus @property def keyboard_focus_value(self): """ :return: XCUIElement hasKeyboardFocus value :rtype: bool """ return bool(self.keyboard_focus.GetValueAsSigned()) @property def keyboard_focus_summary(self): """ :return: XCUIElement hasKeyboardFocus summary :rtype: str | None """ if self.keyboard_focus_value: return "hasKeyboardFocus: {}".format(self.keyboard_focus_value) return None @property def focus(self): """ :return: XCUIElement hasFocus :rtype: lldb.SBValue """ if self._hasFocus is None: name = "_hasFocus" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._hasFocus = fb.evaluateExpressionValue( "(BOOL)[{} hasFocus]".format(self.element_value) ) else: self._hasFocus = self.element.GetChildMemberWithName(name) return self._hasFocus @property def focus_value(self): """ :return: XCUIElement hasFocus value :rtype: bool """ return bool(self.focus.GetValueAsSigned()) @property def focus_summary(self): """ :return: XCUIElement hasFocus summary :rtype: str | None """ if self.focus_value: return "hasFocus: {}".format(self.focus_value) return None @property def generation(self): """ :return: XCUIElement generation :rtype: lldb.SBValue """ if self._generation is None: name = "_generation" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._generation = fb.evaluateExpressionValue( "(unsigned int)[{} generation]".format(self.element_value) ) else: self._generation = self.element.GetChildMemberWithName(name) return self._generation @property def generation_value(self): """ :return: XCUIElement generation value :rtype: int """ return int(self.generation.GetValueAsUnsigned()) @property def horizontal_size_class(self): """ :return: XCUIElement horizontal size class :rtype: lldb.SBValue """ if self._horizontalSizeClass is None: name = "_horizontalSizeClass" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._horizontalSizeClass = fb.evaluateExpressionValue( "(int)[{} horizontalSizeClass]".format(self.element_value) ) else: self._horizontalSizeClass = self.element.GetChildMemberWithName(name) return self._horizontalSizeClass @property def horizontal_size_class_value(self): """ :return: XCUIElement horizontal size class value :rtype: int """ return int(self.horizontal_size_class.GetValue()) @property def horizontal_size_class_summary(self): """ :return: XCUIElement horizontal size class summary """ return self.get_user_interface_size_class_string( self.horizontal_size_class_value ) @property def vertical_size_class(self): """ :return: XCUIElement vertical size class :rtype: lldb.SBValue """ if self._verticalSizeClass is None: name = "_verticalSizeClass" if self.element.GetIndexOfChildWithName(name) == NOT_FOUND: self._verticalSizeClass = fb.evaluateExpressionValue( "(int)[{} verticalSizeClass]".format(self.element_value) ) else: self._verticalSizeClass = self.element.GetChildMemberWithName(name) return self._verticalSizeClass @property def vertical_size_class_value(self): """ :return: XCUIElement vertical size class value :rtype: int """ return int(self.vertical_size_class.GetValue()) @property def vertical_size_class_summary(self): """ :return: XCUIElement vertical size class summary """ return self.get_user_interface_size_class_string(self.vertical_size_class_value) @property def uniquely_identifying_objective_c_code(self): """ :return: XCUIElement uniquely identifying Objective-C code :rtype: lldb.SBValue """ return fb.evaluateExpressionValue( "(id)[{} _uniquelyIdentifyingObjectiveCCode]".format(self.element_value) ) @property def uniquely_identifying_objective_c_code_value(self): """ :return: XCUIElement uniquely identifying Objective-C code value :rtype: str """ return normalize_array_description( self.uniquely_identifying_objective_c_code.GetObjectDescription() ) @property def uniquely_identifying_swift_code(self): """ :return: XCUIElement uniquely identifying Swift code :rtype: lldb.SBValue """ return fb.evaluateExpressionValue( "(id)[{} _uniquelyIdentifyingSwiftCode]".format(self.element_value) ) @property def uniquely_identifying_swift_code_value(self): """ :return: XCUIElement uniquely identifying Swift code value :rtype: str """ return normalize_array_description( self.uniquely_identifying_swift_code.GetObjectDescription() ) @property def is_touch_bar_element(self): """ :return: XCUIElement is touch bar element :rtype: lldb.SBValue """ return fb.evaluateExpressionValue( "(BOOL)[{} isTouchBarElement]".format(self.element_value) ) @property def is_touch_bar_element_value(self): """ :return: XCUIElement is touch bar element value :rtype: bool """ return bool(self.is_touch_bar_element.GetValueAsSigned()) @property def is_top_level_touch_bar_element(self): """ :return: XCUIElement is top level touch bar element :rtype: lldb.SBValue """ return fb.evaluateExpressionValue( "(BOOL)[{} isTopLevelTouchBarElement]".format(self.element_value) ) @property def is_top_level_touch_bar_element_value(self): """ :return: XCUIElement is top level touch bar element value :rtype: bool """ return bool(self.is_top_level_touch_bar_element.GetValueAsSigned()) @property def suggested_hit_points(self): """ :return: XCUIElement suggested hit points :rtype: lldb.SBValue """ return fb.evaluateExpressionValue( "(NSArray *)[{} suggestedHitpoints]".format(self.element_value) ) @property def suggested_hit_points_value(self): """ :return: XCUIElement suggested hit points :rtype: str """ return normalize_array_description( self.suggested_hit_points.GetObjectDescription() ) @property def visible_frame(self): """ :return: XCUIElement visible frame :rtype: lldb.SBValue """ import_uikit() return fb.evaluateExpressionValue( "(CGRect)[{} visibleFrame]".format(self.element_value) ) @property def visible_frame_summary(self): """ :return: XCUIElement visible frame :rtype: str """ return CGRect(self.visible_frame).summary() @property def depth(self): """ :return: XCUIElement depth :rtype: lldb.SBValue """ return fb.evaluateExpressionValue("(int)[{} depth]".format(self.element_value)) @property def depth_value(self): """ :return: XCUIElement depth :rtype: int """ return int(self.depth.GetValue()) @property def hit_point(self): """ :return: XCUIElement hit point :rtype: lldb.SBValue """ import_uikit() return fb.evaluateExpressionValue( "(CGPoint)[{} hitPoint]".format(self.element_value) ) @property def hit_point_value(self): """ :return: XCUIElement hit point :rtype: str """ return CGPoint(self.hit_point).summary() @property def hit_point_for_scrolling(self): """ :return: XCUIElement hit point for scrolling :rtype: lldb.SBValue """ import_uikit() return fb.evaluateExpressionValue( "(CGPoint)[{} hitPointForScrolling]".format(self.element_value) ) @property def hit_point_for_scrolling_value(self): """ :return: XCUIElement hit point for scrolling :rtype: str """ return CGPoint(self.hit_point_for_scrolling).summary() def summary(self, pointer=False, trait=False, frame=False): """ Returns XCElementSnapshot summary :param bool pointer: Print pointers :param bool trait: Print traits :param bool frame: Print frames :return: XCElementSnapshot summary :rtype: str """ type_text = self.type_summary if pointer: type_text += " {:#x}".format(int(self.element_value, 16)) if trait: type_text += " traits: {}({:#x})".format( self.traits_summary, self.traits_value ) frame_text = self.frame_summary if frame else None identifier = self.identifier_summary label = self.label_summary title = self.title_summary value = self.value_summary placeholder = self.placeholder_summary enabled = self.enabled_summary selected = self.selected_summary main_window = self.is_main_window_summary keyboard_focus = self.keyboard_focus_summary focus = self.focus_summary texts = [ t for t in [ frame_text, identifier, label, title, value, placeholder, enabled, selected, main_window, keyboard_focus, focus, ] if t is not None ] return "{}: {}".format(type_text, ", ".join(texts)) def detail_summary(self): """ Returns XCElementSnapshot detail summary :return: XCElementSnapshot detail summary :rtype: str """ texts = list() texts.append("Pointer: {:#x}".format(int(self.element_value, 16))) texts.append("Type: {}".format(self.type_summary)) texts.append("Depth: {}".format(self.depth_value)) texts.append( "Traits: {} ({:#x})".format(self.traits_summary, self.traits_value) ) texts.append("Frame: {}".format(self.frame_summary)) texts.append("Visible frame: {}".format(self.visible_frame_summary)) texts.append("Identifier: '{}'".format(self.identifier_value)) texts.append("Label: '{}'".format(self.label_value)) texts.append("Title: '{}'".format(self.title_value)) texts.append("Value: '{}'".format(self.value_value)) texts.append("Placeholder: '{}'".format(self.placeholder_value)) if self.language != lldb.eLanguageTypeSwift: # They doesn't work on Swift :( texts.append("Hit point: {}".format(self.hit_point_value)) texts.append( "Hit point for scrolling: {}".format(self.hit_point_for_scrolling_value) ) texts.append("Enabled: {}".format(self.enabled_value)) texts.append("Selected: {}".format(self.selected_value)) texts.append("Main Window: {}".format(self.is_main_window_value)) texts.append("Keyboard focus: {}".format(self.keyboard_focus_value)) texts.append("Focus: {}".format(self.focus_value)) texts.append("Generation: {}".format(self.generation_value)) texts.append( "Horizontal size class: {}".format(self.horizontal_size_class_summary) ) texts.append("Vertical size class: {}".format(self.vertical_size_class_summary)) texts.append("TouchBar element: {}".format(self.is_touch_bar_element_value)) texts.append( "TouchBar top level element: {}".format( self.is_top_level_touch_bar_element_value ) ) texts.append( "Unique Objective-C: {}".format( self.uniquely_identifying_objective_c_code_value ) ) texts.append( "Unique Swift: {}".format(self.uniquely_identifying_swift_code_value) ) texts.append("Suggested hit points: {}".format(self.suggested_hit_points_value)) return "\n".join(texts) def tree(self): """ Returns tree of elements in hierarchy :return: Elements hierarchy :rtype: _ElementList """ children = [ XCElementSnapshot(e, self.language).tree() for e in self.children_list ] return _ElementList(self, children) def find_missing_identifiers(self, status_bar): """ Find element which has a label but doesn't have an identifier :param bool status_bar: Print status bar items :return: Hierarchy structure with items which has a label but doesn't have an identifier :rtype: _ElementList | None """ # Do not print status bar items if status_bar is not True and self.type_value == XCUIElementType.StatusBar: return None children_missing = [ XCElementSnapshot(e, self.language).find_missing_identifiers( status_bar=status_bar ) for e in self.children_list ] children_missing = [x for x in children_missing if x is not None] # Self and its children are not missing identifiers if self.is_missing_identifier is False and len(children_missing) == 0: return None return _ElementList(self, children_missing) @staticmethod def get_type_value_string(value): """ Get element type string from XCUIElementType (as int) :param int value: XCUIElementType (as int) :return: XCUIElementType string :rtype: str """ return XCUIElementType.name_for_value(value) @staticmethod def get_traits_value_string(value): """ Get element traits string from UIAccessibilityTraits (as int) :param int value: UIAccessibilityTraits (as int) :return: UIAccessibilityTraits string :rtype: str """ return UIAccessibilityTraits.name_for_value(value) @staticmethod def get_user_interface_size_class_string(value): """ Get user interface size class string from UIUserInterfaceSizeClass (as int) :param value: UIAccessibilityTraits (as int) :return: UIUserInterfaceSizeClass string :rtype: str """ return UIUserInterfaceSizeClass.name_for_value(value) class XCUIElementType(object): """ Represents all XCUIElementType types """ Any = 0 Other = 1 Application = 2 Group = 3 Window = 4 Sheet = 5 Drawer = 6 Alert = 7 Dialog = 8 Button = 9 RadioButton = 10 RadioGroup = 11 CheckBox = 12 DisclosureTriangle = 13 PopUpButton = 14 ComboBox = 15 MenuButton = 16 ToolbarButton = 17 Popover = 18 Keyboard = 19 Key = 20 NavigationBar = 21 TabBar = 22 TabGroup = 23 Toolbar = 24 StatusBar = 25 Table = 26 TableRow = 27 TableColumn = 28 Outline = 29 OutlineRow = 30 Browser = 31 CollectionView = 32 Slider = 33 PageIndicator = 34 ProgressIndicator = 35 ActivityIndicator = 36 SegmentedControl = 37 Picker = 38 PickerWheel = 39 Switch = 40 Toggle = 41 Link = 42 Image = 43 Icon = 44 SearchField = 45 ScrollView = 46 ScrollBar = 47 StaticText = 48 TextField = 49 SecureTextField = 50 DatePicker = 51 TextView = 52 Menu = 53 MenuItem = 54 MenuBar = 55 MenuBarItem = 56 Map = 57 WebView = 58 IncrementArrow = 59 DecrementArrow = 60 Timeline = 61 RatingIndicator = 62 ValueIndicator = 63 SplitGroup = 64 Splitter = 65 RelevanceIndicator = 66 ColorWell = 67 HelpTag = 68 Matte = 69 DockItem = 70 Ruler = 71 RulerMarker = 72 Grid = 73 LevelIndicator = 74 Cell = 75 LayoutArea = 76 LayoutItem = 77 Handle = 78 Stepper = 79 Tab = 80 TouchBar = 81 @classmethod def _attributes_by_value(cls): """ :return: Hash of all attributes and their values :rtype: dict[int, str] """ class_attributes = set(dir(cls)) - set(dir(object)) return dict( [ (getattr(cls, n), n) for n in class_attributes if not callable(getattr(cls, n)) and not n.startswith("__") ] ) @classmethod def name_for_value(cls, value): """ Get element type string from XCUIElementType (as int) :param int value: XCUIElementType (as int) :return: Name of type :rtype: str """ attributes = cls._attributes_by_value() if value in attributes: return attributes[value] else: return "Unknown ({:#x})".format(value) class UIAccessibilityTraits(object): """ Represents all UIAccessibilityTraits types """ Button = 0x0000000000000001 Link = 0x0000000000000002 Image = 0x0000000000000004 Selected = 0x0000000000000008 PlaysSound = 0x0000000000000010 KeyboardKey = 0x0000000000000020 StaticText = 0x0000000000000040 SummaryElement = 0x0000000000000080 NotEnabled = 0x0000000000000100 UpdatesFrequently = 0x0000000000000200 SearchField = 0x0000000000000400 StartsMediaSession = 0x0000000000000800 Adjustable = 0x0000000000001000 AllowsDirectInteraction = 0x0000000000002000 CausesPageTurn = 0x0000000000004000 TabBar = 0x0000000000008000 Header = 0x0000000000010000 @classmethod def _attributes_by_value(cls): """ :return: Hash of all attributes and their values :rtype: dict[int, str] """ class_attributes = set(dir(cls)) - set(dir(object)) return dict( [ (getattr(cls, n), n) for n in class_attributes if not callable(getattr(cls, n)) and not n.startswith("__") ] ) @classmethod def name_for_value(cls, value): """ Get element traits string from UIAccessibilityTraits (as int) :param int value: UIAccessibilityTraits (as int) :return: UIAccessibilityTraits string :rtype: str """ if value == 0: return "None" traits = [] attributes = cls._attributes_by_value() for k in attributes.keys(): if value & k: traits.append(attributes[k]) if len(traits) == 0: return "Unknown" else: return ", ".join(traits) class UIUserInterfaceSizeClass(object): """ Represents all UIUserInterfaceSizeClass types """ Unspecified = 0 Compact = 1 Regular = 2 @classmethod def name_for_value(cls, value): """ Get user interface size class string from UIUserInterfaceSizeClass (as int) :param int value: UIAccessibilityTraits (as int) :return: UIUserInterfaceSizeClass string :rtype: str """ if value == cls.Unspecified: return "Unspecified" elif value == cls.Compact: return "Compact" elif value == cls.Regular: return "Regular" else: return "Unknown ({:#x})".format(value) class CGRect(object): """ CGRect wrapper :param lldb.SBValue element: CGRect object """ def __init__(self, element): """ :param lldb.SBValue element: CGRect object """ super(CGRect, self).__init__() self.element = element def summary(self): """ :return: CGRect summary :rtype: str """ origin_element = self.element.GetChildMemberWithName("origin") origin = CGPoint(origin_element) size = self.element.GetChildMemberWithName("size") width = size.GetChildMemberWithName("width") height = size.GetChildMemberWithName("height") width_value = float(width.GetValue()) height_value = float(height.GetValue()) return "{{{}, {{{}, {}}}}}".format(origin.summary(), width_value, height_value) class CGPoint(object): """ CGPoint wrapper :param lldb.SBValue element: CGPoint object """ def __init__(self, element): super(CGPoint, self).__init__() self.element = element def summary(self): """ :return: CGPoint summary :rtype: str """ x = self.element.GetChildMemberWithName("x") y = self.element.GetChildMemberWithName("y") x_value = float(x.GetValue()) y_value = float(y.GetValue()) return "{{{}, {}}}".format(x_value, y_value) def normalize_summary(summary): """ Normalize summary by removing "'" and "@" characters :param str summary: Summary string to normalize :return: Normalized summary string :rtype: str """ return summary.lstrip("@").strip('"') def normalize_array_description(description): """ Normalize array object description by removing "<" and ">" characters and content between them. :param str description: Array object description :return: Normalized array object description string :rtype: str """ return re.sub("^(<.*>)", "", description).strip() _uikit_imported = False def import_uikit(): """ Import UIKit framework to the debugger """ global _uikit_imported if _uikit_imported: return _uikit_imported = True fb.evaluateExpressionValue("@import UIKit") def debug(element): """ Debug helper :param lldb.SBValue element: Element to debug """ print("---") print("element: {}".format(element)) print("element class: {}".format(element.__class__)) print("element name: {}".format(element.GetName())) print("element type name: {}".format(element.GetTypeName())) print("element value: {}".format(element.GetValue())) print("element value class: {}".format(element.GetValue().__class__)) print("element value type: {}".format(element.GetValueType())) print("element value signed: {0}({0:#x})".format(element.GetValueAsSigned())) print("element value unsigned: {0}({0:#x})".format(element.GetValueAsUnsigned())) print("element summary: {}".format(element.GetSummary())) print("element description: {}".format(element.GetObjectDescription())) print("element children num: {}".format(element.GetNumChildren())) for i in range(0, element.GetNumChildren()): child = element.GetChildAtIndex(i) """:type: lldb.SBValue""" print("element child {:02}: {}".format(i, child.GetName())) print("===") ================================================ FILE: fbchisellldb.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import imp import os from contextlib import redirect_stderr, redirect_stdout from optparse import OptionParser import lldb def __lldb_init_module(debugger, dict): filePath = os.path.realpath(__file__) lldbHelperDir = os.path.dirname(filePath) commandsDirectory = os.path.join(lldbHelperDir, "commands") loadCommandsInDirectory(commandsDirectory) def loadCommandsInDirectory(commandsDirectory): for file in os.listdir(commandsDirectory): fileName, fileExtension = os.path.splitext(file) if fileExtension == ".py": module = imp.load_source(fileName, os.path.join(commandsDirectory, file)) if hasattr(module, "lldbinit"): module.lldbinit() if hasattr(module, "lldbcommands"): module._loadedFunctions = {} for command in module.lldbcommands(): loadCommand( module, command, commandsDirectory, fileName, fileExtension ) def loadCommand(module, command, directory, filename, extension): func = makeRunCommand(command, os.path.join(directory, filename + extension)) name = command.name() helpText = ( command.description().strip().splitlines()[0] ) # first line of description key = filename + "_" + name module._loadedFunctions[key] = func functionName = "__" + key lldb.debugger.HandleCommand( "script " + functionName + " = sys.modules['" + module.__name__ + "']._loadedFunctions['" + key + "']" ) lldb.debugger.HandleCommand( 'command script add --help "{help}" --function {function} {name}'.format( help=helpText.replace('"', '\\"'), # escape quotes function=functionName, name=name, ) ) def makeRunCommand(command, filename): def runCommand(debugger, input, exe_ctx, result, _): # lldb assumes that any output meant for the user is written # to the result object. By redirecting stdout here, we can # use methods like print (or parse_args) in the command logic # as if they are writing to stdout, but write to result # instead. lldb will handle displaying it to the user. with redirect_stdout(result), redirect_stderr(result): command.result = result command.context = exe_ctx splitInput = command.lex(input) # OptionParser will throw in the case where you want just one # big long argument and no options and you enter something # that starts with '-' in the argument. e.g.: # somecommand -[SomeClass someSelector:] # This solves that problem by prepending a '--' so that # OptionParser does the right thing. options = command.options() if len(options) == 0: if "--" not in splitInput: splitInput.insert(0, "--") parser = optionParserForCommand(command) (options, args) = parser.parse_args(splitInput) # When there are more args than the command has declared, assume # the initial args form an expression and combine them into a single arg. if len(args) > len(command.args()): overhead = len(args) - len(command.args()) head = args[: overhead + 1] # Take N+1 and reduce to 1. args = [" ".join(head)] + args[-overhead:] if validateArgsForCommand(args, command): command.run(args, options) runCommand.__doc__ = helpForCommand(command, filename) return runCommand def validateArgsForCommand(args, command): if len(args) < len(command.args()): defaultArgs = [arg.default for arg in command.args()] defaultArgsToAppend = defaultArgs[len(args) :] index = len(args) for defaultArg in defaultArgsToAppend: if not defaultArg: arg = command.args()[index] print("Whoops! You are missing the <" + arg.argName + "> argument.") print("\nUsage: " + usageForCommand(command)) return index += 1 args.extend(defaultArgsToAppend) return True def optionParserForCommand(command): parser = OptionParser() for argument in command.options(): if argument.boolean: parser.add_option( argument.shortName, argument.longName, dest=argument.argName, help=argument.help, action=("store_false" if argument.default else "store_true"), ) else: parser.add_option( argument.shortName, argument.longName, dest=argument.argName, help=argument.help, default=argument.default, ) return parser def helpForCommand(command, filename): help = command.description() argSyntax = "" optionSyntax = "" if command.args(): help += "\n\nArguments:" for arg in command.args(): help += "\n <" + arg.argName + ">; " if arg.argType: help += "Type: " + arg.argType + "; " help += arg.help argSyntax += " <" + arg.argName + ">" if command.options(): help += "\n\nOptions:" for option in command.options(): if option.longName and option.shortName: optionFlag = option.longName + "/" + option.shortName elif option.longName: optionFlag = option.longName else: optionFlag = option.shortName help += "\n " + optionFlag + " " if not option.boolean: help += "<" + option.argName + ">; Type: " + option.argType help += "; " + option.help optionSyntax += " [{name}{arg}]".format( name=(option.longName or option.shortName), arg=("" if option.boolean else ("=" + option.argName)), ) help += "\n\nSyntax: " + command.name() + optionSyntax + argSyntax help += "\n\nThis command is implemented as %s in %s." % ( command.__class__.__name__, filename, ) return help def usageForCommand(command): usage = command.name() for arg in command.args(): if arg.default: usage += " [" + arg.argName + "]" else: usage += " " + arg.argName return usage ================================================ FILE: fbchisellldbbase.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import json import shlex import lldb class FBCommandArgument: # noqa B903 def __init__( self, short="", long="", arg="", type="", help="", default="", boolean=False ): self.shortName = short self.longName = long self.argName = arg self.argType = type self.help = help self.default = default self.boolean = boolean class FBCommand: def name(self): return None def options(self): return [] def args(self): return [] def description(self): return "" def lex(self, commandLine): return shlex.split(commandLine) def run(self, arguments, option): pass def isSuccess(error): # When evaluating a `void` expression, the returned value will indicate an # error. This error is named: kNoResult. This error value does *not* mean # there was a problem. This logic follows what the builtin `expression` # command does. See: https://git.io/vwpjl (UserExpression.h) kNoResult = 0x1001 return error.success or error.value == kNoResult def importModule(frame, module): options = lldb.SBExpressionOptions() options.SetLanguage(lldb.eLanguageTypeObjC) value = frame.EvaluateExpression("@import " + module, options) return isSuccess(value.error) # evaluates expression in Objective-C++ context, so it will work even for # Swift projects def evaluateExpressionValue( expression, printErrors=True, language=lldb.eLanguageTypeObjC_plus_plus, tryAllThreads=False, ): frame = ( lldb.debugger.GetSelectedTarget() .GetProcess() .GetSelectedThread() .GetSelectedFrame() ) options = lldb.SBExpressionOptions() options.SetLanguage(language) # Allow evaluation that contains a @throw/@catch. # By default, ObjC @throw will cause evaluation to be aborted. At the time # of a @throw, it's not known if the exception will be handled by a @catch. # An exception that's caught, should not cause evaluation to fail. options.SetTrapExceptions(False) # Give evaluation more time. options.SetTimeoutInMicroSeconds(5000000) # 5s # Most Chisel commands are not multithreaded. options.SetTryAllThreads(tryAllThreads) value = frame.EvaluateExpression(expression, options) error = value.GetError() # Retry if the error could be resolved by first importing UIKit. if ( error.type == lldb.eErrorTypeExpression and error.value == lldb.eExpressionParseError and importModule(frame, "UIKit") ): value = frame.EvaluateExpression(expression, options) error = value.GetError() if printErrors and not isSuccess(error): print(error) return value def evaluateInputExpression(expression, printErrors=True): # HACK if expression.startswith("(id)"): return evaluateExpressionValue(expression, printErrors=printErrors).GetValue() frame = ( lldb.debugger.GetSelectedTarget() .GetProcess() .GetSelectedThread() .GetSelectedFrame() ) options = lldb.SBExpressionOptions() options.SetTrapExceptions(False) value = frame.EvaluateExpression(expression, options) error = value.GetError() if printErrors and error.Fail(): print(error) return value.GetValue() def evaluateIntegerExpression(expression, printErrors=True): output = evaluateExpression("(int)(" + expression + ")", printErrors).replace( "'", "" ) if output.startswith("\\x"): # Booleans may display as \x01 (Hex) output = output[2:] elif output.startswith("\\"): # Or as \0 (Dec) output = output[1:] return int(output, 0) def evaluateBooleanExpression(expression, printErrors=True): return ( int(evaluateIntegerExpression("(BOOL)(" + expression + ")", printErrors)) != 0 ) def evaluateExpression(expression, printErrors=True): return evaluateExpressionValue(expression, printErrors=printErrors).GetValue() def describeObject(expression, printErrors=True): return evaluateExpressionValue( "(id)(" + expression + ")", printErrors ).GetObjectDescription() def evaluateEffect(expression, printErrors=True): evaluateExpressionValue("(void)(" + expression + ")", printErrors=printErrors) def evaluateObjectExpression(expression, printErrors=True): return evaluateExpression("(id)(" + expression + ")", printErrors) def evaluateCStringExpression(expression, printErrors=True): ret = evaluateExpression(expression, printErrors) process = lldb.debugger.GetSelectedTarget().GetProcess() error = lldb.SBError() ret = process.ReadCStringFromMemory(int(ret, 16), 256, error) if error.Success(): return ret else: if printErrors: print(error) return None RETURN_MACRO = """ #define IS_JSON_OBJ(obj)\ (obj != nil && ((bool)[NSJSONSerialization isValidJSONObject:obj] ||\ (bool)[obj isKindOfClass:[NSString class]] ||\ (bool)[obj isKindOfClass:[NSNumber class]])) #define RETURN(ret) ({\ if (!IS_JSON_OBJ(ret)) {\ (void)[NSException raise:@"Invalid RETURN argument" format:@""];\ }\ NSDictionary *__dict = @{@"return":ret};\ NSData *__data = (id)[NSJSONSerialization dataWithJSONObject:__dict options:0 error:NULL];\ NSString *__str = (id)[[NSString alloc] initWithData:__data encoding:4];\ (char *)[__str UTF8String];}) #define RETURNCString(ret)\ ({NSString *___cstring_ret = [NSString stringWithUTF8String:ret];\ RETURN(___cstring_ret);}) """ def check_expr(expr): return expr.strip().split(";")[-2].find("RETURN") != -1 # evaluate a batch of Objective-C expressions, the last expression # must contain a RETURN marco and it will automatic transform the # Objective-C object to Python object # Example: # >>> fbchisellldbbase.evaluate('NSString *str = @"hello world"; RETURN(@{@"key": str});') # {u'key': u'hello world'} def evaluate(expr): if not check_expr(expr): raise Exception( "Invalid Expression, the last expression not include a RETURN family marco" ) command = "({" + RETURN_MACRO + "\n" + expr + "})" ret = evaluateExpressionValue(command, printErrors=True) if not ret.GetError().Success(): print(ret.GetError()) return None else: process = lldb.debugger.GetSelectedTarget().GetProcess() error = lldb.SBError() ret = process.ReadCStringFromMemory(int(ret.GetValue(), 16), 2**20, error) if not error.Success(): print(error) return None else: ret = json.loads(ret) return ret["return"] def currentLanguage(): return ( lldb.debugger.GetSelectedTarget() .GetProcess() .GetSelectedThread() .GetSelectedFrame() .GetCompileUnit() .GetLanguage() ) ================================================ FILE: fbchisellldbinputhelpers.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import lldb class FBInputHandler: def __init__(self, debugger, callback): self.debugger = debugger self.callback = callback self.inputReader = lldb.SBInputReader() self.inputReader.Initialize( debugger, self.handleInput, lldb.eInputReaderGranularityLine, None, None, # prompt True, # echo ) def isValid(self): return not self.inputReader.IsDone() def start(self): self.debugger.PushInputReader(self.inputReader) def stop(self): self.inputReader.SetIsDone(True) def handleInput(self, inputReader, notification, bytes): if notification == lldb.eInputReaderGotToken: self.callback(bytes) elif notification == lldb.eInputReaderInterrupt: self.stop() return len(bytes) ================================================ FILE: fbchisellldbobjcruntimehelpers.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import re import fbchisellldbbase as fb import lldb def objc_getClass(className): command = '(void*)objc_getClass("{}")'.format(className) value = fb.evaluateExpression(command) return value def object_getClass(object): command = "(void*)object_getClass((id){})".format(object) value = fb.evaluateExpression(command) return value def class_getName(klass): command = "(const char*)class_getName((Class){})".format(klass) value = fb.evaluateExpressionValue(command).GetSummary().strip('"') return value def class_getSuperclass(klass): command = "(void*)class_getSuperclass((Class){})".format(klass) value = fb.evaluateExpression(command) return value def class_isMetaClass(klass): command = "class_isMetaClass((Class){})".format(klass) return fb.evaluateBooleanExpression(command) def class_getInstanceMethod(klass, selector): command = "(void*)class_getInstanceMethod((Class){}, @selector({}))".format( klass, selector ) value = fb.evaluateExpression(command) return value def currentArch(): targetTriple = lldb.debugger.GetSelectedTarget().GetTriple() arch = targetTriple.split("-")[0] if arch == "x86_64h": arch = "x86_64" return arch def functionPreambleExpressionForSelf(): import re arch = currentArch() expressionForSelf = None if arch == "i386": expressionForSelf = "*(id*)($esp+4)" elif arch == "x86_64": expressionForSelf = "(id)$rdi" elif arch == "arm64": expressionForSelf = "(id)$x0" elif re.match(r"^armv.*$", arch): expressionForSelf = "(id)$r0" return expressionForSelf def functionPreambleExpressionForObjectParameterAtIndex(parameterIndex): arch = currentArch() expresssion = None if arch == "i386": expresssion = "*(id*)($esp + " + str(12 + parameterIndex * 4) + ")" elif arch == "x86_64": if parameterIndex > 3: raise Exception( "Current implementation can not return object at index greater than 3 for x86_64" ) registersList = ["rdx", "rcx", "r8", "r9"] expresssion = "(id)$" + registersList[parameterIndex] elif arch == "arm64": if parameterIndex > 5: raise Exception( "Current implementation can not return object at index greater than 5 for arm64" ) expresssion = "(id)$x" + str(parameterIndex + 2) elif re.match(r"^armv.*$", arch): if parameterIndex > 1: raise Exception( "Current implementation can not return object at index greater than 1 for arm32" ) expresssion = "(id)$r" + str(parameterIndex + 2) return expresssion def isMacintoshArch(): arch = currentArch() if not arch == "x86_64": return False nsClassName = "NSApplication" command = '(void*)objc_getClass("{}")'.format(nsClassName) return fb.evaluateBooleanExpression(command + "!= nil") def isIOSSimulator(): return ( fb.evaluateExpressionValue("(id)[[UIDevice currentDevice] model]") .GetObjectDescription() .lower() .find("simulator") >= 0 ) def isIOSDevice(): return not isMacintoshArch() and not isIOSSimulator() ================================================ FILE: fbchisellldbobjecthelpers.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import fbchisellldbbase as fb def isKindOfClass(obj, className): isKindOfClassStr = "[(id)" + obj + " isKindOfClass:[{} class]]" return fb.evaluateBooleanExpression(isKindOfClassStr.format(className)) def className(obj): return fb.evaluateExpressionValue( "(id)[(" + obj + ") class]" ).GetObjectDescription() ================================================ FILE: fbchisellldbviewcontrollerhelpers.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as runtimeHelpers def presentViewController(viewController): vc = "(%s)" % (viewController) if fb.evaluateBooleanExpression( "%s != nil && ((BOOL)[(id)%s isKindOfClass:(Class)[UIViewController class]])" % (vc, vc) ): notPresented = fb.evaluateBooleanExpression( "[%s presentingViewController] == nil" % vc ) if notPresented: fb.evaluateEffect( "[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:%s animated:YES completion:nil]" % vc ) else: raise Exception("Argument is already presented") else: raise Exception("Argument must be a UIViewController") def dismissViewController(viewController): vc = "(%s)" % (viewController) if fb.evaluateBooleanExpression( "%s != nil && ((BOOL)[(id)%s isKindOfClass:(Class)[UIViewController class]])" % (vc, vc) ): isPresented = fb.evaluateBooleanExpression( "[%s presentingViewController] != nil" % vc ) if isPresented: fb.evaluateEffect( "[(UIViewController *)%s dismissViewControllerAnimated:YES completion:nil]" % vc ) else: raise Exception("Argument must be presented") else: raise Exception("Argument must be a UIViewController") def viewControllerRecursiveDescription(vc): return _recursiveViewControllerDescriptionWithPrefixAndChildPrefix( fb.evaluateObjectExpression(vc), "", "", "" ) def _viewControllerDescription(viewController): vc = "(%s)" % (viewController) if fb.evaluateBooleanExpression("[(id)%s isViewLoaded]" % (vc)): result = fb.evaluateExpressionValue( '(id)[[NSString alloc] initWithFormat:@"<%%@: %%p; view = <%%@; %%p>; frame = (%%g, %%g; %%g, %%g)>", (id)NSStringFromClass((id)[(id)%s class]), %s, (id)[(id)[(id)%s view] class], (id)[(id)%s view], ((CGRect)[(id)[(id)%s view] frame]).origin.x, ((CGRect)[(id)[(id)%s view] frame]).origin.y, ((CGRect)[(id)[(id)%s view] frame]).size.width, ((CGRect)[(id)[(id)%s view] frame]).size.height]' % (vc, vc, vc, vc, vc, vc, vc, vc) ) else: result = fb.evaluateExpressionValue( '(id)[[NSString alloc] initWithFormat:@"<%%@: %%p; view not loaded>", (id)NSStringFromClass((id)[(id)%s class]), %s]' % (vc, vc) ) if result.GetError() is not None and str(result.GetError()) != "success": return "[Error getting description.]" else: return result.GetObjectDescription() def _recursiveViewControllerDescriptionWithPrefixAndChildPrefix( vc, string, prefix, childPrefix ): isMac = runtimeHelpers.isMacintoshArch() s = "%s%s%s\n" % ( prefix, "" if prefix == "" else " ", _viewControllerDescription(vc), ) nextPrefix = childPrefix + " |" numChildViewControllers = fb.evaluateIntegerExpression( "(int)[(id)[%s childViewControllers] count]" % (vc) ) for i in range(0, numChildViewControllers): viewController = fb.evaluateExpression( "(id)[(id)[%s childViewControllers] objectAtIndex:%d]" % (vc, i) ) s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix( viewController, string, nextPrefix, nextPrefix ) if not isMac: isModal = fb.evaluateBooleanExpression( "%s != nil && ((id)[(id)[(id)%s presentedViewController] presentingViewController]) == %s" % (vc, vc, vc) ) if isModal: modalVC = fb.evaluateObjectExpression( "(id)[(id)%s presentedViewController]" % (vc) ) s += _recursiveViewControllerDescriptionWithPrefixAndChildPrefix( modalVC, string, childPrefix + " *M", nextPrefix ) s += "\n// '*M' means the view controller is presented modally." return string + s ================================================ FILE: fbchisellldbviewhelpers.py ================================================ #!/usr/bin/python # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. import fbchisellldbbase as fb import fbchisellldbobjcruntimehelpers as runtimeHelpers def flushCoreAnimationTransaction(): fb.evaluateEffect("[CATransaction flush]") def setViewHidden(object, hidden): fb.evaluateEffect("[{} setHidden:{}]".format(object, int(hidden))) flushCoreAnimationTransaction() def maskView(viewOrLayer, color, alpha): unmaskView(viewOrLayer) window = fb.evaluateExpression( "(UIWindow *)[[UIApplication sharedApplication] keyWindow]" ) origin = convertPoint(0, 0, viewOrLayer, window) size = fb.evaluateExpressionValue( "(CGSize)((CGRect)[(id)%s frame]).size" % viewOrLayer ) rectExpr = "(CGRect){{%s, %s}, {%s, %s}}" % ( origin.GetChildMemberWithName("x").GetValue(), origin.GetChildMemberWithName("y").GetValue(), size.GetChildMemberWithName("width").GetValue(), size.GetChildMemberWithName("height").GetValue(), ) mask = fb.evaluateExpression("(id)[[UIView alloc] initWithFrame:%s]" % rectExpr) fb.evaluateEffect("[%s setTag:(NSInteger)%s]" % (mask, viewOrLayer)) fb.evaluateEffect("[%s setBackgroundColor:[UIColor %sColor]]" % (mask, color)) fb.evaluateEffect("[%s setAlpha:(CGFloat)%s]" % (mask, alpha)) fb.evaluateEffect("[%s addSubview:%s]" % (window, mask)) flushCoreAnimationTransaction() def unmaskView(viewOrLayer): window = fb.evaluateExpression( "(UIWindow *)[[UIApplication sharedApplication] keyWindow]" ) mask = fb.evaluateExpression( "(UIView *)[%s viewWithTag:(NSInteger)%s]" % (window, viewOrLayer) ) fb.evaluateEffect("[%s removeFromSuperview]" % mask) flushCoreAnimationTransaction() def convertPoint(x, y, fromViewOrLayer, toViewOrLayer): fromLayer = convertToLayer(fromViewOrLayer) toLayer = convertToLayer(toViewOrLayer) return fb.evaluateExpressionValue( "(CGPoint)[%s convertPoint:(CGPoint){ .x = %s, .y = %s } toLayer:(CALayer *)%s]" % (fromLayer, x, y, toLayer) ) def convertToLayer(viewOrLayer): if fb.evaluateBooleanExpression( "[(id)%s isKindOfClass:(Class)[CALayer class]]" % viewOrLayer ): return viewOrLayer elif fb.evaluateBooleanExpression( "[(id)%s respondsToSelector:(SEL)@selector(layer)]" % viewOrLayer ): return fb.evaluateExpression("(CALayer *)[%s layer]" % viewOrLayer) else: raise Exception("Argument must be a CALayer, UIView, or NSView.") def isUIView(obj): return not runtimeHelpers.isMacintoshArch() and fb.evaluateBooleanExpression( "[(id)%s isKindOfClass:(Class)[UIView class]]" % obj ) def isNSView(obj): return runtimeHelpers.isMacintoshArch() and fb.evaluateBooleanExpression( "[(id)%s isKindOfClass:(Class)[NSView class]]" % obj ) def isView(obj): return isUIView(obj) or isNSView(obj) # Generates a BFS of the views tree starting at the given view as root. # Yields a tuple of the current view in the tree and its level (view, level) def subviewsOfView(view): views = [(view, 0)] yield views[0] while views: (view, level) = views.pop(0) subviews = fb.evaluateExpression("(id)[%s subviews]" % view) subviewsCount = int(fb.evaluateExpression("(int)[(id)%s count]" % subviews)) for i in range(subviewsCount): subview = fb.evaluateExpression("(id)[%s objectAtIndex:%i]" % (subviews, i)) views.append((subview, level + 1)) yield (subview, level + 1) def upwardsRecursiveDescription(view, maxDepth=0): if not fb.evaluateBooleanExpression( "[(id)%s isKindOfClass:(Class)[UIView class]]" % view ) and not fb.evaluateBooleanExpression( "[(id)%s isKindOfClass:(Class)[NSView class]]" % view ): return None currentView = view recursiveDescription = [] depth = 0 while currentView and (maxDepth <= 0 or depth <= maxDepth): depth += 1 viewDescription = fb.evaluateExpressionValue( "(id)[%s debugDescription]" % (currentView) ).GetObjectDescription() currentView = fb.evaluateExpression("(void*)[%s superview]" % (currentView)) try: if int(currentView, 0) == 0: currentView = None except Exception: currentView = None if viewDescription: recursiveDescription.insert(0, viewDescription) if not len(viewDescription): return None currentPrefix = "" builder = "" for viewDescription in recursiveDescription: builder += currentPrefix + viewDescription + "\n" currentPrefix += " | " return builder def slowAnimation(speed=1): fb.evaluateEffect( '[[[UIApplication sharedApplication] windows] setValue:@(%s) forKeyPath:@"layer.speed"]' % speed )