Get any thread that calls the stack.

Source: Internet
Author: User

Bsbacktracelogger is a lightweight framework that can get the call stack of any thread, open source on my GitHub, which is recommended to download and read in conjunction with this article.

We know that there is a NSThread class method callstackSymbols that can fetch the call stack, but it outputs the call stack of the current thread. In the use of Runloop detection card immediately, the sub-thread detected the main thread of the stutter, need to pass the main thread of the call stack to analyze the specific method caused the blocking, then the system provides a way to do nothing.

The simplest and most natural idea is dispatch_async to use or performSelectorOnMainThread wait to go back to the main thread and get the call stack. Needless to say, this idea is not feasible, otherwise there is no need to write this article.

The focus of this article is not to introduce the details of getting the call stack, but the many problems encountered and the solutions that have been tried in the implementation process. Some programs may not solve the problem, but in the process of thinking can put together the knowledge points, in my opinion this is the greatest value of this article.

Before introducing the following knowledge, it is necessary to introduce the relevant background knowledge of the call stack .

Call stack

The first thing to talk about is the stack, which is a data structure that is unique to each thread. Borrow a photo from Wikipedia:

Represents a stack, it is divided into several stack frames (frame), each stack frame corresponding to a function call, such as the blue part is DrawSquare the function of the stack frame, it is executed in the process of calling a DrawLine function, stack frame in green representation.

You can see that the stack frame consists of three parts: the function parameter, the return address, the variable within the frame. For example, when calling a DrawLine function, the parameter of the function is first put into the stack, which is the first part, then the address is returned to the stack, which indicates where the current function will go back to execution, and the variable defined inside the function is part of the third part.

Stack Pointer (stack pointer) represents the top of the current stack, as the stack of most operating systems grows downward, it is actually the minimum value of the stack address. According to the previous explanation, the address that Frame Pointer points to, stores the value of the last Stack Pointer, which is the return address.

In most operating systems, each stack frame also holds the frame Pointer of the previous stack frame, so you know the stack Pointer and frame Pointer of the previous stack frame as long as the stack Pointer and frame Pointer of the current stack frame are known, Thereby recursively gets the frame of the bottom of the stack.

Obviously, when a function call ends, its stack frame does not exist.

Therefore, the call stack is actually an abstract concept of the stack, which represents the call relationship between methods, generally from the stack can parse out the call stack.

Traditional methods of failure

The initial idea is simple, since callstackSymbols only the call stack of the current thread can be obtained, which is called on the target thread. For example, dispatch_async to the main queue, or performSelector series, not to mention can also use Block or proxy methods.

Let UIViewController 's take the viewDidLoad method as an example and speculate on what is happening at the bottom.

First, the main thread is also threads, you have to follow the thread Basic Law. The thread Basic Law says that the first thing to do is to run the thread, and then (if necessary, for example, the main thread) start Runloop to survive. We know that the essence of Runloop is a dead loop, call multiple functions in the loop, Judge Source0, Source1, Timer, Dispatch_queue and other event sources have to deal with the content.

The UI-related events are source0, so they will be executed __CFRunLoopDoSources0 , and eventually step into the way viewDidLoad . When the event is finished processing, Runloop enters the hibernation state.

Assuming we use dispatch_async it, it wakes up the Runloop and handles the event, but at this point it __CFRunLoopDoSources0 's done and it's not possible to get to viewDidLoad the call stack.

performSelectorThe bottom of the series method is also dependent on runloop, so it just commits a task like the current runloop, but still waits for the existing task to complete before it can execute, so it cannot get the real-time call stack.

All in all, anything that involves runloop, or the need to wait for viewDidLoad execution, is unlikely to succeed.

Signal

To be independent of viewDidLoad completion and execute code in the main thread, you can only start at the operating system level. I tried to use the signal (Signal) to achieve,

The signal is actually a soft interrupt and is handled by the system's interrupt handler. When the signal is processed, the operating system saves the executing context, such as the value of the register, the current instruction, and then processes the signal, and then resumes the execution context when the processing is complete.

