Original article link
Applications can be viewed in graphicsProgramAs a learning experience. This helps you understand the internal behavior of an application and obtain information about program optimization. For example, by optimizing frequently called functions, you can achieve optimal performance with minimal effort. In addition, call tracking can also determine the maximum call depth of user functions, which can be used to effectively limit the memory used by the call stack (in an embedded system, this is a very important factor ).
To capture and display call graphs, you need four elements: GNU Compiler tool chain, addr2line tool, and custom intermediateCodeAnd a code named graphviz. The addr2line tool can recognize functions and addressSource codeNumber of lines and executable images. The custom intermediate code is a very simple tool that reduces the number of URL tracking for graphic specifications. The graphviz tool can generate a graphical image. The entire process 1 is shown.
Figure 1. process of collecting, simplifying, and visualizing tracing paths
Data collection: capture the function call path
To collect traces of a function call, you need to determine the time each function is called in the application. In the past, a unique symbol was inserted at the function entrance and exit to manually detect each function. This process is very cumbersome and error-prone, and usually requires a lot of modifications to the source code.
Fortunately, the GNU Compiler tool chain (also knownGcc) Provides a method to automatically detect various functions in an application. When you execute an application, you can collect relevant analysis data. You only need to provide two special analysis functions. One function is called every time you execute the function you want to trace. The other function is called every time you exit the function you want to trace (see Listing 1 ). These two functions are specially designated, so the compiler can recognize them.
Listing 1. GNU Entry and Exit configuration functions
Void _ cyg_profile_func_enter (void * func_address, void * call_site) _ attribute _ (no_instrument_function); void _ cyg_profile_func_exit (void * func_address, void * call_site) _ attribute _ (no_instrument_function )); |
Avoid using special Detection Functions
You may wonder why GCC is not tested if it is the detection function we need._ Cyg _*
What about analysis functions? GCC developers once thought about this issue and providedNo_instrument_function
Function attribute, which can be applied to the function prototype and cannot be checked. Do not apply this function attribute to the analysis function, which will lead to infinite recursive analysis loops and a large amount of useless data.
When calling a detection function,_ Cyg_profile_func_enter
It will also be called andFunc_address
The address of the called function andCall_site
Format address. When a function exits_ Cyg_profile_func_exit
Function, and passFunc_address
Function address and the real address from which the function exits.Call_site
.
In these analysis functions, you can record the address pairs for later analysis. To request all GCC Detection Functions, each file must use-Finstrument-Functions
And-G
To retain the debugging symbols.
Therefore, you can now provide some analysis functions for GCC. These functions can transparently insert function entry points and function exit points in the application. But how should we handle the provided address when calling the analysis function? You have many options, but for the sake of simplicity, you can simply write this address into a file. Note which address is the function entry address, which address is the exit address of the function (see list 2 ).
Note:In Listing 2, call callsite information is not used, because this information is unnecessary for the analysis program.
List 2. analysis functions
Void _ cyg_profile_func_enter (void * This, void * callsite) {/* function entry address */fprintf (FP, "E % P \ n", (int *) this);} void _ cyg_profile_func_exit (void * This, void * callsite) {/* function exit address */fprintf (FP, "x % P \ n ", (int *) This );} |
Now you can collect and analyze data, But where should you open or close your tracking output file? Up to now, you do not need to make any modifications to the source program for analysis. Therefore, how do you detect the entire application (includingMain
Function) instead of initializing the output results of the analysis data? GCC developers have also considered this issue.Main
The constructor and destructor functions of the function provide some methods that happen to meet this requirement.Constructor
The function is called.Main
Called before the function, andDestructor
The function is called when the application exits.
To create the constructor and destructor functions, you must declare two functions and apply them to them.Constructor
AndDestructor
Function attribute. InConstructor
In the function, a new tracking file is opened, and the address tracking of the analysis data is written to this file;Destructor
Function, the trace file is closed (see listing 3 ).
Listing 3. Analyze the constructor and destructor Functions
/* Constructor and destructor prototypes */void main_constructor (void) _ attribute _ (no_instrument_function, constructor); void evaluate (void) _ attribute _ (no_instrument_function, destructor);/* output trace file pointer */static file * FP; void main_constructor (void) {fp = fopen ("trace.txt", "W "); if (FP = NULL) Exit (-1);} void main_deconstructor (void) {fclose (FP );} |
If you compile an analysis function (in instrument. c) and link them with the target application, and then execute the target application. The result will generate an application call tracing, and the tracing record will be written.Trace.txtFile. The trace file is in the same directory as the called application. The final result is that you may get a file with a very large address. To make the data more meaningful, you can use a less famous GNU tool called addr2line.
Back to Top
Use addr2line to resolve the function address to the function name
The addr2line tool (which is part of the standard GNU binutils) is a tool that converts the instruction address and executable image into a file name, function name, and number of lines of source code. This feature is great for converting tracking addresses into more meaningful content.
To understand how this process works, we can test a simple interactive example. (I operate directly from shell, because this is the easiest way to demonstrate this process, as shown in Listing 4 .) In this example, the C file (test. c) usesCat
Implemented by a simple application (that is, the standard output text is redirected to a file ). Then use GCC to compile the file. It will pass some special options. First, you must-Wl
Option) notifies the linker to generate an image file and-G
Option) notifies the compiler to generate debugging symbols. Generate an executable fileTest. After obtaining a new executable application, you can useGrep
Find the tool in the Image FileMain
To find its address. Use this address and the addr2line tool to determine the function name (Main
), The source file (/home/mtj/test. C), and its row number in the source file (4 ).
Use the addr2line Tool-E
Option to specify whether the executable image isTest
. Use-F
Indicates the name of the output function.
Listing 4. An interactive example of addr2line
$ Cat> test. C # include <stdio. h> int main () {printf ("Hello world \ n"); Return 0 ;}< ctld-D> $Gcc-wl,-map = test. Map-g-o Test test. c$ Grep main test. map0x08048258 _ libc_start_main @ glibc_2.00x08048258main $Addr2line 0x08048258-E test-FMain/home/mtj/test. C: 4 $ |
Addr2line and debugger
The addr2line tool provides basic symbolic debugging information, but GNU Debugger (GDB) uses other internal methods.
Back to Top
Streamlined function tracking data
Now you have a method to collect tracing data for the function address. You can also use the addr2line tool to convert the address to the function name. However, how can we streamline a large amount of tracing data from an application to make it more meaningful? This is where custom intermediate code is used to establish a connection between open-source tools. This article provides the complete annotated code of this tool (pvtrace), including instructions on how to compile and use this tool. (For more information, see the download section .)
Recall the content in step 1 and create an application namedTrace.txt. The files that people can read contain a series of address information-one address per line and one prefix character per line. If the prefix isE, Then this address is the entry address of a function (that is, you are calling this function ). If the prefix isXThen this address is an exit address (that is, you are exiting from this function ).
Therefore, if there is an entry address (A) in the tracking file followed by another entry address (B), you can infer that a calls B. If an entry address (a) is followed by an exit address (a), it indicates that this function (a) is directly returned after being called. When a large number of call chains are involved, it is difficult to analyze who calls them. Therefore, a simple solution is to maintain a stack of the entire address. Every time a tracking file encounters an entry address, it is pushed into the stack. The address at the top of the stack represents the last called function (that is, the current active function ). If another entry address is followed, the address in the stack calls the address that was just read from the tracking file. When the function exits, the current active function returns and releases the top element of the stack. This will return the context back to the previous function, so that a correct call chain process can be generated.
Figure 2 describes this concept and how to streamline data. When analyzing the call chain in the trace file, a connection matrix is built to indicate which function calls other functions. The rows in this matrix represent the address of the called function, and the columns represent the called address. For each call pair, the intersection of rows and columns is accumulated continuously (number of calls ). When processing a complete trace file, the result is a very simple representation of the entire call history of the application, including the number of calls.
Figure 2. Process and streamline the tracking data and generate a matrix format
Compile and install tools
After downloading and decompressing the pvtrace tool, you only need to enterMake
Command to compile the pvtrace tool. You can also use the following code to install the tool to the/usr/local/bin directory:
$ Unzip pvtrace.zip-D pvtrace
$ CD pvtrace
$ Make
$ Make install
Now we have built a simplified function connectivity matrix. Next we should build a graphical representation. Let's take a deeper look at graphviz and understand how to generate a call chart from the connection matrix.
Back to Top
Use graphviz
Graphviz or graph visualization is an open-source graphical visualization tool developed by at&t. It provides a variety of drawing capabilities, but our focus is on its ability to directly connect to images using the dot language. In this article, we will briefly introduce how to use dot to create a graph and demonstrate how to convert the analysis data to a specification that graphviz can use. (For more information about downloading this open-source software, see references .)
Dot graphics specifications
Using the dot language, you can specify three objects: graphs, nodes, and edges. To help you understand the meaning of these objects, we will build an example to demonstrate the usage of these elements.
Listing 5 shows a simple directed graph with three nodes. The first line declares this imageGAnd declare the graph type (digraph ). The following three lines of code are used to create nodes for the graph.Node1,Node2AndNode3. Nodes are created when their names appear in the graph specification. Edges are operated by edges on two nodes (->
) Created when the connection is made, as shown in rows 6th to 8th. I also used an optional attribute on the edge.Label
To indicate the name of an edge in the graph. Finally, define the graph specification in Row 3.
Listing 5. Example graph represented by the dot symbol (test. Dot)
1: digraph G {2: node1; 3: node2; 4: node3; 5: 6: node1-> node2 [label = "edge_1_2"]; 7: node1-> node3 [label = "edge_1_3"]; 8: node2-> node3 [label = "edge_2_3"]; 9 :} |
To convert the. Dot file into a graphical image, you need to use the dot tool, which is provided in the graphviz package. Listing 6 describes the conversion.
Listing 6. Use dot to create a jpg image
$ Dot-tjpg test. Dot-O test.jpg $ |
In this Code, I told Dot to use the test. Dot graphic specification, generate a jpg image, and save it in the test.jpg file. The generated image 3 is shown. Here, I use JPG, but the dot tool also supports other formats, including GIF, PNG, And postscript.
Figure 3. Example of dot Creation
The dot language also supports other options, including shape, color, and many attributes. However, this option is sufficient for the functions we want to implement.
Back to Top
Comprehensive
Now we have seen all stages of the process. The following example shows how to merge these stages. Now you have expanded and installed the pvtrace tool, and copied the instrument. c file to the working source code directory.
In this example, a source file is used.Test. c. Listing 7 shows the entire process. In row 3, I used the detection source (instrument. c) to build (compile and connect) applications. Then executeTest
, And then useLs
Command to verify that the trace.txt file has been generated. In row 8th, I called the pvtrace tool and provided this image file as its only parameter. The image name is required, so that addr2line (called in pvtrace) can access debugging information in this image. In row 9th, I executed anotherLs
Command to ensure that the pvtrace generates the graph. Dot file. Finally, in Row 3, use dot to convert the image specification into a jpg image.
Listing 7. The entire process of creating a call trace Graph
1: $ ls 2: instrument. C test. C 3: $$ Gcc-g-finstrument-Functions Test. c instrument. C-o Test4: $./Test 5: $ ls 6: instrument. C test. C 7: Test trace.txt 8: $Pvtrace Test9: $ ls10: Graph. Dot test trace.txt 11: instrument. C test. C12: $Dot-tjpg graph. Dot-O graph.jpg13: $ ls14: Graph. Dot instrument. C test. C15: graph.jpg test trace.txt 16: $ |
Sample output 4 of this process is shown in. This example is obtained from a simple enhanced learning application using Q.
Figure 4. tracing results of the sample application
You can also use this method to analyze larger applications. The last example I want to show is the gzip tool. I simply add instrument. C to the makefile of gzip as a source file on which it depends, compile gzip, and use it to generate a trace file. This image is too big to perform more detailed analysis, but it indicates the processing process when gzip compresses a small file.
Figure 5. gzip tracking result
Back to Top
Conclusion
With open-source software and a small amount of intermediate code, it takes very little time to develop very useful projects. By using several GNU Compiler extensions that analyze applications, you can use the addr2line tool to perform address translation and visualize graphviz applications. Then you can get a program, this program can analyze the application and display a directed graph indicating the call chain. It is very important to understand the internal behavior of an application to view the call chain of an application through graphs. After correctly understanding the call chain and its frequency, this knowledge may be useful for debugging and optimizing applications.
Back to Top
Download
Description |
Name |
Size |
Download Method |
Instrumentation source and pvtrace Source |
Pvtrace.zip |
4 kb |
HTTP |
Information about the Download Method