***************************************************************************** * CHANGING AN EXISTING SYSTEM CALL * * * * * * Copyright (c) 2001 Daniel P. Bovet, Marco Cesati, and Cosimo Comella * * Permission is granted to copy, distribute and/or modify this document * * under the terms of the GNU Free Documentation License, Version 1.1, * * published by the Free Software Foundation; with no Invariant Sections, * * with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the * * license is included in the file named LICENSE. * * * * (version 1.1) * ***************************************************************************** The time has come to make some changes inside the kernel. We'll start with a very simple change, just to break the ice. In the forthcoming lectures, we'll deal with more ambitious tasks. Our goal is to extract the current value of an important kernel variable named "jiffies". This variable is a counter that is incremented every 10 ms, starting from system initialization. Since jiffies is a 32-bit variable, it returns to 0 about 497 days after the system has been booted: we don't care about this overflow problem and we consider ourselves satisfied if we succeed in writing User Mode programs that display the current value of jiffies. A few kernel variables can be read (and in some cases set) by User Mode programs. This is possible if the variable is included in one of the supported proc files, such as /proc/sys/kernel/ostype or /proc/sys/fs/file-max. In this case, the programmer has two ways to retrieve the variable: - read (or write) the proper proc file - invoke the sysctl() system call passing as parameter an identifier of the proc file Unfortunately, no proc file exists for jiffies, so none of the two approaches work. The correct way to proceed would thus create first a new proc file, something like /proc/sys/kernel/jiffies and then expand sysctl() so that it can access jiffies. This looks too complicated for a first project. We'll do something simpler: modify the code of the sysctl() service routine so that when the system call is invoked with a new parameter value, it returns the value of jiffies. Our workplan is thus the following: 1) study the sysctl conventions for parameter passing and prepare a User Mode test programm to check that we really understood them 2) modify the code of the sysctl() system call so that it accepts a new parameter value; we'll have to deal with the sys_sysctl() service routine that appears in the kernel/sysctl.c file 3) recompile the modified kernel, copy bzImage in the boot partition, run lilo as explained in the previous lecture (Booting from disk) and restart the system 4) prepare a User Mode test programm to check whether the kernel change worked, i.e. if we now succeed in getting the current value of jiffies. ***************************************************************************** STEP 0: set the proper EXTRAVERSION value in Makefile ***************************************************************************** replace: EXTRAVERSION = with: EXTRAVERSION = kh2 ***************************************************************************** STEP 1: using the sysctl() system call ***************************************************************************** sysctl() makes use of a single parameter that is a pointer to a struct___sysctl_args structure of 6 elements: 1) name: a pointer to an array of integers that encodes the required proc file; instead of specifying the proc file as: "/proc/kernel/ostype" you must set: int name[0] = CTL_KERN; int name[1] = CTL_OSTYPE; or, more succinctly: int name[] = {CTL_KERN, CTL_OSTYPE}; 2) nlen: the length of the array pointed by name 3) oldval: a pointer to a string of chars where the content of the selected proc file will be returned 4) oldlenp: a pointer to the length of the selected proc file 5) newval: a pointer to a string of chars representingthe new value of the proc file (only if the proc file can be written) 6) newlen: the length of newval The following programm prints the contents of the /proc/sys/kernel/ostype proc file: **************** /* Test program for the original sysctl() system call */ #include #include #include #include _syscall1(int, _sysctl, struct __sysctl_args *, args) int sysctl(int * name, int nlen, void * oldval, size_t * oldlenp, void * newval, size_t new_len) { struct __sysctl_args params = {name, nlen, oldval, oldlenp, newval, new_len}; return _sysctl(¶ms); } char result[100]; int resultlen; /* we want to get the "ostype" proc file stored in the /proc/sys/kernel directory; in this case, query[] must be set to: */ int query[] = {CTL_KERN, KERN_OSTYPE}; /* see file include/linux/sysctl.h for other possible queries */ int main() { int retcode; resultlen = sizeof(result); retcode = sysctl(query, sizeof(query), &result, &resultlen, 0, 0); if (retcode == 0) printf("This machine is running %*s\n", resultlen, result); printf("retcode = %d\n", retcode); exit(0); } **************** The rationale for this intricate sequence of macro and function definitions is the following: - first the syscall1() macro expands into the C function definition: int _sysctl(struct __sysctl_args * args) { ...... } - next comes the function definition of: int sysctl(int * name, int nlen, void * oldval, size_t * oldlenp, void * newval, size_t new_len) which invokes _sysctl() defined previously in the return statement - finally comes the main() function that invokes sysctl() ***************************************************************************** STEP 2: modifying the sys_sysctl() service routine ***************************************************************************** Replace the original sys_sysctl() service routine: extern asmlinkage long sys_sysctl(struct __sysctl_args *args) { struct __sysctl_args tmp; int error; if (copy_from_user(&tmp, args, sizeof(tmp))) return -EFAULT; lock_kernel(); error = do_sysctl(tmp.name, tmp.nlen, tmp.oldval, tmp.oldlenp, tmp.newval, tmp.newlen); unlock_kernel(); return error; } with: extern unsigned long jiffies; extern asmlinkage long sys_sysctl(struct __sysctl_args *args) { struct __sysctl_args tmp; int error; if (copy_from_user(&tmp, args, sizeof(tmp))) return -EFAULT; lock_kernel(); if (tmp.nlen == 9999) { put_user(jiffies, args->oldlenp); unlock_kernel(); return 9999; } error = do_sysctl(tmp.name, tmp.nlen, tmp.oldval, tmp.oldlenp, tmp.newval, tmp.newlen); unlock_kernel(); return error; } As you can notice, we do some dirty coding: whenever the system call is called with the nlen parameter equal to 9999, it always return the value of jiffies in the oldlenp parameter. It's not elegant at all but it works. ***************************************************************************** STEP 3: recompile the modified kernel, copy bzImage in the boot partition, run lilo as explained in the previous lecture (Booting from disk) and restart the system ***************************************************************************** ***************************************************************************** STEP 4: prepare a User Mode test programm to check whether the kernel change worked ***************************************************************************** The test program is quite similar to the one used to check whether we were able to use sysctl(): /* Test program for the modified sysctl() system call */ #include #include #include #include _syscall1(int, _sysctl, struct __sysctl_args *, args) int sysctl(int * name, int nlen, void * oldval, size_t * oldlenp, void * newval, size_t new_len) { struct __sysctl_args params = {name, nlen, oldval, oldlenp, newval, new_len}; return _sysctl(¶ms); } char result[100]; int resultlen; /* even if we don't want to make a canonical query, we pretend to issue a request for the "ostype" proc file stored in the /proc/sys/kernel directory; in this case, query[] must be set to: */ int query[] = {CTL_KERN, KERN_OSTYPE}; /* see file include/linux/sysctl.h for other possible queries */ int main() { int retcode; resultlen = sizeof(result); /* dirty code: sizeof(query) is set to 9999 to fool the sys_sysctl() service routine and force it to return the current value of the "jiffies" kernel variable in the resultlen parameter the service routine returns in retcode the value of the second parameter, which must be set to 9999 */ retcode = sysctl(query, 9999, &result, &resultlen, 0, 0); printf("jiffies= %d, retcode= %d\n", resultlen, retcode); exit(0); }