+
+Public Key Infrastructure (PKI) is a set of standards and specifications to use public-key cryptography more effectively. The X.509 standard is one widely used PKI format.
+
+### Certificate Chain
+
+CAs have hierarchical relationships. The highest-level certificate authority is usually the root CA. Lower-level certificates are signed by an upper-level CA; the root CA typically self-signs (self-signed certificate).
+
+How do you verify that a certificate has not been tampered with?
+When the client connects over HTTPS, the server returns the full certificate chain. Start from the leaf certificate and use the issuer's public key to verify the digital signature of the subordinate certificate. Continue validating up the chain until you reach a trust anchor (a trusted root certificate).
+
+
+
+## Example GET request — deeper exploration
+
+1. Request entry point
+```
+- (NSURLSessionDataTask *)GET:(NSString *)URLString
+ parameters:(id)parameters
+ success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
+ failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
+```
+2. Create NSURLSessionDataTask
+```
+- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
+ URLString:(NSString *)URLString
+ parameters:(id)parameters
+ uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
+ downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
+ success:(void (^)(NSURLSessionDataTask *, id))success
+ failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
+
+```
+
+
+
+## What AF does compared to raw native APIs
+Starting from GET/POST entry points and analyzing the source:
+- Initializes many properties
+- Adds safety measures (for example, addressing iOS 8 task identifier uniqueness issues using dispatch_sync)
+- Provides custom block-based progress callbacks that are easier to use
+- Uses decoupling to reduce complexity in main classes, making maintenance easier
+
+## AFSecurityPolicy source analysis — HTTPS authentication
+
+
+
+
+
+https://www.jianshu.com/p/856f0e26279d
+
+https://www.jianshu.com/u/14431e509ae8
+
+https://blog.csdn.net/ZCMUCZX/article/details/79399517
\ No newline at end of file
diff --git a/Chapter1 - iOS/1.24.md b/Chapter1 - iOS/1.24.md
new file mode 100644
index 0000000..82be9da
--- /dev/null
+++ b/Chapter1 - iOS/1.24.md
@@ -0,0 +1,79 @@
+# The Elegant Design of NSRange
+
+> typedef struct _NSRange {
+ NSUInteger location;
+ NSUInteger length;
+} NSRange;
+
+1. Looking at the official documentation source, you can see NSRange is a struct. But if you were to design such a data type, how would you do it?
+
+Design it as a struct, but what about some helper operations? For developer convenience, design a way for developers to quickly know the upper bound of this struct.
+
+Apple was clever and designed an inline function:
+
+```
+NS_INLINE NSUInteger NSMaxRange(NSRange range) {
+ return (range.location + range.length);
+}
+```
+
+2. What is an inline function?
+
+```
+NS_INLINE return_type functionName(parameter_list) {
+ // function implementation
+ // return ;
+}
+```
+
+3. Applications of inline functions
+
+For example, a custom alert:
+
+```
+NS_INLINE void tipWithMessage(NSString *message){
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+
+ UIAlertView *alerView = [[UIAlertView alloc] initWithTitle:@"Tip" message:message delegate:nil cancelButtonTitle:nil otherButtonTitles:nil, nil];
+
+ [alerView show];
+
+ [alerView performSelector:@selector(dismissWithClickedButtonIndex:animated:) withObject:@[@0, @1] afterDelay:0.9];
+
+ });
+
+}
+```
+
+4. Notes about inline functions
+
+Inline functions save the overhead of a function call at the cost of code duplication. They reduce call overhead and thus can improve execution speed. If the time to execute the inline function's code is large compared to the function call overhead, the efficiency gain will be small.
+
+Also, each call site of an inline function copies the function code, so the program size increases and consumes more text/code segment space. Therefore, the following situations are not suitable for inline functions:
+
+ * If the function body is relatively long, inlining will cause significant memory/code size increase.
+ * If the function body contains loops, the execution time of the function body will be larger than the call overhead.
+
+5. FOUNDATION_EXPORT
+
+Looking at NSRange's code you may also notice the keyword **FOUNDATION_EXPORT**, which can be used to define constants.
+
+FOUNDATION_EXPORT and #define can both be used to define constants.
+Usage:
+
+```objc
+// .h
+FOUNDATION_EXPORT NSString *const NickName;
+
+// .m
+NSString *const NickName = @"Hangzhou Xiao Liu";
+```
+
+So how does it differ from **#define**?
+
+FOUNDATION_EXPORT is more efficient when checking whether string values are equal.
+When using **NickName == MyName** to compare, FOUNDATION_EXPORT compares pointers, whereas with #define you would use **[NickName isEqualToString:MyName]** to compare contents.
+
+ * Essentially FOUNDATION_EXPORT compares pointers directly.
+ * \#define compares whether each string's content is equal.
\ No newline at end of file
diff --git a/Chapter1 - iOS/1.29.md b/Chapter1 - iOS/1.29.md
new file mode 100644
index 0000000..b60c898
--- /dev/null
+++ b/Chapter1 - iOS/1.29.md
@@ -0,0 +1,155 @@
+# JavascriptCore
+
+1. JSCore is a JavaScript wrapper implemented in C/C++ based on WebKit that makes interaction between JavaScript and native code simpler.
+
+- JSContext
+ JSContext represents an instance of a JavaScript execution environment. All JavaScript execution occurs inside a context. JSContext is also used to manage the lifecycle of JavaScript objects in the VM.
+
+- JSValue
+ JSValue is used to receive the return results from JSContext execution. A JSValue can be any JavaScript type (primitive, object, function, etc.).
+
+- JSManagedValue
+ JSManagedValue is a wrapper around JSValue that helps resolve retain-cycle issues between JS and Objective-C. The most common use is to safely reference a JSValue stored on the heap. If a JSValue is stored on the heap incorrectly, it can easily create retain cycles that prevent JSContext from being released properly.
+
+- JSExport
+ A protocol used to expose native objects to JS; the exposed object can point to itself and to other objects.
+
+- JSVirtualMachine
+ Manages the JS object space and needed resources.
+
+2. Native calling JS
+
+- Load JS code
+ (JSValue *)evaluateScript:(NSString *)script;
+
+- Call JS function
+ JSValue *callback = self.context[@"sayHi"];
+ [callback callWithArguments:@[@"Hangzhou Xiao Liu"]];
+
+3. JS calling Native
+
+- Implement via a block. Then call the method directly from JS. Note: inside the block do not directly use externally defined JSContext or JSValue; pass them as parameters or obtain them via + (JSContext *)currentContext; otherwise it may cause retain cycles and memory won't be freed correctly.
+ self.context[@"showMessage"] = ^(NSString *message){
+ UIAlertController *alertCtr = [UIAlertController alertControllerWithTitle:@"Notice" message:message preferredStyle:UIAlertControllerStyleAlert];
+ UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil];
+ [alertCtr addAction:cancel];
+ // Note: the block executes on a background thread; update UI on main thread.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [weakSelf presentViewController:alertCtr animated:YES completion:nil];
+ });
+ };
+
+- Implement via the JSExport protocol; JS will call methods on an injected Objective-C object, so the methods must be declared in the protocol and implemented on the injected object. Inject the native object when the web view finishes loading.
+ // Protocol declaration
+
+ @protocol JSInject ***
+ + + + + + + + +Native calls JS + + // Native + - (void)callJS{ + JSValue *functionName = self.jsContext[@"sum"]; + NSInteger sum = [[functionName callWithArguments:@[@"2",@"18"]] toInt32]; + + UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"Calculation from JS" message:[NSString stringWithFormat:@"%zd",sum] preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]; + [alertVC addAction:okAction]; + [self presentViewController:alertVC animated:YES completion:nil]; + } + + // JS + function sum(a ,b){ + return parseInt(a) + parseInt(b); + } \ No newline at end of file diff --git a/Chapter1 - iOS/1.43.md b/Chapter1 - iOS/1.43.md new file mode 100644 index 0000000..153f819 --- /dev/null +++ b/Chapter1 - iOS/1.43.md @@ -0,0 +1,91 @@ +# Debugging Tricks + +1. In daily development we often encapsulate a feature module and expose a method for external use. But many times callers don't pass parameters according to the contract. We use assertions to catch this. However, in production if assertions are used the app will crash. Xcode provides a small feature to address this. + +`NS_BLOCK_ASSERTIONS`: prevents NSAssert from firing in Release builds; adding this single macro will filter out NSAssert. +How: In Build Settings search for **Preprocessor Macros**, then add `NS_BLOCK_ASSERTIONS` under the Release configuration. + + + +### Breakpoints + +#### Categories +Breakpoints include Normal Breakpoint, Exception Breakpoint, OpenGL ES Error Breakpoint, Symbolic Breakpoint, Test Failure Breakpoint, and Watchpoint. Use different breakpoint types according to the scenario. + +### NSAssert and dispatch_once + +NSAssert is very common in development, especially when building SDKs and libraries. Assertions help catch problems during development and enforce expected behavior. NSAssert essentially raises an exception; when an exception occurs it triggers the C function `objc_exception_throw`. + + + +Callback information example: +```Objective-c +*** First throw call stack: +( + 0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350 + 1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48 + 2 CoreFoundation 0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88 + 3 Foundation 0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191 + 4 TEst 0x0000000106edfeef -[AppDelegate application:didFinishLaunchingWithOptions:] + 287 + 5 UIKitCore 0x00007fff48089ad8 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 232 + 6 UIKitCore 0x00007fff4808b460 -[UIApplication _callInitializationDelegatesWithActions:forCanvas:payload:fromOriginatingProcess:] + 3980 + 7 UIKitCore 0x00007fff48090f05 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1226 + 8 UIKitCore 0x00007fff477c57a6 -[_UISceneLifecycleMultiplexer completeApplicationLaunchWithFBSScene:transitionContext:] + 179 + 9 UIKitCore 0x00007fff4808d514 -[UIApplication _compellApplicationLaunchToCompleteUnconditionally] + 59 + 10 UIKitCore 0x00007fff4808d813 -[UIApplication _run] + 754 + 11 UIKitCore 0x00007fff48092d4d UIApplicationMain + 1621 + 12 TEst 0x0000000106ee0144 main + 116 + 13 libdyld.dylib 0x00007fff5227ec25 start + 1 +) +libc++abi.dylib: terminating with uncaught exception of type NSException +``` + +You can clearly see when an assertion fails Xcode can precisely locate the line of code where NSAssert occurred—source is available. But in some scenarios exceptions cannot be traced to the real origin. For example, in large apps people often build Pods as static libraries to speed up builds; in those cases the exception cannot be precisely traced to the offending source line. If the assertion occurs inside a GCD block and the source context is missing, you also cannot pinpoint it. + +Example output: +```Objective-c +*** First throw call stack: +( + 0 CoreFoundation 0x00007fff23c7127e __exceptionPreprocess + 350 + 1 libobjc.A.dylib 0x00007fff513fbb20 objc_exception_throw + 48 + 2 CoreFoundation 0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88 + 3 Foundation 0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 191 + 4 TEst 0x000000010f242e95 __57-[AppDelegate application:didFinishLaunchingWithOptions:]_block_invoke + 229 + 5 libdispatch.dylib 0x000000010f55fdd4 _dispatch_call_block_and_release + 12 + 6 libdispatch.dylib 0x000000010f560d48 _dispatch_client_callout + 8 + 7 libdispatch.dylib 0x000000010f56ede6 _dispatch_main_queue_callback_4CF + 1500 + 8 CoreFoundation 0x00007fff23bd4049 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 + 9 CoreFoundation 0x00007fff23bceca9 __CFRunLoopRun + 2329 + 10 CoreFoundation 0x00007fff23bce066 CFRunLoopRunSpecific + 438 + 11 GraphicsServices 0x00007fff384c0bb0 GSEventRunModal + 65 + 12 UIKitCore 0x00007fff48092d4d UIApplicationMain + 1621 + 13 TEst 0x000000010f243134 main + 116 + 14 libdyld.dylib 0x00007fff5227ec25 start + 1 +) +libc++abi.dylib: terminating with uncaught exception of type NSException +``` + +Add a Symbolic Breakpoint in Xcode for the symbol `objc_exception_throw`; then you can see the full stack in Xcode’s left-side navigator. + + + +Check GCD’s `_dispatch_client_callout` to see if there’s anything unusual. You can find libdispatch source here: https://opensource.apple.com/tarballs/libdispatch/. + +```Objective-c +// object.m +#undef _dispatch_client_callout +void +_dispatch_client_callout(void *ctxt, dispatch_function_t f) +{ + @try { + return f(ctxt); + } + @catch (...) { + objc_terminate(); + } +} +``` + +You’ll find `_dispatch_client_callout` wraps the GCD block invocation in a try/catch that catches Objective-C exceptions and then calls `objc_terminate`, which breaks the call stack. + +There’s a scenario where a crash shows up inside `dispatch_once` because code inside `dispatch_once` threw an Objective-C exception. Big companies often see this early on; later they add code around assertions specifically to record an owner or provide better diagnostics. \ No newline at end of file diff --git a/Chapter1 - iOS/1.50.md b/Chapter1 - iOS/1.50.md new file mode 100644 index 0000000..957ffdb --- /dev/null +++ b/Chapter1 - iOS/1.50.md @@ -0,0 +1,24 @@ +# Static Libraries and Dynamic Libraries + +Usually, our Xcode projects depend on some third-party libraries, including: .a static libraries (Static Library) and .framework dynamic libraries (Dynamic Library). + +However, simply calling files with the .framework suffix "dynamic libraries" is not precise, because in iOS/macOS development, frameworks are further divided into "static frameworks" and "dynamic frameworks." The differences are as follows: + +* Static framework: Can be understood as a collection of a .a static file + .h public header files + resource files. Essentially it is the same as a .a static library. +* Dynamic framework: The actual dynamic library in the true sense, generally including a dynamic binary, header files, resource files, etc. + +For a Static Library project, its build products are a .a static binary file + public .h header files. + +For a Framework project, whether the final build product is a dynamic library or a static library can be chosen in Build Settings -> Linking -> Mach-O Type by setting its value to Dynamic Library or Static Library. + +In addition, we know that for a Mach-O binary file, whether static or dynamic, it generally contains several different processor architectures (Architectures), for example: i386, x86_64, armv7, armv7s, arm64, etc. + +Xcode handles static libraries and dynamic libraries differently during compilation and linking. + +For static libraries, at link time Xcode will automatically select the appropriate architectures from the static library and merge them into the main executable binary for the corresponding processor architecture; and when packaging and archiving, Xcode will automatically ignore architectures in the static library that are not used, for example removing i386, x86_64, and other simulator-only architectures for macOS. + +For dynamic libraries, during build and packaging Xcode will directly copy the entire dynamic framework file into the final .ipa. The actual dynamic linking only happens when the app launches at runtime. However, Apple does not allow .ipa files uploaded to App Store Connect to contain simulator architectures such as i386, x86_64 — this will cause an Invalid error. Therefore, for dynamic frameworks in the project, when building a Release production package we typically remove these invalid architectures via commands or scripts. + +Finally, how do we add these static/dynamic libraries into an Xcode project? + +For ".a static libraries" and "static frameworks", simply drag them into the project and check the "Copy if needed" option. No further configuration is required. \ No newline at end of file diff --git a/Chapter1 - iOS/1.87.md b/Chapter1 - iOS/1.87.md new file mode 100644 index 0000000..e1f9f23 --- /dev/null +++ b/Chapter1 - iOS/1.87.md @@ -0,0 +1,159 @@ +# Objective-C Under the Hood + +1. Objects and classes in Objective-C are primarily implemented using structs from C/C++. + +Method 1: + +You can verify with clang. `clang -rewrite-objc main.m -o main.cpp` +To target a specific platform: `xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp` + +``` +struct NSObject_IMPL { + Class isa; +}; + +/// An opaque type that represents an Objective-C class. +typedef struct objc_class *Class; +``` + +So Class is a pointer to a struct, which occupies 8 bytes on a 64-bit system and 4 bytes on a 32-bit system. + +NSObject as a struct therefore occupies 8 bytes. + +[images omitted] + +You notice `class_getInstanceSize` and `malloc_size` results differ. + +```c +// Class's ivar size rounded up to a pointer-size boundary. +uint32_t alignedInstanceSize() const { + return word_align(unalignedInstanceSize()); +} +``` + +`class_getInstanceSize` returns the size of a class instance's member variables (rounded up to pointer-size boundary, so not the exact allocated size). + +```c +extern size_t malloc_size(const void *ptr); +/* Returns size of given ptr */ +``` + +`malloc_size` returns the actual size allocated for the pointer. + +[image omitted] + +Conclusions: + +- If a class inherits from NSObject and has no additional properties, the class occupies 16 bytes. `class_getInstanceSize` returns 8, `malloc_size` returns 16. +- If a class inherits from NSObject and has additional properties, the class occupies 16 bytes. `class_getInstanceSize` returns 16, `malloc_size` returns 16. + +Method 2: Verify from source code (top-down) + +```c++ +// NSObject.mm +// Replaced by ObjectAlloc ++ (id)allocWithZone:(struct _NSZone *)zone { + return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone); +} + +// objc-class-old.mm +id +_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) +{ + id obj; + + if (fastpath(!zone)) { + obj = class_createInstance(cls, 0); + } else { + obj = class_createInstanceFromZone(cls, 0, zone); + } + + if (slowpath(!obj)) obj = _objc_callBadAllocHandler(cls); + return obj; +} + +// objc-class-old.mm +/*********************************************************************** +* _class_createInstance. Allocate an instance of the specified +* class with the specified number of bytes for indexed variables, in +* the default zone, using _class_createInstanceFromZone. +**********************************************************************/ +static id _class_createInstance(Class cls, size_t extraBytes) +{ + return _class_createInstanceFromZone (cls, extraBytes, nil); +} + + + +static ALWAYS_INLINE id +_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, + int construct_flags = OBJECT_CONSTRUCT_NONE, + bool cxxConstruct = true, + size_t *outAllocatedSize = nil) +{ + ASSERT(cls->isRealized()); + + // Read class's info bits all at once for performance + bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); + bool hasCxxDtor = cls->hasCxxDtor(); + bool fast = cls->canAllocNonpointer(); + size_t size; + + size = cls->instanceSize(extraBytes); + if (outAllocatedSize) *outAllocatedSize = size; + + id obj; + if (zone) { + obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); + } else { + obj = (id)calloc(1, size); + } + if (slowpath(!obj)) { + if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { + return _objc_callBadAllocHandler(cls); + } + return nil; + } + + if (!zone && fast) { + obj->initInstanceIsa(cls, hasCxxDtor); + } else { + // Use raw pointer isa on the assumption that they might be + // doing something weird with the zone or RR. + obj->initIsa(cls); + } + + if (fastpath(!hasCxxCtor)) { + return obj; + } + + construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; + return object_cxxConstructFromClass(obj, cls, construct_flags); +} +``` + +```c++ +// objc-runtime-new.h +// Class's ivar size rounded up to a pointer-size boundary. +uint32_t alignedInstanceSize() const { + return word_align(unalignedInstanceSize()); +} +``` + +```c++ +// objc-runtime-new.h +size_t instanceSize(size_t extraBytes) const { + if (fastpath(cache.hasFastInstanceSize(extraBytes))) { + return cache.fastInstanceSize(extraBytes); + } + + size_t size = alignedInstanceSize() + extraBytes; + // CF requires all objects be at least 16 bytes. + if (size < 16) size = 16; + return size; +} +``` + +"CF requires all objects be at least 16 bytes." The system allocates at least 16 bytes for NSObject objects, but it uses 8 bytes for storing ivars (on 64-bit systems). + +2. How memory is allocated for a class that inherits from NSObject \ No newline at end of file diff --git a/Chapter1 - iOS/1.88.md b/Chapter1 - iOS/1.88.md new file mode 100644 index 0000000..469647c --- /dev/null +++ b/Chapter1 - iOS/1.88.md @@ -0,0 +1,172 @@ +# Principle of fishhook + +## Hook classifications + +- Method Swizzle: Uses the Objective-C runtime to dynamically change the mapping between SEL (method selector) and IMP (method implementation), altering the Objective-C method call flow. Mainly used for Objective-C methods. +- fishhook: A tool provided by Facebook to dynamically modify the linking of Mach-O files. It uses Mach-O loading principles to modify pointers in the lazy and non-lazy symbol pointer tables to hook C functions. +- Cydia Substrate: Formerly Mobile Substrate, it hooks Objective-C methods, C functions, and function addresses. It's not limited to iOS; Android can also use it. Official site: http://www.cydiasubstrate.com + +There are only two types of hooks: +- inline hook: directly modifies the function entry code or some code inside the function to jump to your own code +- address replacement: includes replacing entry table addresses, exit table addresses, addresses inside structs, etc. This is the simplest but not always effective—calls that don't go through the address table won't be hooked. + +## Application + +You often meet hooking Objective-C methods, but for functions like NSLog or objc_msgSend the Objective-C runtime isn't enough. With fishhook, hooking C functions is no longer difficult. + +Why put "C functions" in quotes? Keep reading. + + + +### Hooking system C functions + +Take NSLog as an example. + +[image: FishHookWithCFunction.png] + +You can see the hook succeeded. + +```c +struct rebinding { + const char *name; // name of the function to hook, C string + void *replacement; // new function address + void **replaced; // pointer to store the original function address +}; +``` + + + +### Hooking user-defined C functions + +Create a user C function `handleTouchAction`, but the hook fails. + +[image: FishHookWithUserCFunction.png] + +This raises curiosity: why can system C functions be hooked but not user-defined C functions? Continue exploring. + + + +## Peek into the principle + +FishHook is a Facebook tool that can dynamically modify Mach-O linking. It leverages Mach-O loading mechanics and modifies lazy and non-lazy symbol pointer tables to hook system C functions. + + + +### Mach-O file permissions + +Mach-O is divided into code segment, data segment, etc.: + +- Code segment: readable, executable, not writable +- Data segment: readable, writable, not executable + + + +### System shared cache + +We know NSLog's implementation is in the Foundation library, while user-defined functions reside in the app's own executable Mach-O. + +iOS shared cache: since iOS 3.1, Apple packaged system libraries into a large cache file located at /System/Library/Caches/com.apple.dyld/ to reduce redundancy and optimize memory usage. + +- Accessible by all processes +- Architecture-specific cache files (e.g., dyld_shared_cache_arm64 for ARM64) +- Optimizes dynamic library loading by avoiding repeated loading of the same libraries across apps, speeding startup and improving performance + +When an app's Mach-O is loaded by dyld, the address of NSLog is not yet fixed because its real implementation lives in the Foundation framework inside the shared cache. + +That raises a question: when compiled, clang cannot know the actual runtime addresses of Foundation functions (on any device or architecture). How is this solved? + + + +Static linking vs dynamic linking + +Linking can be static or dynamic. Early computers used static linking. Static linking has drawbacks: + +- Large memory and disk waste because each program contains copies of common library functions like printf, scanf, etc. +- Development and distribution inconvenience: if a third-party lib.o updates, the app must be relinked and redistributed. + +Dynamic linking splits modules into separate files and resolves links at runtime. This solves space and update problems but requires OS-level support and a dynamic linker. Dynamic linking introduces runtime overhead, but lazy binding reduces it: symbols are resolved only when first used. + +To avoid repeated symbol resolution at load time, PIC (Position Independent Code) was introduced. + + + +### PIC technology + +Relocation at load time can break shared instruction sharing among processes. PIC separates parts of code that require modification and places them with data so the instruction section can remain unchanged and be shared. This allows code to be loaded at different addresses without rewriting instructions. + +Benefits: + +- Shared code: multiple apps can share a single dynamic library instance +- dyld can optimize symbol binding and improve startup speed +- Supports code relocation for flexible updates and patches + + + +When your code contains a call to NSLog, at compile time the IDE shows the declaration, but the compiled executable doesn't know NSLog's runtime address. How does this work? + +With PIC, workflow: + +- At compile time, the Mach-O's data segment contains a region called the symbol table (writable). + All references to symbols from shared cache libraries are set up to point to symbol entries. For example, a reference to NSLog results in a NSLog symbol in the Mach-O; the app's call to NSLog references that symbol. +- When dyld loads the Mach-O, it performs symbol binding. + Dyld reads load commands in the header, finds required libraries, and binds symbols. For NSLog, dyld writes the real Foundation NSLog address into the Mach-O's _DATA symbol entry for NSLog. + + + +### Practical exploration + +Experiment to verify the full process. + +[image: MachOLazySymbolLatestVersionLocation.png] + +Step 1: You can see NSLog in the Lazy Symbol Pointers as the first entry. "lazy" means it's bound only when used. Set breakpoints to verify. + +[image: FishHookMachO.png] + +Step 2: At the NSLog breakpoint, in LLDB run `image list` to view images. The first image is the app's main executable; its image base is 0x0000000100da5000. + +[image: FishHookDemoImageList.png] + +Step 3: Use image base + offset to compute the NSLog address: `memory read 0x0000000102eec000+0xC000` to inspect memory. + +[image: NSLogFakeAddress.png] + +Step 4: Set the breakpoint to proceed so NSLog runs once; then disassemble the address (`dis -s addr`) to view assembly. + +[image: LLDBNSLogAddressSymbol.png] + +Step 5: Continue execution past the breakpoint, call `rebind_symbols`, then inspect memory again. After rebind, the address changed; disassembly now shows your custom function. + +[image: FishhookResult.png] + + + +Detailed mapping steps: + +Step 1: In Lazy Symbol Pointers you see the first symbol `NSLog` at index 1. + +[image: FishHookMachO1.png] + +Step 2: In the Dynamic Symbol Table, the first entry relates to NSLog. Its Data value `00000084` (hex) equals 132 (decimal). + +[image: FishHookMachO2.png] + +Step 3: Use that index to find the 132nd entry in the Symbol Table. Its Data value `000000AA` is an offset. + +[image: FishHookMachO3.png] + +Step 4: In the String Table, the first position `0000CFE4` plus offset `0xAA` equals `0xD08E`, which is the symbol name location corresponding to `NSLog`. + +[image: FishHookMachO4.png] + + + +Functions like `NSLog`, `dispatch_once`, etc., use stubs that point to Lazy Symbol Pointers, which in turn point to a stub_helper and ultimately to `dyld_stub_binder`. The real address is resolved on the first call. + +fishhook leverages this behavior by replacing entries in the Lazy Symbol Pointers with addresses of custom functions to achieve hooks. This is why fishhook cannot hook C functions defined inside the same binary. + + + +What fishhook actually does: it modifies the system symbol table entries so specific symbols' addresses are replaced with custom function addresses—i.e., it hooks external C functions that are dynamically bound. + +Therefore fishhook cannot hook user-defined C functions inside the same Mach-O, because those functions are not called via the symbol binding process that fishhook manipulates. User functions are directly linked within the binary rather than resolved through the dynamic symbol pointers. \ No newline at end of file