Principle and implementation of dynamic replacement of Linux core functions

Source: Internet
Author: User
Tags diff goto int size prepare

Reprint: https://www.ibm.com/developerworks/cn/linux/l-knldebug/

Principle and implementation of dynamic replacement of Linux core functions

When debugging a Linux core module, it is sometimes necessary to get real-time access to the execution state of some functions on one of the internal paths, such as seeing if the incoming variable is the desired value to determine if the entire execution process is still normal. Due to the dynamic nature of the system runtime, it is impossible to know beforehand where the execution path might be problematic, so that it can only add a lot of unnecessary information query points to the whole path, which will cause the useful state information to be drowned. And this way of increasing the output of information (typically printed in the core via the PRINTK statement) requires recompiling the kernel and rebooting, resulting in wasted time. At this point, we need a method that can easily intercept the suspicious point on the execution path in real time, this paper describes a basic implementation principle of dynamic replacement of the kernel function of Linux.

1. Purpose

In the process of debugging the core module, it is found that the kernel-provided function shows inconsistent state with the expected performance during the execution of a period of time, which may be caused by the exception of the parameters passed in when the core module calls the function, or it may be the influence of the Linux kernel by the Insert module. resulting in inconsistencies in their internal state. There is a need for a mechanism to track the execution flow of the questioned function. However, because the current core is running, the usual widely used methods such as adding print statements in the target function need to recompile and start the kernel, which will destroy the rare scene, so it is not suitable for such occasions, only the mechanism that can dynamically replace the dynamically running kernel functions can play a real role.

2. Basic Principles

The Linux operating system requires two of the most basic prerequisites when executing a program (the kernel can also be considered a running large program): (1) The stack of stored parameters, return addresses, and local variables (stacks), and (2) executable binary code. Before invoking a function, you need to prepare the passed parameters for the function in the stack, the return address after the function executes, and then set the processor's program counter (EIP, which points to the next instruction that the processor is about to execute) as the address of the first code execution of the called function. This allows the next processor cycle to jump to the called function execution. As shown in the scenario where the Execute function func (parameter1, Parameter2, ... parametern) is invoked, the function executable code in kernel space has an address of func_addr:

The purpose of dynamically replacing kernel culvert or the desired effect is to change the original execution flow of the kernel and jump to our own custom function flow. As you can see from the schematic diagram of the function call above, there are three places that can be used as a starting point for function substitution:

(1) Modifying the stack
However, this method can only modify the function execution parameters and return address, not to change the execution process of the purpose;

(2) Modify the contents of the program counter
The EIP cannot be assigned directly within the operating system, and no such instruction code is provided;

(3) Modify the original function code
When a function is called to execute, the EIP will point to the starting address of the called function code, and the next value of the EIP will be determined based on the first instruction of the function. So we can modify the header of the original function code to keep the existing stack content intact, so that it jumps the contents of the EIP to the alternative function code we provide.

There are two instructions in the instruction set that can jump the program execution process: Call and JMP.

Call is a function invocation instruction, which is known by the preceding discussion that before call executes, it is necessary to set the parameters necessary for the function to execute in the stack, where parameters have been set before entering the original function, so we must copy the parameters to the top of the stack. This copy process involves the stack address associated with the number of parameters, so the different functions need to be recalculated, more error-prone.

JMP is a straightforward jump (like a goto statement in C), and can continue to use the original function to prepare parameters and return address information, without having to re-copy the contents of the stack, it is relatively safe and more convenient to implement.

is a scene of dynamic function substitution. Replace_func is the replacement function of the Func function with the address new_address.

The entire replacement process is done by a core module. The core module, when initialized, replaces the instruction code of the original function's Func starting part with a jump script, making this part of the code an instruction to go to the function Replace_func. At the same time, in order to finally be able to restore the original function func, the original function must be replaced part of the instruction code to save, so that after we achieve the intended purpose to unload the module, the saved script can be re-overwritten back to the original address, so that when the subsequent kernel to execute the function func again, It is also possible to continue executing the original execution code of the function without destroying the state of the kernel.

3. Example of function substitution

Here, for i386 32-bit platforms, the 2.4.18 Linux environment is used to dynamically replace kernel functions such as vmtruncate, Fget, and so on, using the mechanism described above.