So theoretically, the signal can force the target thread to stop and process the signal and restore it. In general, the signal is sent for the entire process, and any thread can accept and process it, or pthread_kill() send a signal to the specified thread.

Signal processing can be used signal or sigaction to achieve, the former is relatively simple, the latter function more powerful.

For example, when we run the program and press the Ctrl + C actual SIGINT signal, the following code can Ctrl + C do some output and avoid the program exit when it is pressed:

void sig_handler(int signum) {    printf("Received signal %d\n", signum);}void main() {    signal(SIGINT, sig_handler);}

Unfortunately, the use pthread_kill() of the signal does not seem to be properly handled by the above method, access to a variety of data after the failure to abandon the idea. But it still seems to be possible, if anyone knows.

Mach_thread

Recall before the introduction of the stack, as long as you know Stackpointer and Framepointer can fully determine a stack of information, there is no way to get all the threads of Stackpointer and framepointer it?

The answer is yes, first the system provides a task_threads way to get all the threads, note that the thread here is the lowest level of the Mach thread, and its relationship with Nsthread will be elaborated later.

For each thread, you can thread_get_state get all of its information in a method, and the information is populated in the parameters of the _STRUCT_MCONTEXT type. There are two parameters in this method that vary with the CPU architecture, so I define the BS_THREAD_STATE_COUNT BS_THREAD_STATE difference between the two macros used to mask different CPUs.

In a _STRUCT_MCONTEXT struct of type, the frame Pointer of the current thread's stack Pointer and the topmost stack frame is stored, thus acquiring the call stack for the entire thread.

In the project, the call stack is stored in backtraceBuffer an array where each pointer corresponds to a stack frame, and each stack frame corresponds to a function call, and each function has its own symbolic name.

The next task is to get the symbolic name of the function call according to frame Pointer of the stack frame.

Symbolic parsing

Just like "take a few steps to put an elephant in the freezer", getting the symbol name corresponding to Frame Pointer can also be divided into the following steps:

    1. Find the address of a function call according to Frame Pointer
    2. Find out which image file Frame Pointer belongs to
    3. Locate the symbol table for the image file
    4. Find the symbol name corresponding to the function call address in the symbol table

This is actually a C language programming problem, I have no relevant experience, but fortunately there are predecessors of the research results can be used for reference. Interested readers can read the source code directly.

Secret Nsthread

Based on the above analysis, we can get all the threads and their call stacks, but what if we want to get the stack of a thread separately? The problem is how to establish a connection between the nsthread thread and the kernel thread.

Once again Google no fruit, I found the source of the gnustep-base, downloaded 1.24.9, which contains the Foundation library source code, I can not ensure that the current nsthread fully adopt the implementation here, but at least from the NSThread.m class to dig out a lot of useful Information.

Nsthread Package Level

Many articles mention that Nsthread is a pthread package, which involves two questions:

    1. What is Pthread?
    2. Nsthread How to encapsulate Pthread

The letter P in Pthread is a shorthand for POSIX, and POSIX means "Portable operating System Interface (Portable Operating system Interface)".

Each operating system has its own threading model, and different operating systems provide the same API for threading, which poses a problem for cross-platform thread management, and POSIX's purpose is to provide abstract pthread and related APIs that have different implementations in different operating systems. But the finished function is consistent.

The Unix system provides the thread_get_state and task_threads such methods, the operation is the kernel thread, each kernel thread is thread_t uniquely identified by the ID of the type, and the unique identifier of the pthread is the pthread_t type.

The conversion of kernel threads and pthread (that is thread_t , and in pthread_t turn) is easy, because Pthread is born to abstract kernel threads.

Said Nsthread encapsulated Pthread is not very accurate, nsthread inside only a few places to use the pthread. The simplified version of the Nsthread start method is implemented as follows:

