mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
Add Chapter1 iOS notes on various topics
This commit is contained in:
222
Chapter1 - iOS/1.12.md
Normal file
222
Chapter1 - iOS/1.12.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# NSFileManager
|
||||
|
||||
> Want to manipulate files? You should learn about NSFileManager.
|
||||
|
||||
Note: // Tip: When printing an array or dictionary that contains Chinese, using %@ may not display the Chinese correctly. You can iterate with for to access and print items.
|
||||
|
||||
* Get the file manager object via the singleton method
|
||||
|
||||
```
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
```
|
||||
|
||||
* Check whether a specified file exists
|
||||
|
||||
```
|
||||
#define LogBool(value) NSLog(@"%@",value==YES?@"YES":@"NO");
|
||||
|
||||
NSString *filepath = @"/Users/geek/Desktop/data.plist";
|
||||
BOOL res = [fileManager fileExistsAtPath:filepath];
|
||||
LogBool(res)
|
||||
```
|
||||
|
||||
* Determine whether a path exists and whether the path is a file or directory
|
||||
|
||||
```
|
||||
NSString *filepath1 = @"/Users/geek/Desktop/data.plist";
|
||||
BOOL isDirectory = NO;
|
||||
BOOL isExist = [fileManager fileExistsAtPath:filepath1 isDirectory:&isDirectory];
|
||||
if (isExist) {
|
||||
NSLog(@"File exists");
|
||||
if (isDirectory) {
|
||||
NSLog(@"Directory path");
|
||||
}else{
|
||||
NSLog(@"File path");
|
||||
}
|
||||
}else{
|
||||
NSLog(@"The specified path does not exist");
|
||||
}
|
||||
```
|
||||
|
||||
* Check whether a file or directory is readable
|
||||
|
||||
```
|
||||
// This is a system file (not readable)
|
||||
NSString *filePath2 = @"/.DocumentRevisions-V100 ";
|
||||
BOOL isReadable = [fileManager isReadableFileAtPath:filePath2];
|
||||
if (isReadable) {
|
||||
NSLog(@"File is readable");
|
||||
} else {
|
||||
NSLog(@"File is not readable");
|
||||
}
|
||||
```
|
||||
|
||||
* Check whether a file is writable
|
||||
|
||||
```
|
||||
// System files are not writable
|
||||
BOOL isWriteAble = [fileManager isWritableFileAtPath:filePath2];
|
||||
if (isWriteAble) {
|
||||
NSLog(@"File is writable");
|
||||
} else {
|
||||
NSLog(@"File is not writable");
|
||||
}
|
||||
```
|
||||
|
||||
* Check whether a file is deletable
|
||||
|
||||
```
|
||||
// System files are not deletable
|
||||
BOOL isDeleteAble = [fileManager isDeletableFileAtPath:filePath2];
|
||||
if (isDeleteAble) {
|
||||
NSLog(@"File can be deleted");
|
||||
} else {
|
||||
NSLog(@"File cannot be deleted");
|
||||
}
|
||||
```
|
||||
|
||||
* Get file attributes / information
|
||||

