Profiling Stack
Author: qingapple studio Compilation
This section discusses some common concepts of stacks. The characteristics of a stack depend on the type of the CPU platform, the compiler used to generate the program, and the way the code is written.
The following content is rewritten from the book "into ER Inside and Out (McGraw Hill Press:
Stack is used to temporarily store information. This is important when you are handling the process.
Most medium-sized and large programs are divided into relatively independent modules, called processes. (In advanced languages, these modules are generally called functions or subprograms ). When a process calls another process, you can say that the first call is the second. When the second process ends, the program returns the first process.
As part of executing a call command, the processor must obtain a position in the first process. When the second process ends, the execution is resumed here. This location is called the return address.
The processor pushes the returned address into the stack to save it. When the second process returns, the return address of the Process pops up the stack and resumes execution at this position. Because the stack pop-up items are post-in, first-out, and the process can call other processes before the call, they will still return the correct position.
Another important purpose of the stack is to save the register content at the beginning of a process. The register can be restored by popping up the stack with the old value before the program returns. This allows each process to use registers as needed. But from the perspective of each call process, the register value remains unchanged.
Stack is also used to transmit information from one process to another. The process pushes the data into the stack and then calls the second process to use the data. If necessary, the second process can be passed back with the stack.
The last purpose of a stack is to store temporary results that occur in complex operations. Sometimes you can store some temporary values in general-purpose registers (on the CPU), and sometimes push them into the stack. The method to use depends on the logic of the specific program.
Stack and multi-stream applications
The preceding memory diagram (figure 6) only shows a portion of the memory associated with the stack. However, each stream has its own stack, because each stream needs to understand its own history, which is completely independent from other streams in the process.
Generally, the stack grows down (from high to low memory address) and is created by pushing a value into it. The position specified for the next value is determined by the value of the ESP register. When a value is pushed, the processor automatically reduces ESP and points to the next information location.
The following example shows an execution stream. Note the pseudocode of three different functions. The first method is to call the next one, and the second method is to call the third one. Every time you make a call, you add a framework to the stack. This framework stores information, allows the process to return and transmits the information back and forth.
Figure 8: Stack
Understand the stack framework
Figure 8 shows how to change the execution line when a program encounters a jump statement, such as call or jnz. As one jump is transferred to another part of the code for further execution, you need to save the information to the stack here and allow the process to return at the end of the jump. The data you save to the stack can be small or large. Regardless of the size, all the data stored here is called a framework. Therefore, in the above example, you actually added two frameworks to the stack: one is when the top function calls the second function, and the other is when the second function calls the third function. When the third function returns, the second function is restored and the execution continues by restoring its stack framework.
The stack information actually provides a roadmap for program execution. When you analyze a log generated by an accident monitor, you can view the road signs and find out what your application is doing when a fault occurs. When you start ReadLogs or fault stack for the first time, each framework on the stack corresponds to a row in the primary output window of ReadLogs.
Heap and stack conflict
Yes, heap conflict. Let's take a look at this sentence. Wh @ I waMARY HAD A LITTLE LAMBn this subject.
Obviously, the above sentence is conflicted. If you consider the memory space used by the program and use the above sentence as the data (or code) required by the program, you will find it difficult for the program to use the information. This is a form of heap conflicts. Heap is a part of the memory, and the program uses it to store information that needs to be restored in the future. For example, app stream 1 decides what to discuss (a heap conflict occurs at this time), and app stream 2 decides to sing a song. Well, stream 1 gets some heap memory to store its monologue, which writes these monologue to the memory.
For example, stream 2 also gets some memory, and it sings so happily that it forgets where it exists, so it decides to start from a place. Well, it will overwrite the monologue in Stream 1. Now it's time for stream 1 to read its monologue back, but it won't work, so it calls the operating system and says, "I can't stand it now", and then it will die. This is an accident. Unfortunately, the operating system does not know that stream 2 has caused this accident because it has completed singing and made some calls and went home for the weekend.
This may not be the most technical and accurate description of the heap conflicts you have seen, but it expresses the general meaning. Basically, every time a stream is written into memory that it does not own, it is at risk of overwriting the data or code owned by other streams. The biggest problem here is that these covered memories are sometimes not used. When it is finally called, the annoying code that overwrites it may no longer run, so you do not know what causes a conflict due to memory corruption.
The same situation applies to stack conflicts, but a pointer (or memory address) on the stack points to an error address. The CPU finds this pointer and says, "I should come here to find the next command to execute ". But what if the pointer points to null memory? This is usually because some code passes an incorrect pointer to the next function in the stack. Now back to the stack track, we can see that there are multiple stack frames in the stack list. This pointer can be passed many times before it is actually used.
You cannot say that the function at the top of the stack track is usually a faulty function. There are two reasons. It may get the error pointer from below (this means that other functions on the stack cause this problem ), maybe it encountered a problem when accessing some previously corrupted memory (which means the criminal may no longer exist ). Sometimes you may be able to detect heap conflicts because the faulty Stack has the same calls as RtlCoalesceFreeBlocks. You can also find some string operation functions, such as strcat, strcpy, and memcpy. If improperly used, they often cause heap conflicts. Stack conflicts also exist in many forms, but they are beyond the scope of this article and are not discussed in depth.