❯ sudo insmod ./hello-1.ko insmod: ERROR: could not insert module ./hello-1.ko: Invalid module format ~/Documents/LKM/writeup/code Py base 09:24:36 ❯ sudo dmesg | grep modules [ 715.059773] module: x86/modules: Skipping invalid relocation target, existing value is nonzero for type 1, loc 000000000fc2cc28, val ffffffffc0cb4040 [ 1165.465487] module: x86/modules: Skipping invalid relocation target, existing value is nonzero for type 1, loc 000000000fc2cc28, val ffffffffc0cb4040
❯ sudo insmod ./hello-1.ko insmod: ERROR: could not insert module ./hello-1.ko: Invalid module format ~/Documents/LKM/writeup/code Py base 09:24:36 ❯ sudo dmesg | grep modules [ 715.059773] module: x86/modules: Skipping invalid relocation target, existing value is nonzero for type 1, loc 000000000fc2cc28, val ffffffffc0cb4040 # solution 注:如果重装的不是uname -r显示的版本,需要指定 sudo apt update && sudo apt upgrade sudo apt remove --purge linux-headers-* sudo apt autoremove && sudo apt autoclean sudo apt install linux-headers-generic
2.1.1 PrintK()
注:打印函数与Printf函数不同(应用程序可以调用C标准库,但内核函数一般调用自己提供的函数)
printk函数可以用来进行内核调试,其可以打印信息到终端或日志中,打印信息分为几个等级。
【附:除Printk外的一些打印内核信息的函数】
2.2 编译内核模块
使用Makefile进行编译
Makefile:指定内核源码,编译参数,编译平台
1 2 3 4 5 6 7
obj-m += hello-1.o
all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
/* * hello-2.c - Demonstrating the module_init() and module_exit() macros. * This is preferred over using init_module() and cleanup_module(). */ #include<linux/module.h>/* Needed by all modules */ #include<linux/kernel.h>/* Needed for KERN_INFO */ #include<linux/init.h>/* Needed for the macros */
staticint __init hello_2_init(void) { printk(KERN_INFO "Hello, world 2\n"); return0; }
staticvoid __exit hello_2_exit(void) { printk(KERN_INFO "Goodbye, world 2\n"); }
/* * hello-3.c - Illustrating the __init, __initdata and __exit macros. */ #include<linux/module.h>/* Needed by all modules */ #include<linux/kernel.h>/* Needed for KERN_INFO */ #include<linux/init.h>/* Needed for the macros */
/* * Get rid of taint message by declaring code as GPL. */ MODULE_LICENSE("GPL");
/* * Or with defines, like this: */ MODULE_AUTHOR(DRIVER_AUTHOR); /* Who wrote this module? */ MODULE_DESCRIPTION(DRIVER_DESC); /* What does this module do */
/* * This module uses /dev/testdevice. The MODULE_SUPPORTED_DEVICE macro might * be used in the future to help automatic configuration of modules, but is * currently unused other than for documentation purposes. */ MODULE_SUPPORTED_DEVICE("testdevice");
/* * module_param(foo, int, 0000) * The first param is the parameters name * The second param is it's data type * The final argument is the permissions bits, * for exposing parameters in sysfs (if non-zero) at a later stage. */
module_param(myshort, short, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); MODULE_PARM_DESC(myshort, "A short integer"); module_param(myint, int, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); MODULE_PARM_DESC(myint, "An integer"); module_param(mylong, long, S_IRUSR); MODULE_PARM_DESC(mylong, "A long integer"); module_param(mystring, charp, 0000); MODULE_PARM_DESC(mystring, "A character string");
/* * module_param_array(name, type, num, perm); * The first param is the parameter's (in this case the array's) name * The second param is the data type of the elements of the array * The third argument is a pointer to the variable that will store the number * of elements of the array initialized by the user at module loading time * The fourth argument is the permission bits */ module_param_array(myintArray, int, &arr_argc, 0000); MODULE_PARM_DESC(myintArray, "An array of integers");
staticint __init hello_5_init(void) { int i; printk(KERN_INFO "Hello, world 5\n=============\n"); printk(KERN_INFO "myshort is a short integer: %hd\n", myshort); printk(KERN_INFO "myint is an integer: %d\n", myint); printk(KERN_INFO "mylong is a long integer: %ld\n", mylong); printk(KERN_INFO "mystring is a string: %s\n", mystring); for (i = 0; i < (sizeof myintArray / sizeof (int)); i++) { printk(KERN_INFO "myintArray[%d] = %d\n", i, myintArray[i]); } printk(KERN_INFO "got %d arguments for myintArray.\n", arr_argc); return0; }
staticvoid __exit hello_5_exit(void) { printk(KERN_INFO "Goodbye, world 5\n"); }
modinfo hello-5.ko filename: /home/bronya/Documents/LKM/writeup/code/hello-5.ko author: Peter Jay Salzman license: GPL srcversion: A704327C32F7F311666C13C depends: retpoline: Y name: hello_5 vermagic: 6.2.0-37-generic SMP preempt mod_unload modversions parm: myshort:A short integer (short) parm: myint:An integer (int) parm: mylong:A long integer (long) parm: mystring:A character string (charp) parm: myintArray:An array of integers (array of int)
加载模块显示信息:
1 2 3 4 5 6 7 8 9 10 11
[ 4659.140006] hello_5: unknown parameter 'mybyte' ignored [ 4659.140047] Hello, world 5 ============= [ 4659.140048] myshort is a short integer: 1 [ 4659.140049] myint is an integer: 420 [ 4659.140049] mylong is a long integer: 9999 [ 4659.140050] mystring is a string: bebop [ 4659.140050] myintArray[0] = -1 [ 4659.140051] myintArray[1] = -1 [ 4659.140051] got 1 arguments for myintArray.
2.7 多文件编译模块
可以将内核模块分为多个源文件
例:一个模块分为start,stop,将入口函数和退出函数分离
1 2 3 4 5 6 7 8 9 10 11 12
/* * start.c - Illustration of multi filed modules */
#include<linux/kernel.h>/* We're doing kernel work */ #include<linux/module.h>/* Specifically, a module */
intinit_module(void) { printk(KERN_INFO "Hello, world - this is the kernel speaking\n"); return0; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* * stop.c - Illustration of multi filed modules */
#include<linux/kernel.h>/* We're doing kernel work */ #include<linux/module.h>/* Specifically, a module */
voidcleanup_module() { printk(KERN_INFO "Short is the life of a kernel module\n"); }
/* * f_{lock,count,pos_lock} members can be highly contended and share * the same cacheline. f_{lock,mode} are very frequently used together * and so share the same cacheline as well. The read-mostly * f_{path,inode,op} are kept on a separate cacheline. */ structfile { union { structllist_nodef_llist; structrcu_headf_rcuhead; unsignedint f_iocb_flags; };
/* * Protects f_ep, f_flags. * Must not be taken from IRQ context. */ spinlock_t f_lock; fmode_t f_mode; atomic_long_t f_count; structmutexf_pos_lock; loff_t f_pos; unsignedint f_flags; structfown_structf_owner; conststructcred *f_cred; structfile_ra_statef_ra; structpathf_path; structinode *f_inode;/* cached value */ conststructfile_operations *f_op;
u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif /* needed for tty driver, and maybe others */ void *private_data;
#ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ structhlist_head *f_ep; #endif/* #ifdef CONFIG_EPOLL */ structaddress_space *f_mapping; errseq_t f_wb_err; errseq_t f_sb_err; /* for syncfs */ } __randomize_layout __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
/* * chardev.c: Creates a read-only char device that says how many times * you've read from the dev file */
#include<linux/kernel.h> #include<linux/module.h> #include<linux/fs.h> #include<asm/uaccess.h>/* for put_user */
/* * Prototypes - this would normally go in a .h file */ intinit_module(void); voidcleanup_module(void); staticintdevice_open(struct inode *, struct file *); staticintdevice_release(struct inode *, struct file *); staticssize_tdevice_read(struct file *, char *, size_t, loff_t *); staticssize_tdevice_write(struct file *, constchar *, size_t, loff_t *);
#define SUCCESS 0 #define DEVICE_NAME "chardev"/* Dev name as it appears in /proc/devices */ #define BUF_LEN 80 /* Max length of the message from the device */
/* * Global variables are declared as static, so are global within the file. */
staticint Major; /* Major number assigned to our device driver */ staticint Device_Open = 0; /* Is device open? * Used to prevent multiple access to device */ staticchar msg[BUF_LEN]; /* The msg the device will give when asked */ staticchar *msg_Ptr;
/* * This function is called when the module is loaded */ intinit_module(void) { Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) { printk(KERN_ALERT "Registering char device failed with %d\n", Major); return Major; }
printk(KERN_INFO "I was assigned major number %d. To talk to\n", Major); printk(KERN_INFO "the driver, create a dev file with\n"); printk(KERN_INFO "'mknod /dev/%s c %d 0'.\n", DEVICE_NAME, Major); printk(KERN_INFO "Try various minor numbers. Try to cat and echo to\n"); printk(KERN_INFO "the device file.\n"); printk(KERN_INFO "Remove the device file and module when done.\n");
return SUCCESS; }
/* * This function is called when the module is unloaded */ voidcleanup_module(void) { /* * Unregister the device */ int ret = unregister_chrdev(Major, DEVICE_NAME); if (ret < 0) printk(KERN_ALERT "Error in unregister_chrdev: %d\n", ret); }
/* * Methods */
/* * Called when a process tries to open the device file, like * "cat /dev/mycharfile" */ staticintdevice_open(struct inode *inode, struct file *file) { staticint counter = 0;
if (Device_Open) return -EBUSY;
Device_Open++; sprintf(msg, "I already told you %d times Hello world!\n", counter++); msg_Ptr = msg; try_module_get(THIS_MODULE);
return SUCCESS; }
/* * Called when a process closes the device file. */ staticintdevice_release(struct inode *inode, struct file *file) { Device_Open--; /* We're now ready for our next caller */
/* * Decrement the usage count, or else once you opened the file, you'll * never get get rid of the module. */ module_put(THIS_MODULE);
return0; }
/* * Called when a process, which already opened the dev file, attempts to * read from it. */ staticssize_tdevice_read(struct file *filp, /* see include/linux/fs.h */ char *buffer, /* buffer to fill with data */ size_t length, /* length of the buffer */ loff_t * offset) { /* * Number of bytes actually written to the buffer */ int bytes_read = 0;
/* * If we're at the end of the message, * return 0 signifying end of file */ if (*msg_Ptr == 0) return0;
/* * Actually put the data into the buffer */ while (length && *msg_Ptr) {
/* * The buffer is in the user data segment, not the kernel * segment so "*" assignment won't work. We have to use * put_user which copies data from the kernel data segment to * the user data segment. */ put_user(*(msg_Ptr++), buffer++);
length--; bytes_read++; }
/* * Most read functions return the number of bytes put into the buffer */ return bytes_read; }
/* * Called when a process writes to dev file: echo "hi" > /dev/hello */ staticssize_t device_write(struct file *filp, constchar *buff, size_t len, loff_t * off) { printk(KERN_ALERT "Sorry, this operation isn't supported.\n"); return -EINVAL; }
#include<linux/module.h>/* Specifically, a module */ #include<linux/kernel.h>/* We're doing kernel work */ #include<linux/proc_fs.h>/* Necessary because we use the proc fs */ #include<linux/sched.h>
#include<linux/module.h>/* Specifically, a module */ #include<linux/kernel.h>/* We're doing kernel work */ #include<linux/proc_fs.h>/* Necessary because we use the proc fs */ #include<asm/uaccess.h>/* for copy_from_user */
/** * The buffer used to store character for this module * */ staticchar procfs_buffer[PROCFS_MAX_SIZE]; /** * The size of the buffer * */ staticunsignedlong procfs_buffer_size = 0; /** * This function is called then the /proc file is read * */ staticssize_tread_proc(struct file *filp, char __user *buf, size_t count, loff_t *offp) { int ret; printk(KERN_INFO "procfile_read (/proc/%s) called\n", PROCFS_NAME); printk(KERN_INFO "Read data: %s", procfs_buffer); if (*offp > 0) { /* we have finished to read, return 0 */ ret = 0; } else { /* fill the buffer, return the buffer size*/ copy_to_user(buf, procfs_buffer, procfs_buffer_size); } return ret; }
/** * This function is called with the /proc file is written * */ staticssize_twrite_proc(struct file *file, constchar __user *buffer, size_t count, loff_t *offp) { /* get buffer size */ procfs_buffer_size = count; if (procfs_buffer_size > PROCFS_MAX_SIZE ) { procfs_buffer_size = PROCFS_MAX_SIZE; } /* write data to the buffer */ if ( copy_from_user(procfs_buffer, buffer, procfs_buffer_size) ) { return -EFAULT; } printk(KERN_INFO "Write data: %s", procfs_buffer); return procfs_buffer_size; }
/** *This function is called when the module is loaded * */ intinit_module() { /* create the /proc file */ proc_create(PROCFS_NAME, 0644, NULL, &proc_fops);
printk(KERN_INFO "/proc/%s created\n", PROCFS_NAME); return0; /* everything is ok */ }
/** *This function is called when the module is unloaded * */ voidcleanup_module() { remove_proc_entry(PROCFS_NAME, NULL); printk(KERN_INFO "/proc/%s removed\n", PROCFS_NAME); }
/* * procfs4.c - create a "file" in /proc * This program uses the seq_file library to manage the /proc file. */ #include<linux/kernel.h>/* We are doing kernel work */ #include<linux/module.h>/* Specifically, a module */ #include<linux/proc_fs.h>/* Necessary because we use proc fs */ #include<linux/seq_file.h>/* for seq_file */ #include<linux/version.h> #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) #define HAVE_PROC_OPS #endif #define PROC_NAME "iter" /* This function is called at the beginning of a sequence. * ie, when: * - the /proc file is read (first time) * - after the function stop (end of sequence) */ staticvoid *my_seq_start(struct seq_file *s, loff_t *pos) { staticunsignedlong counter = 0; /* beginning a new sequence? */ if (*pos == 0) { /* yes => return a non null value to begin the sequence */ return &counter; } /* no => it is the end of the sequence, return end to stop reading */ *pos = 0; returnNULL; } /* This function is called after the beginning of a sequence. * It is called until the return is NULL (this ends the sequence). */ staticvoid *my_seq_next(struct seq_file *s, void *v, loff_t *pos) { unsignedlong *tmp_v = (unsignedlong *)v; (*tmp_v)++; (*pos)++; returnNULL; } /* This function is called at the end of a sequence. */ staticvoidmy_seq_stop(struct seq_file *s, void *v) { /* nothing to do, we use a static value in start() */ } /* This function is called for each "step" of a sequence. */ staticintmy_seq_show(struct seq_file *s, void *v) { loff_t *spos = (loff_t *)v; seq_printf(s, "%Ld\n", *spos); return0; } /* This structure gather "function" to manage the sequence */ staticstructseq_operationsmy_seq_ops = { .start = my_seq_start, .next = my_seq_next, .stop = my_seq_stop, .show = my_seq_show, }; /* This function is called when the /proc file is open. */ staticintmy_open(struct inode *inode, struct file *file) { return seq_open(file, &my_seq_ops); }; /* This structure gather "function" that manage the /proc file */ #ifdef HAVE_PROC_OPS staticconststructproc_opsmy_file_ops = { .proc_open = my_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = seq_release, }; #else staticconststructfile_operationsmy_file_ops = { .open = my_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; #endif staticint __init procfs4_init(void) { structproc_dir_entry *entry; entry = proc_create(PROC_NAME, 0, NULL, &my_file_ops); if (entry == NULL) { pr_debug("Error: Could not initialize /proc/%s\n", PROC_NAME); return -ENOMEM; } return0; } staticvoid __exit procfs4_exit(void) { remove_proc_entry(PROC_NAME, NULL); pr_debug("/proc/%s removed\n", PROC_NAME); } module_init(procfs4_init); module_exit(procfs4_exit); MODULE_LICENSE("GPL");
/* * chardev2.c - Create an input/output character device */ #include<linux/atomic.h> #include<linux/cdev.h> #include<linux/delay.h> #include<linux/device.h> #include<linux/fs.h> #include<linux/init.h> #include<linux/module.h>/* Specifically, a module */ #include<linux/printk.h> #include<linux/types.h> #include<linux/uaccess.h>/* for get_user and put_user */ #include<linux/version.h> #include<asm/errno.h> #include"chardev.h" #define SUCCESS 0 #define DEVICE_NAME "char_dev" #define BUF_LEN 80 enum { CDEV_NOT_USED = 0, CDEV_EXCLUSIVE_OPEN = 1, }; /* Is the device open right now? Used to prevent concurrent access into * the same device */ staticatomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED); /* The message the device will give when asked */ staticchar message[BUF_LEN + 1]; staticstructclass *cls; /* This is called whenever a process attempts to open the device file */ staticintdevice_open(struct inode *inode, struct file *file) { pr_info("device_open(%p)\n", file); try_module_get(THIS_MODULE); return SUCCESS; } staticintdevice_release(struct inode *inode, struct file *file) { pr_info("device_release(%p,%p)\n", inode, file); module_put(THIS_MODULE); return SUCCESS; } /* This function is called whenever a process which has already opened the * device file attempts to read from it. */ staticssize_tdevice_read(struct file *file, /* see include/linux/fs.h */ char __user *buffer, /* buffer to be filled */ size_t length, /* length of the buffer */ loff_t *offset) { /* Number of bytes actually written to the buffer */ int bytes_read = 0; /* How far did the process reading the message get? Useful if the message * is larger than the size of the buffer we get to fill in device_read. */ constchar *message_ptr = message; if (!*(message_ptr + *offset)) { /* we are at the end of message */ *offset = 0; /* reset the offset */ return0; /* signify end of file */ } message_ptr += *offset; /* Actually put the data into the buffer */ while (length && *message_ptr) { /* Because the buffer is in the user data segment, not the kernel * data segment, assignment would not work. Instead, we have to * use put_user which copies data from the kernel data segment to * the user data segment. */ put_user(*(message_ptr++), buffer++); length--; bytes_read++; } pr_info("Read %d bytes, %ld left\n", bytes_read, length); *offset += bytes_read; /* Read functions are supposed to return the number of bytes actually * inserted into the buffer. */ return bytes_read; } /* called when somebody tries to write into our device file. */ staticssize_tdevice_write(struct file *file, constchar __user *buffer, size_t length, loff_t *offset) { int i; pr_info("device_write(%p,%p,%ld)", file, buffer, length); for (i = 0; i < length && i < BUF_LEN; i++) get_user(message[i], buffer + i); /* Again, return the number of input characters used. */ return i; } /* This function is called whenever a process tries to do an ioctl on our * device file. We get two extra parameters (additional to the inode and file * structures, which all device functions get): the number of the ioctl called * and the parameter given to the ioctl function. * * If the ioctl is write or read/write (meaning output is returned to the * calling process), the ioctl call returns the output of this function. */ staticlong device_ioctl(struct file *file, /* ditto */ unsignedint ioctl_num, /* number and param for ioctl */ unsignedlong ioctl_param) { int i; long ret = SUCCESS; /* We don't want to talk to two processes at the same time. */ if (atomic_cmpxchg(&already_open, CDEV_NOT_USED, CDEV_EXCLUSIVE_OPEN)) return -EBUSY; /* Switch according to the ioctl called */ switch (ioctl_num) { case IOCTL_SET_MSG: { /* Receive a pointer to a message (in user space) and set that to * be the device's message. Get the parameter given to ioctl by * the process. */ char __user *tmp = (char __user *)ioctl_param; char ch; /* Find the length of the message */ get_user(ch, tmp); for (i = 0; ch && i < BUF_LEN; i++, tmp++) get_user(ch, tmp); device_write(file, (char __user *)ioctl_param, i, NULL); break; } case IOCTL_GET_MSG: { loff_t offset = 0; /* Give the current message to the calling process - the parameter * we got is a pointer, fill it. */ i = device_read(file, (char __user *)ioctl_param, 99, &offset); /* Put a zero at the end of the buffer, so it will be properly * terminated. */ put_user('\0', (char __user *)ioctl_param + i); break; } case IOCTL_GET_NTH_BYTE: /* This ioctl is both input (ioctl_param) and output (the return * value of this function). */ ret = (long)message[ioctl_param]; break; } /* We're now ready for our next caller */ atomic_set(&already_open, CDEV_NOT_USED); return ret; } /* Module Declarations */ /* This structure will hold the functions to be called when a process does * something to the device we created. Since a pointer to this structure * is kept in the devices table, it can't be local to init_module. NULL is * for unimplemented functions. */ staticstructfile_operationsfops = { .read = device_read, .write = device_write, .unlocked_ioctl = device_ioctl, .open = device_open, .release = device_release, /* a.k.a. close */ }; /* Initialize the module - Register the character device */ staticint __init chardev2_init(void) { /* Register the character device (atleast try) */ int ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops); /* Negative values signify an error */ if (ret_val < 0) { pr_alert("%s failed with %d\n", "Sorry, registering the character device ", ret_val); return ret_val; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) cls = class_create(DEVICE_FILE_NAME); #else cls = class_create(THIS_MODULE, DEVICE_FILE_NAME); #endif device_create(cls, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_FILE_NAME); pr_info("Device created on /dev/%s\n", DEVICE_FILE_NAME); return0; } /* Cleanup - unregister the appropriate file from /proc */ staticvoid __exit chardev2_exit(void) { device_destroy(cls, MKDEV(MAJOR_NUM, 0)); class_destroy(cls); /* Unregister the device */ unregister_chrdev(MAJOR_NUM, DEVICE_NAME); } module_init(chardev2_init); module_exit(chardev2_exit); MODULE_LICENSE("GPL");
/* * chardev.h - the header file with the ioctl definitions. * * The declarations here have to be in a header file, because they need * to be known both to the kernel module (in chardev2.c) and the process * calling ioctl() (in userspace_ioctl.c). */ #ifndef CHARDEV_H #define CHARDEV_H #include<linux/ioctl.h> /* The major device number. We can not rely on dynamic registration * any more, because ioctls need to know it. */ #define MAJOR_NUM 100 /* Set the message of the device driver */ #define IOCTL_SET_MSG _IOW(MAJOR_NUM, 0, char *) /* _IOW means that we are creating an ioctl command number for passing * information from a user process to the kernel module. * * The first arguments, MAJOR_NUM, is the major device number we are using. * * The second argument is the number of the command (there could be several * with different meanings). * * The third argument is the type we want to get from the process to the * kernel. */ /* Get the message of the device driver */ #define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *) /* This IOCTL is used for output, to get the message of the device driver. * However, we still need the buffer to place the message in to be input, * as it is allocated by the process. */ /* Get the n'th byte of the message */ #define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int) /* The IOCTL is used for both input and output. It receives from the user * a number, n, and returns message[n]. */ /* The name of the device file */ #define DEVICE_FILE_NAME "char_dev" #define DEVICE_PATH "/dev/char_dev" #endif
/* userspace_ioctl.c - the process to use ioctl's to control the kernel module * * Until now we could have used cat for input and output. But now * we need to do ioctl's, which require writing our own process. */ /* device specifics, such as ioctl numbers and the * major device file. */ #include"../chardev.h" #include<stdio.h>/* standard I/O */ #include<fcntl.h>/* open */ #include<unistd.h>/* close */ #include<stdlib.h>/* exit */ #include<sys/ioctl.h>/* ioctl */ /* Functions for the ioctl calls */ intioctl_set_msg(int file_desc, char *message) { int ret_val; ret_val = ioctl(file_desc, IOCTL_SET_MSG, message); if (ret_val < 0) { printf("ioctl_set_msg failed:%d\n", ret_val); } return ret_val; } intioctl_get_msg(int file_desc) { int ret_val; char message[100] = { 0 }; /* Warning - this is dangerous because we don't tell * the kernel how far it's allowed to write, so it * might overflow the buffer. In a real production * program, we would have used two ioctls - one to tell * the kernel the buffer length and another to give * it the buffer to fill */ ret_val = ioctl(file_desc, IOCTL_GET_MSG, message); if (ret_val < 0) { printf("ioctl_get_msg failed:%d\n", ret_val); } printf("get_msg message:%s", message); return ret_val; } intioctl_get_nth_byte(int file_desc) { int i, c; printf("get_nth_byte message:"); i = 0; do { c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++); if (c < 0) { printf("\nioctl_get_nth_byte failed at the %d'th byte:\n", i); return c; } putchar(c); } while (c != 0); return0; } /* Main - Call the ioctl functions */ intmain(void) { int file_desc, ret_val; char *msg = "Message passed by ioctl\n"; file_desc = open(DEVICE_PATH, O_RDWR); if (file_desc < 0) { printf("Can't open device file: %s, error:%d\n", DEVICE_PATH, file_desc); exit(EXIT_FAILURE); } ret_val = ioctl_set_msg(file_desc, msg); if (ret_val) goto error; ret_val = ioctl_get_nth_byte(file_desc); if (ret_val) goto error; ret_val = ioctl_get_msg(file_desc); if (ret_val) goto error; close(file_desc); return0; error: close(file_desc); exit(EXIT_FAILURE); }