Generalised program hooks6769117Abstract A hook interface module cooperates with a kernel whose functionality is being modified and with kernel modules providing modified functionality. The kernel includes symbols indicating execution points where modification is permitted. The hook interface module resolves an address for each symbol and maintains a list of any modification functions associated with each symbol. An API exposes a registration method for enabling the kernel modules to register a modification function for a symbol and add an indicator for the modification function to the list for the symbol. An arming method enables the kernel modules to arm modification functions associated with the symbols. This modifies the address contents for each symbol to cause program execution at the symbol address to jump to a location in the hook interface module. A dispatcher responds to this and causes execution to jump to any modification function associated with a symbol from which execution has jumped. Claims What is claimed is: Description BACKGROUND OF THE INVENTION
asm volatile (" .global GKHook001;
.global GKHook001_ret;
.global GKHook001_bp;
GKHook001: jmp GKHook001_bp;
/* replace with nop;nop;nop; to
activate */
leal %1, %%eax
push %%eax; /* push address of
parm 1 */
leal %0, %%eax
push %%eax; /* push address of
parm 0 */
push $2; /* push number of
parameters */
Nop;nop;nop;nop;nop;
/* replace with jmp GKHook001_dsp when
active * /
GKHook001_ret: add $12, %%esp; /* clean up
stack */
GKHook001_bp: ;
": : "m" (parm0), "m" (parm1) : "%eax")
Furthermore this construct can be reduced to a single line by defining it as a macro thus:
#define GKHOOK_2VAR_RO(h, p0, p1) asm volatile (".global
GKHook"h";
.global GKHook"h"_ ret;
.global GKHook"h"_bp;
GKHook"h": jmp GKHook"h"_bp;
/* replace with nop;nop;nop; to
activate */
leal %1, %%eax
push %%eax;
leal %0, %%eax;
push %%eax;
push $2;
Nop;nop;nop;nop;nop;
/* replace with jmp GKHook"h"_ dsp when
active* /
GKHook"h"_ ret: add $12, %%esp;
GKHook"h"_ bp: ; " : : "m" (p0), "m" (p1) : "%eax")
The hook would be encoded in kernel source as: GKHOOK.sub.-- 2VAR_RO(001,parm0,parm1); Hook locations are designated a unique numeric identifier. In the example above the identifier is 001. For convenience each identifier is assigned a symbolic synonym. For example if hook 001 were located at the entry point to the trap3 function in the kernel then the hook identifier could be identified as: #define GKH_trap3_entry 0001 and the hook encoded as: GKHOOK.sub.-- 2VAR_RO(GKH_trap3_entry,parm0,parm1); The example given above assume two parameters are passed for read-only purposes, hence the RO suffix to the macro name. A minor modification to the macro would allow the parameters be used in a read/write manner by the hook exit, however, this may cause the GCC compiler to generate additional code for preserving and updating temporary copies variables that might have been changed by the exit. So as not to impose this overhead unnecessarily a read/write version of each hook macro is defined. The read/write version of the example above would be defined as follows:
#define GKHOOK_2VAR_RW(h, p0, p1) asm volatile (".global
GKHook"h";
.global GKHook"h"_ret;
.global GKHook"h"_ bp;
GKHook"h": jmp GKHook"h"_bp;
/* replace with nop;nop;nop; to
activate */
leal %1, %%eax
push %%eax;
leal %0, %%eax;
push %%eax;
push $2;
Nop;nop;nop;nop;nop;
/* replace with jmp GKHook"h"_ dsp when
active*/
GKHook"h"_ ret: add $12, %%esp;
GKHook"h"_ bp: ; " : "=m" (p0), "=m" (p1) : "0" (p0),
"1" (p1) : "%eax")
A hook may be in an active (default) or inactive state, and these are defined as follows: An inactive hook is one in which the only instruction executed is the jump instruction at the beginning of the hook, which bypasses the rest of the hook. An active hook is one where the bypassing jump instruction has been replaced by no-op instructions (3 NOPs) and the 5 NOPs that precede the return label have been populated with a jump instruction with a target address of the hook dispatcher routine 18 for this hook within the hook interface module. The hook interface module 16 uses address information saved during its initialisation by calling, for example, the get_kernel_syms interface to locate the instructions that must be altered on activation (and subsequent deactivation). A hook is made active when at least one exit registered for this hook becomes armed. Using the example above, when active, the assembler instructions forming the hook are equivalent to:
asm volatile (" .global GKHook001;
.global GKHook001_ret;
.global GKHook001_bp;
GKHook001: nop;nop;nop;
leal %1, %%eax
push %%eax; /* push address of
parm 1 */
leal %0, %%eax
push %%eax; /* push address of
parm 0 */
push $2; /* push number of
parameters */
jmp GKHook001_ dsp;
GKHook001_ ret: add $12, %%esp; /* clean up
stack */
GKHook001_bp: ;
": : "m" (parm0), "m" (parm1) : "%eax")
A hook is made deactive when no more hook exits are registered for that hook. In the preferred embodiment, the hook interface module is loaded using the Linux insmod utility. The hook interface module includes an init_module routine which performs the following sequence of operations: 1. extract kernel public symbols using the get kernel syms function. This provides a cross-reference table of global kernel symbol names to their memory addresses. 2. for each kernel hook, locate the addresses of each of the three global symbols and save these, per hook in an internal table status table 20. The entry symbol (GKHook001 in the example above) is used to locate the jump instruction that is change to three NOP instructions on hook activation. The return symbol (GKHook001_ret in the example above) has 5 subtracted to locate the sequence of 5 NOP instructions that are populated with a jump to the hook dispatcher routine 18 when the hook becomes active. The dispatcher routine also needs to return to the hook once the modification function--the hook exit--completes and so the return symbol within the hook is also used to populate the target address of a jump instruction in the dispatcher routine 18 for that hook. This initialisation stage allows hook exits to be loaded, registered, armed and disarmed dynamically. It should be noted that, in the preferred embodiment, a hard-coded external reference from the kernel to another module would not be possible, since that module would have to be brought into memory before the kernel, but the kernel would be required to be in memory to load the module. This conflict therefore debars the modularization of the kernel in the conventional way in which module functionality is normally allowed to be split over multiple executable load modules. The hook interface module 16, in turn, provides an API comprising four methods for kernel modules 12, 14 to call. These are: GKHregister--This is used to register an entry-point in a kernel module that will become a hook exit. When this method is called, the hook interface module is passed: the hook number and a hook registration record (HRR), FIG. 2. (The hook registration record is required to be allocated from persistent system memory, so that it can be later referenced by both the hook interface module and the kernel module during hook dispatching, de-registration, arming and disarming.) The hook registration record is of the following structure:
typedef struct {
void * hkExit;
void * hkNext;
void * hkPrev;
unsigned int hkFlag;
void * hkLink;
void * hkHead;
unsigned int hkIndex;
} gkhook_t;
Each hook has an associated ordered double-linked list (represented by a loop 22), and registration amounts to inserting the hook registration record into the double-linked list of registered exits for the hook in question. Thus, each HRR includes a hkPrev and hkNext pointer to the next lower/higher priority exit on the list of registered exits by which the list may be traversed. These pointers are populated by the hook interface module when inserting the HRR in the list 22. HkHead is a pointer to the head of the double-linked list 22 in which the HRR lies. This pointer is also set by the hook interface module on registration and is used as a validity check when the HRR is subsequently used in an Arm, Disarm or De-register operation. The registration record also contains an integer (hkindex) through which the registration method returns an index indicating the dispatching order and so priority of the hook exit--that is the number of HRRs lower than the hkHead, a given HRR lies in the list 22 (The HRR with hkindex=1 will be pointed to by the hkPrev pointer of the hkHead HRR.) The hook dispatcher relies on position on the linked list to determine dispatching order and so the higher the number the lower the priority. In the preferred embodiment, hkindex is used by the hook interface module as a value to return at registration time. The only significant value is 1, which indicates that the hook exit was the first to register for a given hook. If, on the other hand, hkindex is set by the kernel module to, say 1 if it wishes its hook exit to be highest priority or another higher number for a specified lower priority, when passing the HRR to the hook interface module, then the hook interface module, will attempt to place the HRR for this hook exit at the highest available At place in the list 22 for the hook. Registration succeeds if no more than the specified number of other hook exits have requested that their index be fixed at a higher priority (i.e . . . a lower number). If so, registration for the hook exit will fail, and it is up to the kernel module to decide if it wishes to reregister the hook exit with a lower priority. If not, registration succeeds. Furthermore, the kernel module could set hkindex with a predefined number, for example 0.times.FFFFFF, to indicate that it wishes the HRR to be placed last in the list 22, that is the HRR should be pointed to by hkNext pointer of the hkHead HRR. The hook registration record further contains the address of the hook exit entry point (hkExit) and a flag (hkFlag). As illustrated in the example of FIG. 2, the hook exit need not be a location within the registering kernel module 12, 14 and can in fact reside anywhere, as in the case of Hook Exit #2. The flag is used by both the kernel module and the hook interface module, with: 1. the hook interface module setting a state portion of the flag to indicate whether a hook exit is armed, disarmed, registered or de-registered; 2. the kernel module setting a second portion of the flag to indicate whether this exit is to be the only exit registered for a given hook; and 3. The kernel module setting a third portion of the flag to indicate if any serialisation (locking), such as disabling interrupt or spinning other CPUs, needs to be performed before the hook exit is called. This second portion of the flag can in fact be subdivided into a first sub-portion to allow the kernel module to indicate it wishes its hook exit to be the first dispatched hook exit, and a second sub-portion to allow the kernel module to indicate it wishes its hook exit to be the last hook exit. If both are set then the hook exit will be the only allowed hook exit. This allows a dispatch priority insensitive hook exit to register before a priority sensitive exit and for the latter to acquire its desired priority. Registration returns a failure/success indication. Failure can occur: 1. if the flag was set to indicate that the exit was to be the only exit and another exit was already registered for a hook; 2. if the hook is defined as supporting only one exit and one is already registered; 3. if the hook exit is already registered for the hook; 4. if the hook number is invalid. Once registered, a hook may be de-registered or armed. The hook exit will not be called until it is armed. GKHderegister--This is used to de-register one or more hook exits. De-registration involves removing knowledge of the hook exit by deleting the HRR from the linked list 22 corresponding to a hook to be de-registered and correcting the hkNext and hkPrev pointers for the previous and next hook registration records in the list 22, and adjusting the hkindex values for lower priority hook registration records in the list 22 GKHderegister is in fact passed a linked list of one or more hook registration records. If any hook in the list is armed (see GKHarm below) then it is disarmed before being de-registered. A failure/success indication is returned to the caller. Failure can occur if a hook exit was not previously registered. Note: it is the responsibility of the kernel module 12, 14 registering hook exits to de-register them explicitly when, for example, its cleanup_module routine is called during module termination. GKHarm--Make a set of hook exits dispatchable. Arming a hook exit comprises marking the exit eligible for being called (or dispatched). Each registered exit has a status portion of the hkFlag, indicating whether or not the hook exit may be called. When the first hook exit is armed for a particular hook, the hook is made active, by replacing the bypassing jump instruction and the nop instructions for the hook as explained above. In order to support group arming, disarming and de-registration, in the preferred embodiment, the registration record also contains a link pointer (hkLink). When a kernel module has registered more than one hook exit for associated hooks, before arming the hooks described above, its goes to the corresponding HRR in the hook's list 22, and sets the hkLink pointer to the HRR of the next hook. In the case of FIG. 2, the kernel module has registered hook exits for hook#2 and hook#z. Before arming the hooks, the kernel module goes to HRR#2 of the double-linked list 22 for hook#2 and sets its hkLink pointer to point to HRR#1 of the double-linked list 22 for hook#z. The hkLink pointer for HRR#1 is set to null as this lies at the end of the list of hook exits to be armed. GKHarm is passed a pointer to the first HRR in a linked list of HRRs. It first checks if it is possible to arm all HRRs in the list and if so attempts to do so. If the check fails, then GKHarm returns a fail without arming any of the HRRs. GKHdisarm--Make a set of hook exits non-dispatchable. Disarming comprises setting the hook exit status accordingly and if no exits are left armed for a given hook then the hook is made deactive by inserting the jump instruction at the start of the hook and replacing the call to the hook dispatcher with nop's. The disarm function is also passed linked list of registration records that have previously been registered. Disarming an already disarmed exit is treated as a null operation. Failure indication is only given if the hook exit has not been registered. In this case, it will be seen that the overhead of ensuring that the correct registration records are chosen for disarming lies with the kernel module which builds the list supplied to GKHdisarm. This gives flexibility in selecting which HRRs to disarm. On the other hand, GKHdisarm could instead receive a pointer to the first HRR in a list linked by hkLink pointers, and simply traverse this list disarming these HRR's. Whenever the hook interface module modifies code it takes the following precautions to ensure predictable results while the modification is being made: 1) In a multiprocessor environment an inter-processor communication mechanism, such as the inter-processor interrupt (IPI) defined by Linux, is used to signal to each of the other processors to spin with interrupts disabled until signalled to resume normal operation. Under Linux the IPI signals each target processor to call a specified function via the smp_call_function service. The specified function (GKHspin) is implemented in the hook interface module and does the following: Disables interrupts. Set a variable known as GHKspinlock to a non-zero value. Continuously tests the variable until it is zero. When zero, enables interrupts and returns to the system. 2) Disables interrupts to avoid recursion on the same processor. 3) After any code modifications are made, an instruction, for example Store CPUID on Intel, is used to cause the processor to flush any pre-fetched instructions. 4) In a multiprocessor environment cause all spinning processors to resume normal operation but using either an inter-processor communications mechanism supplied by the operating system, or directly by changing a value in memory on which each of the spinning processes is continually testing. The latter method is employed in the Linux implementation. Once one or more hook exits are armed they will be called by a dispatching routine 18 within the hook interface module 16 whenever the kernel code containing the corresponding hook is executed. Beginning at the HRR pointed to by the hkPrev pointer of the HRR pointed to by hkHead, the dispatcher traverses the linked list 22 of registered hook exits for its particular hook. Only hook exits in the armed state are used. For each armed hook exit the dispatcher will call the exit's entry point (hkExit) having first saved the address of the current registration record as a parameter on the stack. This provides an easy way for the dispatcher to restore its state on return from the hook exit. It also allows the hook exit to change its status by updating the flag (hkFlag) in the registration record. This provides a dispatch-once facility but also allows locking conditions to be altered without de-registering or disarming the hook exit. When the hook exit returns to the dispatcher, the return code (EAX register in the case of Linux on Intel platforms) is examined. If, non-zero then no further exits in the linked list are dispatched on this occasion and the dispatcher returns immediately to the kernel. For performance reasons, the hook interface module internally registers per hook a default exit which is always called last, i.e. its HRR will be pointed to by hkHead. This exit sets the return code to 1 thus causing the dispatcher to return to the kernel. The performance saving occurs when more than one exit is registered for a hook by avoiding a test for last exit, i.e. that is checking for hkHead, each time the previous exit returns to the dispatcher. It also provides performance savings when registering and de-registering exits, since the boundary condition when no-exits are registered never occurs. Also, to cater for the case where an exit might update the exit status by changing the status portion of hkFlag in the registration record directly to disarm the HRR, the hook interface module can be set to call GKHdisarm later passing the address of the HRR which disarmed itself. The disarm for this HRR will be null, but causes GKHdisarm to check if all exits are disarmed and if so, the hook interface module deactivates the hook. In a refinement of the preferred embodiment, kernel modules can identify additional hooks after the hook interface module has initialised, and remove a hook completely from the hook interface module. In this case, the HRR pointed to by hkHead is a special head registration record. As in the preferred embodiment, the hook exit for this HRR is the default hook exit which causes the HIM to return to the kernel. The head registration record is an extension of the regular HRR and contains additional fields that identify the hook location and chain the hook head registration records together. Furthermore, the status flag stores an additional value to show whether the hook has been activated or not. The structure of the hook head registration record is:
typedef struct {
void * hkExit;
void * hkNext;
void * hkPrev;
unsigned int hkFlag;
void * hkLink;
void * hkHead;
unsigned int hkIndex;
void * hkHeadNext;
void * hkpHook;
void * hkpHook_ret;
void * hkpHook_pb;
unsigned int hkHookId;
} gkhookhead_t;
The additional fields are defined as follows:
hkHeadNext
Pointer to the next head record.
hkpHook
Pointer to the hook location.
hkpHook_ret
Pointer to the hook return point.
hkpHook_bp
Pointer to the hook bypass point.
hkHookId
The hook identifier. (This could alternatively be a string, although strings are more complicated to manipulate.) Each of these fields is populated by the hook interface module during initialisation of the standard set of hooks. The hook interface module then offers an additional interface to allow kernel modules to identify additional hook locations after the hook interface module has initialised. GKHIdentify--Used to identify a hook in a kernel module. Identify is passed the address of the hook identifier and uses the kernel symbols extracted with get_kernel_syms to locate the new hook, build a default hook exit, head hook registration record and hook dispatcher routine. An error is returned if the hook is already identified or if the hook cannot be located. GKHDelete--Used to delete a hook from hook interface module's management. Delete is passed the hook identifier and a force flag. Deletion comprises removal of the head hook registration record. After deletion, no further registration for that hook is permitted. If the force flag is zero the hook will only be deleted if inactive. If non-zero a force-delete function is performed in which all registered and armed hooks are disarmed and de-registered. It will therefore be seen that the preferred embodiment of the invention provides a mechanism for: allowing kernel exits to be loaded dynamically; allowing multiple hook exits to coexist at a given functional location within the kernel; avoiding the rework of a kernel patch whenever the kernel is updated; avoiding the recompilation of the kernel whenever the modification is updated; allowing local variables in which the hook is placed to be read and updated by the exit; and allowing the exit to have certain serialisation conditions present on entry. Referring again to FIG. 1, modules which use of the invention include for example a dynamic probe module 12. This comprises a Probe Definition File (PDF) generator 50 which passes a PDF file to a command program 52. The command program 52, instructs a first portion of the probe module, the probe manager, to register and arm a list of hook exits derived from the PDF file. When at least one dynamic probe hook exit for a hook is armed, the dispatcher 18 within the hook interface module 16 redirects program execution to a dynamic probes event handler (DPEH) portion of the probe module 12--this includes the modification functionality described above. In this case the functionality simply extracts information for a dynamic trace formatter 54 to enable a user to assess and monitor the operation of the kernel 10. The dynamic probe module 12 and its associated modules 50, 52 and 54 provide comparable functionality to the Dynamic Trace functions available for the OS/2 operating system from IBM Corporation, yet they need not be recompiled or updated to take into account variations in the kernel being monitored. It is also possible that products from other companies such as SGI's Kernel Crash Dump for Linux 14 from Silicon Graphics Inc. could be adapted to operate both with the hook interface module and as illustrated in FIG. 1 complement the functionality of the dynamic probe module. For example, the location jumped to by the dispatcher within the DPEH could be a command of the form "exit to" causing program execution to in turn jump, step 56, to, for example, a predefined location within module 14.
|
Same subclass Same class Consider this |
||||||||||