3.1. Pre-conditions

There are two prerequisites to be aware of when using this method:

(1) The moment the original function is being replaced, that is, when inserting the replacement core module, is not used by other processes, otherwise the result may cause the kernel state inconsistency.

(2) The replacement function and the original function have the same parameter list, and the corresponding order has the same parameter type, the parameter number is the same, and the function has the same return value. In general, the purpose of replacing the core function is not to change its function, but to track whether the function's execution flow has an exception, whether the variables and parameters have the expected values, so the replacement function and the original function have the same functionality.

3.2. Replacement process

The implementation of the entire replacement process is divided into the following steps:

(1) Replacement instruction code:
B8 xx/*movl, $eax; this will be replaced by the address of the specific replacement function */
FF E0/*jmp * $EAX; jump function */
Store the above 7 scripts in a single character array:
REPLACE_CODE[7]

(2) Overwrite the next 8 0 in the first instruction with the address of the replacement function and retain the original instruction code:
memcpy (Orig_code, Func, 7); /* Keep the original function's instruction code */
* ((long*) &replace_code[1]) = (long) Replace_func; /* Assign the address of the replacement function */
memcpy (func, Replace_code, 7);/* Replace the original function instruction code with the new script */

(3) The recovery process overwrites the original function code with a reserved instruction code:
memcpy (func, Orig_code, 7)

3.3. Replace the Vmtruncate function

The following is a detailed kernel module implementation code that replaces the kernel function vmtruncate:

#ifndef __kernel__#define __kernel__#endif#ifndef module#define module#endif#include <linux/kernel.h> #include <linux/config.h> #include <linux/module.h> #include <asm/string.h> #include <asm/unistd.h># Include <linux/fs.h> #include <linux/sched.h> #include <linux/mm.h> #include <linux/pagemap.h > #include <asm/smplock.h>int (*orig_vmtruncate) (struct inode * inode, loff_t offset) = (int (*) (struct inode *ino DE, loff_t offset)) 0xc0125d70;  /* The address of the original Vmtruncate function 0xc0125d70 can be found in the System.map file */#define CODESIZE 7/* Replacement code length */static char orig_code[7];  /* Save the execution code of the original Vmtruncate function to be covered part */static char code[7] = "\xb8\x00\x00\x00\x00" "\xff\xe0"; /* Replacement Code *//* If the function is not export, you need to implement it yourself, for Vmtruncate call */static void _vmtruncate_list (struct vm_area_struct *mpnt, unsigned Long Pgoff) {do {struct mm_struct *mm = mpnt->vm_mm;unsigned long start = mpnt->vm_start;unsigned long end = Mpnt-&gt ; vm_end;unsigned Long len = end-start;unsigned long diff;iF (Mpnt->vm_pgoff >= Pgoff) {zap_page_range (mm, start, Len); continue;} Len = len >> Page_shift;diff = pgoff-mpnt->vm_pgoff;if (diff >= len) continue;start + = diff << Page_shi Ft;len = (Len-diff) << page_shift;zap_page_range (mm, start, Len);} while ((mpnt = mpnt->vm_next_share) = NULL);} /* Vmtruncate replacement function */int _vmtruncate (struct inode * inode, loff_t offset) {unsigned long pgoff;struct address_space *mappin    g = inode->i_mapping;unsigned long limit; /* In this function we have added a lot of information to determine the parameters of the printing */printk (kern_alert "Enter into my vmtruncate, pid:%d\n", current->pid);p rint K (Kern_alert "Inode->i_ino:%d, inode->i_size:%d, pid:%d\n", Inode->i_ino, INODE-&G T;i_size, Current->pid);p rintk (kern_alert "offset:%ld, PID:%d\n", offset, current->pid);p rintk (kern_alert "Do n othing, PID:%d\n ", current->pid); return 0;if (Inode->i_size < offset) goto do_expand;inode->i_size = offset; Spin_lock (&mapping->i_shared_lock); if (!mapping->i_mmap &&!mapping->i_mmap_shared) goto Out_unlock;pgoff = (offset + page_cache_size-1) >> page_cache_shift;printk (kern_alert "Begin to truncate mmap list, PID:%d\n", Current->pid ); if (mapping->i_mmap! = null) _vmtruncate_list (Mapping->i_mmap, Pgoff); if (mapping->i_mmap_shared! = null) _ Vmtruncate_list (mapping->i_mmap_shared, Pgoff); OUT_UNLOCK:PRINTK (Kern_alert "before to truncate inode pages, pid:% D\n ", current->pid); Spin_unlock (&mapping->i_shared_lock); truncate_inode_pages (mapping, offset); Goto out _truncate;do_expand:limit = current->rlim[rlimit_fsize].rlim_cur;if (limit! = rlim_infinity && offset > Limit) goto out_sig;if (offset > inode->i_sb->s_maxbytes) goto out;inode->i_size = offset;out_truncate: PRINTK (Kern_alert "Come to Out_truncate, PID:%d\n", current->pid); if (Inode->i_op && inode->i_op-> Truncate) {Lock_kernel (); inode->i_op->truncate (inode); unlock_Kernel ();} PRINTK (Kern_alert "Leave, pid:%d\n", current->pid); return 0;out_sig:send_sig (Sigxfsz, current, 0); Out:return- Efbig;} /* Memory copy function in core, used to copy replacement code */void* _memcpy (void *dest, const void *SRC, int size) {const char *p = Src;char *q = Dest;int I;fo R (i=0; i<size; i++) *q++ = *p++;return dest;} int Init_module (void) {* (long *) &code[1] = (long) _vmtruncate;/* Assign replacement function address */_memcpy (Orig_code, Orig_vmtruncate, CODES IZE); _memcpy (orig_vmtruncate, Code, codesize); return 0;} void Cleanup_module (void) {/* When uninstalling the core module, restore the original Vmtruncate function */_memcpy (orig_vmtruncate, Orig_code, codesize);}
3.4. Replace the Fget function

