//------------------------------------------------------------------- // faultcpl.c // // This module modifies the IDT (Interrupt Descriptor Table) // so as to incorporate a 'front-end' for the Linux kernel's // page-fault exception-handler that will capture the values // of the code-segment selectors that are in effect whenever // page-faults occur. The least significant pair of bits in // any code-segment selector will encode the processor's CPL // (Current Privilege Level), and users may see these values // in hexadecimal format by using the 'cat' command to print // our '/proc/faultcpl' pseudo-file, as follows: // // $ cat /proc/faultcpl // // // to compile: $ mmake faultcpl.c // to install: $ /sbin/insmod faultcpl.ko // and remove: $ /sbin/rmmod faultcpl // // NOTE: This code was written and tested for kernel 2.6.35.9. // // programmer: ALLAN CRUSE // written on: 08 MAR 2012 //------------------------------------------------------------------- #include // for module_init(), module_exit() #include // for create_proc_read_entry() #define INT_ID 14 // this IDT-entry is for page-faults #define MAXNUM 255 // maximum number of saved selectors typedef struct { long lo, hi; } GATE_DESCRIPTOR; char modname[] = "faultcpl"; unsigned short oldidtr[5], newidtr[5]; GATE_DESCRIPTOR *oldidt, *newidt; unsigned long kpage; const long max = MAXNUM; unsigned long oldisr14; unsigned long pgfaults = 0; unsigned short selector[ MAXNUM ]; void load_IDTR( void *regimage ) { asm(" lidtq %0 " : : "m" (*(unsigned short*)regimage) ); } //-------- INTERRUPT SERVICE ROUTINE ------------ void isr_entry( void ); asm(" .text "); asm(" .type isr_entry, @function "); asm("isr_entry: "); asm(" push %rax "); asm(" push %rbx "); asm(" mov pgfaults, %rax "); asm(" cmp max, %rax "); asm(" jge bypass "); asm(" mov $1, %rbx "); asm(" xadd %rbx, pgfaults "); asm(" mov 32(%rsp), %rax "); asm(" mov %ax, selector(, %rbx, 2) "); asm("bypass: "); asm(" pop %rbx "); asm(" pop %rax "); asm(" jmpq *oldisr14 "); //------------------------------------------------- int my_proc_read( char *buf, char **start, off_t off, int count, int *eof, void *data ) { int counter = pgfaults; int i, k = 0, len = 0; // count the number of page-faults that occurred when CPL was zero for (i = 0; i < counter; i++) if ( (selector[i] & 3) == 0 ) ++k; // display the values of the captured code-segment selectors len += sprintf( buf+len, "\n " ); len += sprintf( buf+len, "Below are shown the code-segment selectors" ); len += sprintf( buf+len, " for the first %d page-faults:", counter ); len += sprintf( buf+len, "\n" ); for (i = 0; i < MAXNUM; i++) { if ( (i % 15) == 0 ) len += sprintf( buf+len, "\n " ); len += sprintf( buf+len, "%04X ", selector[i] ); } // report the number of these selectors having CPL=0 len += sprintf( buf+len, "\n\n " ); len += sprintf( buf+len, "Observe that %d of these page-faults", k ); len += sprintf( buf+len, " occurred while executing in kernel-mode" ); len += sprintf( buf+len, "\n\n" ); return len; } static int __init faultcpl_init( void ) { GATE_DESCRIPTOR oldgate, newgate; long long isrlocn; printk( "<1>\nInstalling \'%s\' module\n", modname ); // allocate kernel memory for a new Interrupt Descriptor Table kpage = get_zeroed_page( GFP_KERNEL ); if ( !kpage ) return -ENOMEM; // initialize our module's global variables asm(" sidtq oldidtr \n sidtq newidtr "); memcpy( newidtr+1, &kpage, sizeof( unsigned long ) ); oldidt = (GATE_DESCRIPTOR*)(*(unsigned long*)(oldidtr+1)); newidt = (GATE_DESCRIPTOR*)(*(unsigned long*)(newidtr+1)); memcpy( newidt, oldidt, 256 * sizeof( GATE_DESCRIPTOR ) ); // setup our Interrupt Gate isrlocn = (long long)isr_entry; oldgate = oldidt[ INT_ID ]; oldgate.lo &= 0x0000FFFFFFFF0000L; newgate.hi = (isrlocn >> 32); newgate.hi &= 0x00000000FFFFFFFFL; newgate.lo = isrlocn; newgate.lo &= 0x00000000FFFFFFFFL; newgate.lo |= (newgate.lo << 32); newgate.lo &= 0xFFFF00000000FFFFL; newgate.lo |= oldgate.lo; newgate.lo |= (0x6000L << 32); newidt[ INT_ID ] = newgate; // initialize our forwarding pointer oldgate = oldidt[ INT_ID ]; oldisr14 = (oldgate.hi << 32); oldisr14 |= (oldgate.lo >> 32) & 0xFFFF0000L; oldisr14 |= (oldgate.lo & 0x0000FFFFL); // create the pseudo-file create_proc_read_entry( modname, 0, NULL, my_proc_read, NULL ); // activate our new Interrupt Descriptor Table load_IDTR( newidtr ); smp_call_function( load_IDTR, newidtr, 1 ); return 0; //SUCCESS } static void __exit faultcpl_exit(void ) { // delete the pseudo-file remove_proc_entry( modname, NULL ); // reactivate the original Interrupt Descriptor Table smp_call_function( load_IDTR, oldidtr, 1 ); load_IDTR( oldidtr ); // release allocated kernel memory if ( kpage ) free_page( kpage ); printk( "<1>Removing \'%s\' module\n", modname ); } module_init( faultcpl_init ); module_exit( faultcpl_exit ); MODULE_LICENSE("GPL");