Three [pile insertion] techniques for tracking the call of [library function] in Linux

Author: Daoge, a 10 + year embedded development veteran, focusing on: C/C + +, embedded and Linux.

Pay attention to the official account below, reply to books, get classic books in Linux and embedded field. Reply to [PDF] to obtain all original articles (PDF format).

catalogue

Other people's experience, our ladder!

What is pile insertion?

In code with a little scale (C language), it is a common working scenario to call functions in a third-party dynamic library to complete some functions.

Suppose there is a task: you need to do some additional processing before and after calling a function in a dynamic library.

Such requirements are generally referred to as pile insertion, that is, for a specified objective function, we create a new wrapper function to complete some additional functions.

Call the real object function in the wrapper function, but you can do something extra before or after the call.

For example, count the number of function calls, verify whether the input parameters of the function are legal, and so on.

For the official definition of program pile insertion, please refer to the description in [Baidu Encyclopedia]:

  1. Program pile insertion was first proposed by Professor J.C. Huang.

  2. On the basis of ensuring the original logical integrity of the tested program, it inserts some probes into the program (also known as "detector", which is essentially the code segment for information collection, which can be assignment statements or function calls for collecting covering information).

  3. Through the execution of the probe and throwing out the characteristic data of program operation, through the analysis of these data, the control flow and data flow information of the program can be obtained, and then the dynamic information such as logic coverage can be obtained, so as to realize the purpose of testing.

  4. According to the time of probe insertion, it can be divided into target code plug-in and source code plug-in.

In this article, we will discuss together: what methods can be used to realize the pile insertion function in the C language development under Linux environment.

Example code analysis of pile insertion

The sample code is simple:

├── app.c
└── lib
    ├── rd3.h
    └── librd3.so

Suppose the dynamic library librd3 So is provided by a third party with a function: int rd3_func(int, int);.

// lib/rd3.h

#ifndef _RD3_H_
#define _RD3_H_
extern int rd3_func(int, int);
#endif

In the application app In C, the function in the dynamic library is invoked.

app.c codes are as follows:

#include <stdio.h>
#include <stdlib.h>
#include "rd3.h"

int main(int argc, char *argv[])
{
    int result = rd3_func(1, 1);
    printf("result = %d \n", result);
    return 0;
}

compile:

$ gcc -o app app.c -I./lib -L./lib -lrd3 -Wl,--rpath=./lib
  1. -50. / lib: specifies to search for library files in the Lib directory during compilation.

  2. -Wl,--rpath=./lib: specifies to search for library files in the lib directory during execution.

Generate executable program: app, execute:

$ ./app
result = 3

The sample code is simple enough to be called a brother version of helloworld!

Pile insertion during compilation

The basic requirement for inserting a function is that the original file (app.c) should not be additionally modified.

Due to app In the C file, you have included "RD3. H" and called rd3_func(int, int) function.

So we need to create a fake "rd3.h" for app c. And you want to put the function rd3_func(int, int) "redirect" to a wrapper function, and then call the real objective function in the wrapper function, as shown in the following figure:

Redirection function: can be implemented using macros.

Wrap function: create a new C file. In this file, you need #include "lib/rd3.h" and then call the real target file.

The complete file structure is as follows:

├── app.c
├── lib
│   ├── librd3.so
│   └── rd3.h
├── rd3.h
└── rd3_wrap.c

The last two files are new: RD3 h, rd3_ wrap. c. Their contents are as follows:

// rd3.h

#ifndef _LIB_WRAP_H_
#define _LIB_WRAP_H_

// Function "redirection", in this case app C to call wrap_rd3_func
#define rd3_func(a, b)   wrap_rd3_func(a, b)

// Function declaration
extern int wrap_rd3_func(int, int);

#endif
// rd3_wrap.c

#include <stdio.h>
#include <stdlib.h>

// Real objective function
#include "lib/rd3.h"

// Wrapper function, used by app C call
int wrap_rd3_func(int a, int b)
{
    // Do some processing before calling the object function
    printf("before call rd3_func. do something... \n");
    
    // Call object function
    int c = rd3_func(a, b);
    
    // After calling the object function, do some processing
    printf("after call rd3_func. do something... \n");
    
    return c;
}

Let app C and rd3_wrap.c compile together:

$ gcc -I./ -L./lib -Wl,--rpath=./lib -o app app.c rd3_wrap.c -lrd3

The search path of header file cannot be wrong: you must search rd3.0 in the current directory h. In that case, app The #include "rd3.h" found in C is the header file rd3.h we added h.

Therefore, in the compilation instruction, the first option is - I. / which means to search for header files in the current directory.

In addition, because in rd3_wrap.c file, use #include "lib/rd3.h" to include the header file in the library. Therefore, in the compilation instruction, you do not need to specify the Lib directory to find the header file.

The executable app is compiled and executed as follows:

$ ./app 
before call rd3_func. do something... 
after call rd3_func. do something... 
result = 3 

Perfect!

Link stage pile insertion