Here is the implementation code that replaces the Fget function:

#ifndef __kernel__#define __kernel__#endif#ifndef module#define module#endif#include <linux/kernel.h> #include <linux/config.h> #include <linux/module.h> #include <asm/string.h> #include <asm/unistd.h># Include <linux/fs.h> #include <linux/sched.h> #include <asm/smplock.h>struct file * (*orig_fget) ( unsigned int fd) = (struct file * (*) (unsigned int)) 0xc0138800; /* Address of the original Fget function */#define CODESIZE 7static char orig_fget_code[7];static char fget_code[7] = "\xb8\x00\x00\x00\x0  0 "\xff\xe0"; void* _memcpy (void *dest, const void *SRC, int size) {const char *p = SRC;  char *q = dest;  int i;  for (i=0; i<size; i++) *q++ = *p++; return dest;}  /* If the function is not export, you need to implement */static inline struct file * _fcheck (unsigned int fd) {struct file * file = NULL;    struct Files_struct *files = current->files;  if (FD < Files->max_fds) file = files->fd[fd]; return file;} /* Replace Fget's function */struct file* _fget (unsigned intFD) {struct file * file;    struct Files_struct *files = current->files;   Read_lock (&files->file_lock);  File = _fcheck (FD);    if (file) {struct Dentry *dentry = file, f_dentry;    struct Inode *inode;      if (dentry && dentry->d_inode) {inode = Dentry-D_inode; if (Inode->i_ino = = 298553) {/* Here, we print out information about the variables we care about, for the query */PRINTK ("Enter into my fget for file:      Name:%s, Ino:%d\n ", Dentry->d_name.name, Inode->i_ino);  }} get_file (file);  } read_unlock (&files->file_lock); return file;}  int Init_module (void) {Lock_kernel ();  * (long *) &fget_code[1] = (long) _fget;  _memcpy (Orig_fget_code, Orig_fget, codesize);  _memcpy (Orig_fget, Fget_code, codesize);  Unlock_kernel (); return 0;} void Cleanup_module (void) {/* unload module, restore original function */_memcpy (Orig_fget, Orig_fget_code, codesize);}
4. Limitations of the method

You need to customize your replacement function before replacing it, and you must be able to find the address of the replaced function in that run core (via System.map or/proc/ksyms). In addition, before the replacement of the function on the target computer, it is better to first test on the other nodes with the same hardware platform and operating system core, because the substitution function written by oneself often has some problems and cannot pass at once, so as not to cause unnecessary trouble.

Principle and implementation of dynamic replacement of Linux core functions

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.