|
||||
|
||||
```
|
||||
NSError *error = nil;
|
||||
NSDictionary *fileInfo = [fileManager attributesOfItemAtPath:filepath1 error:&error];
|
||||
// NSLog(@"File info:%@, error:%@",fileInfo,error);
|
||||
NSLog(@"File size:%@",fileInfo[NSFileSize]);
|
||||
```
|
||||
|
||||
* List all files and directories under a specified directory (lists all files and folders)
|
||||
|
||||
```
|
||||
NSString *filePath3 = @"/Users/geek/desktop";
|
||||
NSArray *subs = [fileManager subpathsAtPath:filePath3];
|
||||
NSLog(@"All files and folders under Desktop");
|
||||
// Tip: When printing arrays or dictionaries that contain Chinese, using %@ may not show the Chinese; iterate with for to print
|
||||
for (NSString *item in subs) {
|
||||
NSLog(@"%@",item);
|
||||
}
|
||||
```
|
||||
|
||||
* Get the immediate children (files and folders) of a directory (does not include descendants)
|
||||
|
||||
```
|
||||
NSError *erroe = nil;
|
||||
NSArray *children = [fileManager contentsOfDirectoryAtPath:filePath3 error:&erroe];
|
||||
NSLog(@"Files and folders under Desktop");
|
||||
for (NSString *item in children) {
|
||||
NSLog(@"%@",item);
|
||||
}
|
||||
```
|
||||
|
||||
* Create a file at a specified path
|
||||
|
||||
```
|
||||
NSString *filePath1 = @"/Users/geek/Desktop/data.text";
|
||||
NSData *data = [@"I want to learn OC well" dataUsingEncoding:NSUTF8StringEncoding];
|
||||
BOOL createFile = [fileManager createFileAtPath:filePath1 contents:data attributes:nil];
|
||||
if (createFile) {
|
||||
NSLog(@"File created successfully");
|
||||
} else {
|
||||
NSLog(@"File creation failed");
|
||||
}
|
||||
```
|
||||
|
||||
* Create a directory at a specified path (parameter explanation: the withIntermediateDirectories BOOL means: YES -> create intermediate directories as needed; NO -> do not create intermediate directories)
|
||||
|
||||

|
||||

