An exhausted bug change experience
I. Introduction
Recently, I have been working on technical requirements for JavaScriptCore. I found a problem last week. When JavaScriptCore is in garbage collection, the project may crash. The call stack when a crash occurs is as follows:
Figure 1 Call Stack
First, describe the two most important stack processes:
Figure 2 generate JSValue
1) toJSValueInContext: The method is to generate a JSValue through JSObjectMake. For example, a JSValue is returned, and the JSValue makes a strong reference to self (PHOValue type.
Figure 3 JSValue release callback
2) PHOObject_finalizeCallback is a JSValue destructor. It is called when a JS object generated through JSObjectMake is released. In this function, we release the self (PHOValue type) strongly referenced before ). When self is released, object A that is strongly held by self will be released. In the dealloc Method for further execution of A, in the dealloc method, we call the JSObjectMake function again to generate other objects and hold the object again, and pass JSValue into JS for other method calls (if you do not understand this problem, refer to JSPatch's handling of rewrite dealloc method, but the difference is that JSPatch does not rely on garbage collection ).
To illustrate the problem, a memory flow diagram is provided to help you understand the problem:
Figure 4 memory and process description 2. Problem locating
In order to locate the problem, we have made many guesses. Here we will list two representative guesses.
Conjecture 1: dealloc does not allow strong references to objects executing dealloc.
There is a certain probability of this problem, and an error such as Thread 1: EXC_BREAKPOINT (code = exc_i1__bpt, subcode = 0x0) is reported, therefore, we initially focused on tracing the wild pointer. The crash occurred when self was dealloc, but at this time we made a strong reference to self (see Figure 2 code ). In this case, the reference count of self is + 1, so it is estimated that the dealloc of self may be triggered again. However, when a crash occurs, you can view parameters such as self and context in the po operation and find that all parameters are accessible normally. This is not consistent with the call stack phenomenon. At least we did not see two calls to dealloc. Therefore, this conjecture is not true.
Conjecture 2: JavaScriptCore does not allow JSObjectMake during garbage collection.
From the call stack perspective, every crash occurs after JSObjectMake. Does this mean that JSObjectMake cannot be performed during garbage collection? To verify this problem, we do not release any objects in the PHOObject_finalizeCallback function, and only execute JSObjectMake once,
Figure 5 call JSObjectMake In the callback
This change means that JSObjectMake will be called immediately as long as it is in JavaScriptCore for garbage collection. After verification, it was found that the call stack was basically consistent, and the crash occurred here, and the call stack was completely consistent. Therefore, our conjecture is correct. If you think about this problem carefully, experienced people may be excited because the garbage collection mechanism is not under our control. We cannot guarantee that it will not be in the garbage collection period during JSObjectMake, in theory, a crash occurs. Why has this problem not been exposed before? We create objects 100000 times in a loop and continuously trigger garbage collection through the safari debugging function without crashing. JavascriptCore has two types of garbage collection methods: Synchronous garbage collection and asynchronous garbage collection. Either way, JavascriptCore has a common Heap (Heap, the garbage collection process of JavascriptCore is in Heap. in cpp). In other words, under normal circumstances, JSObjectMake cannot access the heap during garbage collection.
Figure 6 two garbage collection methods of JSCore
The cause of the crash is that we have accessed the heap in the garbage collection callback of the object. The pseudocode for this problem is as follows:
Figure 7 pseudocode 3. Solutions
Since the cause of the problem is basically located, the next step is to find a solution to solve the problem. The root cause of the problem is that we want to release the indirectly held OC object when the JS variable is released. If we cannot release it during garbage collection, so does it mean that we can avoid this problem as long as we get the garbage collection start and end callback of JavascriptCore? After searching for JavascriptCore, we found that the callback status still exists, but the interface is not open to us. Heap. h contains an interface for adding observers.
Figure 8 add an observer
When the process of garbage collection and garbage collection ends, the observer will be notified:
Figure 9 start callback
Figure 10 end callback
Now the problem arises. Now that we know the callback method, how can we obtain the callback? At the OC level, we can hook through runtime, and even at the C level, we can implement hook through the fishhook of fb, in C ++, how do we hook a function with a namespace? (We have no idea how to solve this problem. If someone knows how to hook a C ++ function in iOS, please leave a message in time ). After a series of attempts, we abandoned the method of the hook C ++ function and turned to other methods. Back to the original purpose, we actually want to ensure that our JSObjectMake will be executed after garbage collection. Therefore, the delay operation of GCD is a good idea, but how long is the delay? This solution does not seem so perfect. So what else is a delayed release operation? _ Autoreleasing should be a good choice. When _ autoreleasing is added before an object, the object will be released only after the automatic release pool is released. When the automatic release pool is released, the current runloop must be over, that is, the garbage collection must be over (it is impossible to separate a garbage collection into two runloops ). Therefore, you only need to change the code as shown in Figure 11 below.
Figure 11 solution 4. Summary
This problem is still difficult to locate. First, it is difficult to locate the problem caused by garbage collection, and second, it is difficult to find better callbacks, especially hook c ++ functions, we failed many attempts. If someone has implemented the hook C ++ function in the iOS system, please don't hesitate to give me some advice. Thank you!