Home > Back-end >  C language function calls relationship of those little secret
C language function calls relationship of those little secret

Time:10-01

Display function call relationship is the essential features of the debugger, if we imploded in the running of the program, through function calls relations can quickly locate at the root of the problem, understand the relationship between function calls the principle can also expand own aspect of knowledge, in the absence of a debugger, we also can oneself to realize the display function call relationship, before we write a backtrace function myself, let's look at the glibc provide backtrace function, the use of the code is as follows:
This paper reference address: http://www.xxylyg.com/jiaoxuekeyan/news/2.html
# include
# include
# include
# define MAX_LEVEL 4
The static void call2 ()
{
int i=0;
Void * buffer [MAX_LEVEL]={0};
Int size=backtrace (buffer, MAX_LEVEL);
For (I=0; I & lt; The size; I++)
{
Printf (" called by % p \ n ", buffer [I]);
}
return;
}
The static void call1 ()
{
Call2 ();
return;
}
The static void the call ()
{
Call1 ();
return;
}
Int main (int arg c, char * argv [])
{
call();
return 0;
}
Under the first interpretation of the backtrace () function is used:
Int backtrace (void * * buffer, int size)
This function is used to retrieve the current thread call stack, the information will be stored in a buffer, it is a pointer list, size parameters used to specify buffer can hold how many void * elements, function return value is to obtain a pointer to the actual number of maximum size is less than the size, in the buffer pointer to the actual from the stack to obtain the return address, each stack frame have a return address,
The next task is to compile the operation,
Root @ ubuntu:/home/shiyan# GCC -g -wall SSS. C - o p
Root @ ubuntu:/home/shiyan#./p
The output is:
Called by 0 x8048440
Called by 0 x804847d
Called by 0 x804848a
Called by 0 x8048497
The operation of the above result, the caller address, did not seem intuitive, we use the addr2line tool to realize the address to the source code position transformation,
Run
Root @ ubuntu:/home/shiyan#./p | awk '{print "addr2line" $3 "- e p}' & gt; T.s h; . T.s h; The rm -f t.s h
The output is:
/home/shiyan/SSS. C: 12
27/home/shiyan/SSS. C:
/home/shiyan/SSS. C: 34
/home/shiyan/SSS. C: 40
Then look at the stack data structure,
Function parameters of the stack from right to left, the first pressure last parameter, in the overwhelming number of the second, and so on, finally into the first parameter, in order to deepen your impression, I give a test code below:
This paper reference address: http://www.eepw.com.cn/article/270700.htm
# include
Void turn (int x, int y, int z)
{
Printf (" x=% d at [x] % \ n ", x, & amp; X);
Printf (" y=% d at [X] % \ n ", y, & amp; Y);
Printf (" z=% d at [X] % \ n ", z, & amp; Z);
}
Int main (int arg c, char * argv [])
{
Turn (1, 2, 3);
return 0;
}
Results:



Compare the printed address can see how the parameter z is one of the biggest, the address of the minimum x,
Parameters of pressure stack work is completed, the next is in turn the EIP, EBP, temporary variable pressure stack operation, finally pushed the called function itself, and assigned a temporary variable space for it, and for the treatment of different versions of GCC are different, the old version of GCC temporary variables on the highest address first, the second second, ordinal sequence distribution, the new version of the GCC is, on the other hand,
Realize the backtrace () function call relationship, its steps are as follows:
1. Access to the current function of EBP;
2. Get the caller by EBP EIP;
3. The higher level of EBP were obtained through the EBP;
4. Repeat the process, know the end,
Their implementation backtrace () function, the code is as follows:
# include
# define MAX_LEVEL 4
# define OFFSET 4
Int backtrace (void * * buffer, int size)
{
Int n=0 x23f;
Int * p=& amp; n;
int i=0;
Int ebp=p [1 + OFFSET];
Int the eip=p [2 + OFFSET];
For (I=0; I & lt; The size; I++)
{
Buffer [I]=eip (void *);
P=(int *) ebp;
Ebp=p [0];
The eip=p [1];
}
Return the size;
}
The static void call2 ()
{
int i=0;
Void * buffer [MAX_LEVEL]={0};
Int size=backtrace (buffer, MAX_LEVEL);
For (I=0; I & lt; The size; I++)
{
Printf (" called by % p \ n ", buffer [I]);
}
return;
}
The static void call1 ()
{
Call2 ();
return;
}
The static void the call ()
{
Call1 ();
return;
}
Int main (int arg c, char * argv [])
{
call();
return 0;
}
The results are as follows:
Root @ ubuntu:/home/shiyan# GCC -g America c - o tt
Root @ ubuntu:/home/shiyan#./tt
Called by 0 x8048491
Called by 0 x80484ce
Called by 0 x80484db
Called by 0 x80484e8
Into the source code location: root @ ubuntu:/home/shiyan#/tt | awk '{print "addr2line" $3 "- e tt"}' & gt; T.s h; . T.s h; The rm -f t.s h
Root @ ubuntu:/home/shiyan#./tt | awk '{print "addr2line" $3 "- e tt"}' & gt; T.s h; . T.s h; The rm -f t.s h
/home/shiyan/bac. C: 32
/home/shiyan/bac. C: 47
/home/shiyan/bac. C: 54
/home/shiyan/bac. C: 60
Under the emphasis on the backtrace () function implementation principle,
Through the int * p=& amp; n; To obtain the location of the temporary variable first, because I use the new version of GCC, there are five temporary variables, so the values of the EIP in p [6], the values of the EBP in p [5], through the buffer [I]=EIP (void *); Can put the eip cast to can point to any type of pointer, then through p=(int *) ebp; For a function on the ebp, by the ebp after winning the ebp the position relationship between the eip and be able to get the eip, due to the ebp, pointing to the storage unit is a function of the ebp, so with a simple for loop can be achieved,
In addition in the header file "execinfo. H" in addition to the statement in the backtrace () function, and the following two functions are used to get the current thread function call stack,
Const char * * backtrace_symbols (void * * buffer, int size)
Backtrace_symbols information was obtained from the backtrace function can be converted to an array of strings. Parameters of the buffer should be obtained from the backtrace function array pointer, the size is the number of elements in the array (the return value of a backtrace)
Function return value is a pointer to an array of strings, the size of it with the same buffer. Each string contains a relative to the corresponding element in the buffer can print information. It includes the function name, function of offset, and the actual return address
Now, only using the ELF binary format and difficulties to access the function name and the offset address. In other systems, only the hexadecimal return address can be obtained. In addition, you may need to pass the corresponding marks to the linker, to support the function name functions (for example, in the use of the GNU ld system, you need to pass (- rdynamic)), the function of the return value is by applying the malloc function space, therefore it must use a free function called to release the pointer,
Note: if you can't to get the string enough space function return value will be NULL,
Function: void backtrace_symbols_fd (void * const * buffer, int size, int fd)
Backtrace_symbols_fd and backtrace_symbols function with the same function, different is that it won't return to a string array to the caller, but the result will be written to the file descriptor for fd file, each function corresponding to a line. It doesn't need to malloc function, therefore is suitable for the may call the function will fail,

CodePudding user response:

  • Related