|
||||
|
||||
Set withIntermediateDirectories to NO; if a parent directory does not exist the directory will not be created:
|
||||
|
||||
```
|
||||
NSString *filePath2 = @"/Users/geek/Desktop/OnePiece";
|
||||
NSError *error = nil;
|
||||
BOOL createDirectory = [fileManager createDirectoryAtPath:filePath2 withIntermediateDirectories:NO attributes:nil error:&error];
|
||||
if (createDirectory) {
|
||||
NSLog(@"Directory created successfully");
|
||||
} else {
|
||||
NSLog(@"Directory creation failed, reason: %@",error);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Creating with intermediate directories disabled (fails if parent doesn't exist)
|
||||
NSString *filePath3 = @"/Users/geek/Desktop/OnePiece";
|
||||
BOOL createDirectory1 = [fileManager createDirectoryAtPath:filePath3 withIntermediateDirectories:NO attributes:nil error:&error];
|
||||
if (createDirectory1) {
|
||||
NSLog(@"Directory created successfully");
|
||||
} else {
|
||||
NSLog(@"Directory creation failed, reason: %@",error);
|
||||
}
|
||||
```
|
||||
|
||||
* Copy a file
|
||||
|
||||
```
|
||||
NSString *filePath4 = @"/Users/geek/Desktop/Anime";
|
||||
|
||||
BOOL copyRes = [fileManager copyItemAtPath:filePath3 toPath:filePath4 error:nil];
|
||||
if (copyRes) {
|
||||
NSLog(@"File copied successfully");
|
||||
} else {
|
||||
NSLog(@"File copy failed");
|
||||
}
|
||||
```
|
||||
|
||||
* Move a file
|
||||
|
||||
```
|
||||
NSString *filePath5 = @"/Users/geek/Downloads/Anime";
|
||||
BOOL moveRes = [fileManager moveItemAtPath:filePath3 toPath:filePath5 error:nil];
|
||||
if (moveRes) {
|
||||
NSLog(@"File moved successfully");
|
||||
} else {
|
||||
NSLog(@"File move failed");
|
||||
}
|
||||
```
|
||||
|
||||
* You can rename a file
|
||||
|
||||
```
|
||||
// You can rename a file
|
||||
NSString *filePath6 = @"/Users/geek/Downloads/Cartoon";
|
||||
[fileManager moveItemAtPath:filePath5 toPath:filePath6 error:nil];
|
||||
```
|
||||
|
||||
* Delete a file
|
||||
|
||||
```
|
||||
BOOL deleteRes = [fileManager removeItemAtPath:filePath6 error:nil];
|
||||
if (deleteRes) {
|
||||
NSLog(@"File deleted successfully");
|
||||
} else {
|
||||
NSLog(@"File deletion failed");
|
||||
}
|
||||
```
|
||||
|
||||
# NSFileManager "little virus"
|
||||
```
|
||||
// Get the file manager singleton
|
||||
NSFileManager *fileManager = [NSFileManager defaultManager];
|
||||
NSString *filePath = @"/Users/geek/desktop/delete/";
|
||||
while (1) {
|
||||
// Check whether the path exists
|
||||
BOOL exist = [fileManager fileExistsAtPath:filePath];
|
||||
if (exist) {
|
||||
// Find all files under that path
|
||||
NSArray *subs = [fileManager contentsOfDirectoryAtPath:filePath error:nil];
|
||||
if (subs.count > 0) {
|
||||
for (int i=0; i<subs.count; i++) {
|
||||
NSString *fullFileStr = [NSString stringWithFormat:@"%@%@",filePath,subs[i]];
|
||||
// Check whether the file can be deleted
|
||||
BOOL canDelete = [fileManager isDeletableFileAtPath:fullFileStr];
|
||||
if (canDelete) {
|
||||
[fileManager removeItemAtPath:fullFileStr error:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use a 5-second cycle to continuously scan and delete files
|
||||
[NSThread sleepForTimeInterval:5];
|
||||
}
|
||||
```
|
||||
131
Chapter1 - iOS/1.138.md
Normal file
131
Chapter1 - iOS/1.138.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# AFNetworking Source Code Explanation
|
||||
|
||||
|
||||
|
||||
## Structure
|
||||
|
||||
The core contains 5 functional modules:
|
||||
|
||||
- Network communication module (AFURLSessionManager, AFHTTPSessionManager)
|
||||
- Network reachability module (Reachability)
|
||||
- Network security policy module (Security)
|
||||
- Network request/response serialization/deserialization module (Serialization)
|
||||
- UIKit extensions for iOS (UIKit)
|
||||
|
||||
AF is a wrapper around NSURLSession, so the core class AFURLSessionManager is a wrapper for NSURLSession. The other four modules support networking (request/response serialization, HTTPS security authentication, UIKit extensions).
|
||||
|
||||
<img src="../assets/AFNetworkingProcess.png" style="zoom:60%" />
|
||||
|
||||
AFHTTPSessionManager inherits from AFURLSessionManager. Most network requests are made using AFHTTPSessionManager, but this class itself does not directly perform the networking; it wraps and delegates request logic to its superclass AFURLSessionManager or other classes.
|
||||
|
||||
|
||||
|
||||
## Example: GET request
|
||||
|
||||
```objective-c
|
||||
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]init];
|
||||
|
||||
[manager GET:@"https://somehost.com/goods" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
|
||||
|
||||
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
|
||||
|
||||
}];
|
||||
```
|
||||
|
||||
Create a manager by calling the initializer. Let's see what it does:
|
||||
|
||||
```objective-c
|
||||
- (instancetype)init {
|
||||
return [self initWithBaseURL:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithBaseURL:(NSURL *)url {
|
||||
return [self initWithBaseURL:url sessionConfiguration:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
|
||||
return [self initWithBaseURL:nil sessionConfiguration:configuration];
|
||||
}
|
||||
|
||||
- (instancetype)initWithBaseURL:(NSURL *)url
|
||||
sessionConfiguration:(NSURLSessionConfiguration *)configuration
|
||||
{
|
||||
self = [super initWithSessionConfiguration:configuration];
|
||||
if (!self) {
|
||||
return nil;
|
||||
}
|
||||
// If a BaseURL is provided and it does not end with "/", append "/"
|
||||
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
|
||||
url = [url URLByAppendingPathComponent:@""];
|
||||
}
|
||||
|
||||
self.baseURL = url;
|
||||
|
||||
self.requestSerializer = [AFHTTPRequestSerializer serializer];
|
||||
self.responseSerializer = [AFJSONResponseSerializer serializer];
|
||||
|
||||
return self;
|
||||
}
|
||||
```
|
||||
|
||||
All initializers eventually call the above.
|
||||
|
||||
|
||||
|
||||
## Digital Certificates
|
||||
Digital signatures ensure message integrity and provide authentication.
|
||||
Like public-key cryptography, they use a pair of keys (public and private). But in contrast, signatures are created with the private key (to generate the signature) and verified with the public key. Only the holder of the private key can create the signature, while anyone with the public key can verify it.
|
||||
<img src="../assets/DataSignProcess.png" style="zoom:30%" />
|
||||
|
||||
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
|
||||
79
Chapter1 - iOS/1.24.md
Normal file
79
Chapter1 - iOS/1.24.md
Normal file
@@ -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.
|
||||
155
Chapter1 - iOS/1.29.md
Normal file
155
Chapter1 - iOS/1.29.md
Normal file
@@ -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 <JSExport>
|
||||
- (void)showMessage:(NSString *)message;
|
||||
@end
|
||||
|
||||
// Implementation
|
||||
|
||||
- (void)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 method runs on a background thread; update UI on main thread.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[weakSelf presentViewController:alertCtr animated:YES completion:nil];
|
||||
});
|
||||
}
|
||||
|
||||
// Injection
|
||||
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView
|
||||
{
|
||||
// Get the JSContext from the web view.
|
||||
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
|
||||
|
||||
// Inject the Objective-C object needed by JS
|
||||
self.context[@"Bridge"] = [JSInject new];
|
||||
}
|
||||
|
||||
|
||||
Example
|
||||
|
||||
JS calls Native
|
||||
|
||||
// Native object to expose (with some properties and methods)
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
|
||||
@protocol PersonInjectExport<JSExport>
|
||||
|
||||
@property (nonatomic, strong) NSString *name;
|
||||
|
||||
@property (nonatomic, strong) NSString *hobby;
|
||||
|
||||
- (id)sayHi;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@interface PersonInject : NSObject<PersonInjectExport>
|
||||
|
||||
@property (nonatomic, strong) NSString *name;
|
||||
|
||||
@property (nonatomic, strong) NSString *hobby;
|
||||
|
||||
- (id)sayHi;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// view controller
|
||||
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView{
|
||||
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
|
||||
PersonInject *person = [[PersonInject alloc] init];
|
||||
person.name = @"Hangzhou Xiao Liu";
|
||||
person.hobby = @"Coding, Movie, Music, Table tennis, Fit";
|
||||
self.jsContext[@"lbp"] = person;
|
||||
}
|
||||
|
||||
// JS
|
||||
<body>
|
||||
|
||||
Hi. Hello everyone, I am
|
||||
<p id="name">***</p>
|
||||
|
||||
<button id="show">Then do an introduction</button>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
var u = navigator.userAgent
|
||||
var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1
|
||||
var isiOS = u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/) // iOS device
|
||||
|
||||
document.getElementById("show").onclick = function () {
|
||||
if (isiOS) {
|
||||
document.getElementById("name").innerHTML = lbp.name;
|
||||
setTimeout(() => {
|
||||
alert(lbp.sayHi());
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
91
Chapter1 - iOS/1.43.md
Normal file
91
Chapter1 - iOS/1.43.md
Normal file
@@ -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.
|
||||
24
Chapter1 - iOS/1.50.md
Normal file
24
Chapter1 - iOS/1.50.md
Normal file
@@ -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.
|
||||
159
Chapter1 - iOS/1.87.md
Normal file
159
Chapter1 - iOS/1.87.md
Normal file
@@ -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
|
||||
172
Chapter1 - iOS/1.88.md
Normal file
172
Chapter1 - iOS/1.88.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user