The linker function in Linux system is very powerful. It provides an option: - wrap f, which can be inserted in the link phase.

The function of this option is to tell the linker to resolve to f when it encounters the f symbol__ wrap_f. Encountered in__ real_ When the f symbol is parsed into F, it happens to be a pair!

We can use this property to create a new file rd3_wrap.c. And define a function__ wrap_rd3_func(int, int) is called in this function__ real_rd3_func function.

Just add - WL, - wrap, RD3 in the compilation options_ Func, the compiler will:

  1. Put the app RD3 in C_ Func symbol, parsed into__ wrap_rd3_func to call the wrapper function;

  2. Put RD3_ wrap. In C__ real_rd3_func symbol, parsed into rd3_func to call the real function.

The conversion of these symbols is automatically completed by the linker!

According to this idea, let's test it together.

The file directory structure is as follows:

.
├── app.c
├── lib
│   ├── librd3.so
│   └── rd3.h
├── rd3_wrap.c
└── rd3_wrap.h

rd3_wrap.h is app C quoted as follows:

#ifndef _RD3_WRAP_H_
#define _RD3_WRAP_H_
extern int __wrap_rd3_func(int, int);
#endif

rd3_ wrap. The contents of C are as follows:

#include <stdio.h>
#include <stdlib.h>

#include "rd3_wrap.h"

// You can't drink lib / RD3 directly here H, and the linker should complete the parsing.
extern int __real_rd3_func(int, int);

// Packing function
int __wrap_rd3_func(int a, int b)
{
    // Do some processing before calling the object function
    printf("before call rd3_func. do something... \n");
    
    // When the target function is called, the linker resolves to rd3_func. 
    int c = __real_rd3_func(a, b);
    
    // After calling the object function, do some processing
    printf("after call rd3_func. do something... \n");
    
    return c;
}

rd3_ wrap. In C, you cannot directly include "rd3.h" because lib / RD3 The function declaration in H is int rd3_func(int, int);, No__ real prefix.

Compile:

$ gcc -I./lib -L./lib -Wl,--rpath=./lib -Wl,--wrap,rd3_func -o app app.c rd3_wrap.c -lrd3

Note: the header file search path here is still set to - I./lib because app The header file is include d in C.

Get the executable app and execute:

$ ./app
before call rd3_func. do something... 
before call rd3_func. do something... 
result = 3

Perfect!

Pile insertion in execution stage

Insert piles in the compilation phase and create a new file rd3_wrap.c is the same as app C, where the wrapper function name is wrap_rd3_func.

app.c realizes the "redirection" of functions through a macro definition: RD3_ func --> wrap_ rd3_ func.

We can also directly "overlord hard bow": in the new file RD3_ wrap. In C, RD3 is defined directly_ Func function.

Then in this function, dlopen, dlsym series functions are used to dynamically open the real dynamic library, find the target files, and then call the real target function.

Of course, in this case, you are compiling the app C, you cannot connect to Lib / librd3 So file.

Continue to practice according to this idea!

The file directory structure is as follows:

├── app.c
├── lib
│   ├── librd3.so
│   └── rd3.h
└── rd3_wrap.c

rd3_ wrap. The contents of the C file are as follows (some error checks are temporarily ignored):

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

// Header file for Library
#include "rd3.h"

// Function type consistent with target function signature
typedef int (*pFunc)(int, int);

int rd3_func(int a, int b)
{
    printf("before call rd3_func. do something... \n");
    
    //Open dynamic link library
    void *handle = dlopen("./lib/librd3.so", RTLD_NOW);
    
    // Find the objective function in the library
    pFunc pf = dlsym(handle, "rd3_func");
    
    // Call object function
    int c = pf(a, b);
    
    // Close dynamic library handle
    dlclose(handle);
    
    printf("after call rd3_func. do something... \n");
    return c;
}

Compile wrapped dynamic library:

$ gcc -shared -fPIC -I./lib -o librd3_wrap.so rd3_wrap.c

Get the wrapped dynamic library: librd3_wrap.so.

To compile an executable program, you need to link the wrapper library librd3_wrap.so:

$ gcc -I./lib -L./ -o app app.c -lrd3_wrap -ldl

Get the executable app and execute:

$ ./app 
before call rd3_func. do something... 
after call rd3_func. do something... 
result = 3

Perfect!


------ End ------

The test code in this article has been placed on the network disk.

In the official account [IOT Internet of things] background reply keyword: 220109, you can get download address.

It's not easy to be original. Please support Daoge and share the article with more embedded partners. Thank you!

Recommended reading

[1] Linux ab initio series

[2] C language pointer - from the underlying principle to fancy skills, use graphics and code to help you explain thoroughly

[3] The underlying debugging principle of gdb is so simple

[4] Is inline assembly terrible? Finish reading this article and end it!

Other albums: Selected articles,Application design,Internet of things, C language.

The official account of star standard is the first time to read articles.

Keywords: Linux Design Pattern

Added by dlkinsey85 on Wed, 12 Jan 2022 22:11:29 +0200