- (void) start {  pthread_attr_t    attr;  pthread_t     thr;  0;  pthread_attr_init(&attr);  ifself)) {      // Error Handling  }}

Even Nsthread does not store the identity of the new Pthread pthread_t .

Another place to use the pthread is when Nsthread exits, and the call is made pthread_exit() . In addition, there is little sense of pthread, so the individual thinks "nsthread is the encapsulation of pthread" is not accurate.

Performselectoron

In fact, all the performSelector series will eventually go to the following almighty function:

- (void) performSelector: (SEL)aSelector                onThread: (NSThread*)aThread              withObject: (id)anObject           waitUntilDone: (BOOL)aFlag                   modes: (NSArray*)anArray;

And it's just a wrapper, depending on how the thread gets to Runloop, the real call or the Nsrunloop method:

- (void) performSelector: (SEL)aSelector          target: (id)target        argument: (id)argument           order: (NSUInteger)order           modes: (NSArray*)modes{}

This information will form an Performer object to be put into the runloop waiting to be executed.

Nsthread Turn kernel thread

Because the system does not provide the appropriate conversion method, and Nsthread does not retain the thread pthread_t , so the conventional means can not meet the requirements.

One way of thinking is to use the performSelector method to execute code on the specified thread and record thread_t that the execution of the code is not too late, and the call stack will break if it executes when the call stack is printed. The best way to do this is when the thread is created, as mentioned above using the pthread_create method to create the threads, and its callback functions nsthreadLauncher are implemented as follows:

staticvoid *nsthreadLauncher(void* thread){    NSThread *t = (NSThread*)thread;    nil];    [t _setName: [t name]];    [t main];    [NSThread exit];    returnNULL;}

The amazing discovery system actually sends a notification, the notification name is not available, but you can listen to all the notification name method to know its name: @"_NSThreadDidStartNotification" , so we can listen to this notification and call performSelector method.

General Nsthread is initWithTarget:Selector:object created using methods. The selector is executed in the main method, and the thread exits after the main method executes. If you want to do thread keepalive, you need to open runloop in the incoming selector, see my article: In-depth study runloop and thread keepalive.

It can be seen that this scenario is not realistic because it has been explained before and performSelector relies on runloop to open, and runloop until the main method is possible to open.

Looking back, we need a link between the Nsthread object and the kernel thread, that is, to find a unique value for the Nsthread object, and the kernel thread has this unique value.

Look at Nsthread, whose only value is the object address, the object sequence number (Sequence numbers), and the thread name:

0x144d095e0>{number1name = main}

The address is allocated on the heap, no meaning is used, the calculation of the serial number is not understood, so only name is left. Fortunately Pthread also provides a way pthread_getname_np to get the name of the thread, both of which are consistent, and interested readers can read the setName implementation of the method themselves, and it invokes the interface provided by the pthread.

The NP here means not POSIX, which means it doesn't work across platforms.

So the solution is simple, for the Nsthread parameter, change its name to a random number (I chose the timestamp), then traverse Pthread and check for a matching name. After the search is complete, restore the name of the parameter.

Main thread Turn kernel thread

Originally thought the problem has been satisfactorily resolved, behold there is a pit, the main thread setting name cannot be pthread_getname_np read to.

Fortunately we can also detour to solve the problem: get the main thread in advance thread_t , and then do the comparison.

The above scenario requires that we execute the code in the main thread to get it thread_t , and obviously the best solution is in the Load method:

static mach_port_t main_thread_id;+ (void)load {    main_thread_id = mach_thread_self();}
Summarize

The above is the whole analysis of Bsbacktracelogger, it has only one class, 400 lines of code, so it is relatively simple. However, the source of Nsthread, Nsrunloop and GCD is worthy of repeated research and reading.

Accomplishing a technical project is often the biggest harvest not the final result, but the realization of the process of thinking. These detours deepen the understanding of the knowledge system.

Follow and subscribe

Search "Ioszhazha" attention to the public number, the first time to get updates

Resources
    1. Call Stack
    2. Kscrash
    3. Deep understanding of Runloop
    4. Capture and analysis of iOS thread call stack (i), capture and reconciliation of iOS call stack (ii)

Get any thread that calls the stack.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.