How to handle more than one SIGSEGV occurrence in linux? - linux

I have written a program to scan kernel memory for a pattern from user space. I run it from root. I expect that it will generate SIGSEGVs when it hits pages that aren't accessible; I would like to ignore those faults and just jump to the next page to continue the search. I have set up a signal handler that works fine for the first occurrence, and it continues onward as expected. However, when a second SIGSEGV occurs, the handler is ignored (it was reregistered after the first occurrence) and the program terminates. The relevant portions of the code are:
jmp_buf restore_point;
void segv_handler(int sig, siginfo_t* info, void* ucontext)
{
longjmp(restore_point, SIGSEGV);
}
void setup_segv_handler()
{
struct sigaction sa;
sa.sa_flags = SA_SIGINFO|SA_RESTART|SA_RESETHAND;
sigemptyset (&sa.sa_mask);
sa.sa_sigaction = &segv_handler;
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
fprintf(stderr, "failed to setup SIGSEGV handler\n");
}
}
unsigned long search_kernel_memory_area(unsigned long start_address, size_t area_len, const void* pattern, size_t pattern_len)
{
int fd;
char* kernel_mem;
fd = open("/dev/kmem", O_RDONLY);
if (fd < 0)
{
perror("open /dev/kmem failed");
return -1;
}
unsigned long page_size = sysconf(_SC_PAGESIZE);
unsigned long page_aligned_offset = (start_address/page_size)*page_size;
unsigned long area_pages = area_len/page_size + (area_len%page_size ? 1 : 0);
kernel_mem =
mmap(0, area_pages,
PROT_READ, MAP_SHARED,
fd, page_aligned_offset);
if (kernel_mem == MAP_FAILED)
{
perror("mmap failed");
return -1;
}
if (!mlock((const void*)kernel_mem,area_len))
{
perror("mlock failed");
return -1;
}
unsigned long offset_into_page = start_address-page_aligned_offset;
unsigned long start_area_address = (unsigned long)kernel_mem + offset_into_page;
unsigned long end_area_address = start_area_address+area_len-pattern_len+1;
unsigned long addr;
setup_segv_handler();
for (addr = start_area_address; addr < end_area_address;addr++)
{
unsigned char* kmp = (unsigned char*)addr;
unsigned char* pmp = (unsigned char*)pattern;
size_t index = 0;
for (index = 0; index < pattern_len; index++)
{
if (setjmp(restore_point) == 0)
{
unsigned char p = *pmp;
unsigned char k = *kmp;
if (k != p)
{
break;
}
pmp++;
kmp++;
}
else
{
addr += page_size -1;
setup_segv_handler();
break;
}
}
if (index >= pattern_len)
{
return addr;
}
}
munmap(kernel_mem,area_pages);
close(fd);
return 0;
}
I realize I can use functions like memcmp to avoid programming the matching part directly (I did this initially), but I subsequently wanted to insure the finest grained control for recovering from the faults so I could see exactly what was happening.
I scoured the Internet to find information about this behavior, and came up empty. The linux system I am running this under is arm 3.12.30.
If what I am trying to do is not possible under linux, is there some way I can get the current state of the kernel pages from user space (which would allow me to avoid trying to search pages that are inaccessible.) I searched for calls that might provide such information, but also came up empty.
Thanks for your help!

While longjmp is perfectly allowed to be used in the signal handler (the function is known as async-signal-safe, see man signal-safety) and effectively exits from the signal handling, it doesn't restore signal mask. The mask is automatically modified at the time when signal handler is called to block new SIGSEGV signal to interrupt the handler.
While one may restore signal mask manually, it is better (and simpler) to use siglongjmp function instead: aside from the effect of longjmp, it also restores the signal mask. Of course, in that case sigsetjmp function should be used instead of setjmp:
// ... in main() function
if(sigsetjmp(restore_point, 1)) // Aside from other things, store signal mask
// ...
// ... in the signal handler
siglongjmp(restore_point); // Also restore signal mask as it was at sigsetjmp() call

Related

Question in Book Linux device driver 3rd about interrupt handler for circular buffer

I am reading LLDR3, having a question on P.271 in section "Implementing a Handler"
Bottom are codes I am having questions:
I see writer ( ISR ) and reader ( which is waken-up by ISR ) they are touching on same buffer ( short_queue ), since they are touching on the shared resource, doesn't it worry about the case where "short_i_read" got interrupted by the writer ISR while it is working on buffer?
I can understand ISR writer won't get interrupted since it is ISR and normally IRQ will be disabled until completion. But for buffer read "short_i_read" , I don't see any place to guarantee atomic operation.
The one thing I notice is :
buffer writer(ISR) only increment on short_head
buffer reader only increment on short_tail
Does that mean this code here let writer and reader only touch different variable to have it achieve kind of lock-free circular buffer?
irqreturn_t short_interrupt(int irq, void *dev_id, struct pt_regs *regs) {
struct timeval tv;
int written;
do_gettimeofday(&tv);
/* Write a 16 byte record. Assume PAGE_SIZE is a multiple of 16 */
written = sprintf((char *)short_head,"%08u.%06u\n", (int)(tv.tv_sec % 100000000), (int)(tv.tv_usec));
BUG_ON(written != 16);
short_incr_bp(&short_head, written);
wake_up_interruptible(&short_queue);
/* awake any reading process */
return IRQ_HANDLED;
}
static inline void short_incr_bp(volatile unsigned long *index, int delta) {
unsigned long new = *index + delta;
barrier(); /* Don't optimize these two together */
*index = (new >= (short_buffer + PAGE_SIZE)) ? short_buffer : new;
}
ssize_t short_i_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
int count0;
DEFINE_WAIT(wait);
while (short_head == short_tail) {
prepare_to_wait(&short_queue, &wait, TASK_INTERRUPTIBLE);
if (short_head == short_tail)
schedule();
finish_wait(&short_queue, &wait);
if (signal_pending (current)) /* a signal arrived */
return -ERESTARTSYS; /* tell the fs layer to handle it */
}
/* count0 is the number of readable data bytes */
count0 = short_head - short_tail;
if (count0 < 0) /* wrapped */
count0 = short_buffer + PAGE_SIZE - short_tail;
if (count0 < count) count = count0;
if (copy_to_user(buf, (char *)short_tail, count))
return -EFAULT;
short_incr_bp (&short_tail, count);
return count;
}

Building a Simple character device but device driver file will not write or read

I am trying to write a simple character device/LKM that reads, writes, and seeks.
I have been having a lot of issues with this, but have been working on it/troubleshooting for weeks and have been unable to get it to work properly. Currently, my module makes properly and mounts and unmounts properly, but if I try to echo to the device driver file the terminal crashes, and when i try to read from it using cat it returns killed.
Steps for this module:
First, I make the module by running make -C /lib/modules/$(uname -r)/build M=$PWD modules
For my kernel, uname -r is 4.10.17newkernel
I mount the module using sudo insmod simple_char_driver.ko
If I run lsmod, the module is listed
If I run dmesg, the KERN_ALERT in my init function "This device is now open" triggers correctly.
Additionally, if I run sudo rmmod, that functions "This device is now closed" KERN_ALERT also triggers correctly.
The module also shows up correctly in cat /proc/devices
I created the device driver file in /dev using sudo mknod -m 777 /dev/simple_char_driver c 240 0
Before making this file, I made sure that the 240 major number was not already in use.
My device driver c file has the following code:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/slab.h>
#include<asm/uaccess.h>
#define BUFFER_SIZE 1024
MODULE_LICENSE("GPL");
//minor nunmber 0;
static int place_in_buffer = 0;
static int end_of_buffer = 1024;
static int MAJOR_NUMBER = 240;
char* DEVICE_NAME = "simple_char_driver";
typedef struct{
char* buf;
}buffer;
char *device_buffer;
static int closeCounter=0;
static int openCounter=0;
ssize_t simple_char_driver_read (struct file *pfile, char __user *buffer, size_t length, loff_t *offset){
int bytesRead = 0;
if (*offset >=BUFFER_SIZE){
bytesRead = 0;
}
if (*offset + length > BUFFER_SIZE){
length = BUFFER_SIZE - *offset;
}
printk(KERN_INFO "Reading from device\n");
if (copy_to_user(buffer, device_buffer + *offset, length) != 0){
return -EFAULT;
}
copy_to_user(buffer, device_buffer + *offset, length);
*offset += length;
printk(KERN_ALERT "Read: %s", buffer);
printk(KERN_ALERT "%d bytes read\n", bytesRead);
return 0;
}
ssize_t simple_char_driver_write (struct file *pfile, const char __user *buffer, size_t length, loff_t *offset){
int nb_bytes_to_copy;
if (BUFFER_SIZE - 1 -*offset <= length)
{
nb_bytes_to_copy= BUFFER_SIZE - 1 -*offset;
printk("BUFFER_SIZE - 1 -*offset <= length");
}
else if (BUFFER_SIZE - 1 - *offset > length)
{
nb_bytes_to_copy = length;
printk("BUFFER_SIZE - 1 -*offset > length");
}
printk(KERN_INFO "Writing to device\n");
if (*offset + length > BUFFER_SIZE)
{
printk("sorry, can't do that. ");
return -1;
}
printk("about to copy from device");
copy_from_user(device_buffer + *offset, buffer, nb_bytes_to_copy);
device_buffer[*offset + nb_bytes_to_copy] = '\0';
*offset += nb_bytes_to_copy;
return nb_bytes_to_copy;
}
int simple_char_driver_open (struct inode *pinode, struct file *pfile)
{
printk(KERN_ALERT"This device is now open");
openCounter++;
printk(KERN_ALERT "This device has been opened this many times: %d\n", openCounter);
return 0;
}
int simple_char_driver_close (struct inode *pinode, struct file *pfile)
{
printk(KERN_ALERT"This device is now closed");
closeCounter++;
printk(KERN_ALERT "This device has been closed this many times: %d\n", closeCounter);
return 0;
}
loff_t simple_char_driver_seek (struct file *pfile, loff_t offset, int whence)
{
printk(KERN_ALERT"We are now seeking!");
switch(whence){
case 0:{
if(offset<= end_of_buffer && offset >0){
place_in_buffer = offset;
printk(KERN_ALERT" this is where we are in the buffer: %d\n", place_in_buffer);
}
else{
printk(KERN_ALERT"ERROR you are attempting to go ouside the Buffer");
}
break;//THIS IS SEEK_SET
}
case 1:{
if(((place_in_buffer+offset)<= end_of_buffer)&&((place_in_buffer+offset)>0)){
place_in_buffer = place_in_buffer+offset;
printk(KERN_ALERT" this is where we are in the buffer: %d\n", place_in_buffer);
}
else{
printk(KERN_ALERT"ERROR you are attempting to go ouside the Buffer");
}
break;
}
case 2:{//THIS IS SEEK END
if((end_of_buffer-offset)>=0&& offset>0){
place_in_buffer = end_of_buffer-offset;
printk(KERN_ALERT" this is where we are in the buffer: %d\n", place_in_buffer);
}
else{
printk(KERN_ALERT"ERROR you are attempting to go ouside the Buffer");
}
break;
}
default:{
}
}
printk(KERN_ALERT"I sought %d\n", whence);
return place_in_buffer;
}
struct file_operations simple_char_driver_file_operations = {
.owner = THIS_MODULE,
.read = simple_char_driver_read,
.write = simple_char_driver_write,
.open = simple_char_driver_open,
.llseek = &simple_char_driver_seek,
.release = simple_char_driver_close,
};
static int simple_char_driver_init(void)
{
printk(KERN_ALERT "inside %s function\n",__FUNCTION__);
register_chrdev(MAJOR_NUMBER,DEVICE_NAME, &simple_char_driver_file_operations);
device_buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL);
return 0;
}
static void simple_char_driver_exit(void)
{
printk(KERN_ALERT "inside %s function\n",__FUNCTION__);
unregister_chrdev(MAJOR_NUMBER, DEVICE_NAME);
kfree(device_buffer);
}
module_init(simple_char_driver_init);
module_exit(simple_char_driver_exit);
As I said before, this file makes properly with no errors or warnings.
However, currently if I try to echo to the device file
using: echo "hello world" >> /dev/simple_char_driver
The terminal I am using crashes
If I then reopen a terminal, and use: cat /dev/simple_char_driver
then the terminal returns killed.
I am completely lost as to what is going wrong, and I have been searching for a solution for a very long time without success. If anyone has any insight into what is going wrong, please let me know.
Edit: As a user below suggested, I removed all code from my read and write methods except for the printk and the return, to make sure the functions were being triggered.
When I then used echo, dmesg showed that the write printk was triggered, and the device(which I had had open) closed. When I then tried to cat the device file, dmesg showed that the device reopened, the "ready from device" printk showed up succesfully, and then the device closed again. However, echo did not actually find anything to read from the device file, despite my having echoed "Hello world" into it immediately before.
edit
Final functioning read and write functions are as follows:
ssize_t simple_char_driver_read (struct file *pfile, char __user *buffer, size_t length, loff_t *offset)
{
if (*offset > BUFFER_SIZE)
{
printk("offset is greater than buffer size");
return 0;
}
if (*offset + length > BUFFER_SIZE)
{
length = BUFFER_SIZE - *offset;
}
if (copy_to_user(buffer, device_buffer + *offset, length) != 0)
{
return -EFAULT;
}
*offset += length;
return length;
}
ssize_t simple_char_driver_write (struct file *pfile, const char __user *buffer, size_t length, loff_t *offset){
/* *buffer is the userspace buffer where you are writing the data you want to be written in the device file*/
/* length is the length of the userspace buffer*/
/* current position of the opened file*/
/* copy_from_user function: destination is device_buffer and source is the userspace buffer *buffer */
int nb_bytes_to_copy;
if (BUFFER_SIZE - 1 -*offset <= length)
{
nb_bytes_to_copy= BUFFER_SIZE - 1 -*offset;
printk("BUFFER_SIZE - 1 -*offset <= length");
}
else if (BUFFER_SIZE - 1 - *offset > length)
{
nb_bytes_to_copy = length;
printk("BUFFER_SIZE - 1 -*offset > length");
}
printk(KERN_INFO "Writing to device\n");
if (*offset + length > BUFFER_SIZE)
{
printk("sorry, can't do that. ");
return -1;
}
printk("about to copy from device");
copy_from_user(device_buffer + *offset, buffer, nb_bytes_to_copy);
device_buffer[*offset + nb_bytes_to_copy] = '\0';
*offset += nb_bytes_to_copy;
return nb_bytes_to_copy;
}
Your code in general leaves much to be desired, but what I can see at the moment is that your .write implementation might be dubious. There are two possible mistakes - the absence of buffer boundaries check and disregard of null-termination which may lead to undefined behaviour of strlen().
First of all, you know the size of your buffer - BUFFER_SIZE. Therefore, you should carry out a check that *offset + length < BUFFER_SIZE. It should be < and not <= because anyhow the last byte shall be reserved for null-termination. So, such a check shall make the method return immediately if no space is available (else branch or >=). I can't say for sure whether you should return 0 to report that nothing has been written or use a negative value to return an error code, say, -ENOBUFS or -ENOSPC. Anyhow, the return value of the method is ssize_t meaning that negative value may be returned.
Secondly, if your first check succeeds, your method shall calculate actual space available for writing. I.e., you can make use of MIN(A, B) macro to do this. In other words, you'd better create a variable, say, nb_bytes_to_copy and initialise it like nb_bytes_to_copy = MIN(BUFFER_SIZE - 1 - *offset, length) so that you can use it later in copy_from_user() call. If the user, say, requests to write 5 bytes of data starting at the offset of 1021 bytes, then your driver will allow to write only 2 bytes of the data - say, he instead of hello. Also, the return value shall be set to nb_bytes_to_copy so that the caller will be able to detect the buffer space shortage.
Finally, don't forget about null termination. As soon as you've done with
copy_from_user(device_buffer + *offset, buffer, nb_bytes_to_copy);
you shall pay attention to do something like
device_buffer[*offset + nb_bytes_copy] = '\0';
Alternatively, if I recall correctly, you may use a special function like strncopy_from_user() to make sure that the data is copied with an implicit null termination.
Also, although a null-terminated write shall not cause problems with subsequent strlen(), I doubt that you ever need it. You can simply do *offset += nb_bytes_to_copy.
By the way, I'd recommend to name the arguments/variables in a more descriptive way. *offset is an eyesore. It would look better if named *offsetp. If your method becomes huge, an average reader will unlikely remember that offset is a pointer and not a value. offsetp where p stands for "pointer" will ease the job of anyone who will support your code in future.
To put it together, I doubt your .write implementation and suggest that you rework it. If some other mistakes persist, you will need to debug them further. Adding debug printouts may come in handy, but please revisit the basic points first, such as null-termination and buffer boundary protection. To make my answer a little bit more useful for you, I furnish it with the link to the section 3.7 of "Linux Device Drivers 3" book which will shed light on the topic under discussion.

Error when reading from Linux FIFO

In the embedded application I'm working on we have a serial port abstraction, and I'm currently working on a simulated variant of said abstraction to use when you are not running on the 'real' hardware. I'm using FIFO files for this, as you can then plug in whathever software you want to communicate with the actual application but I'm having trouble with the "read" function, which flags that you gave it an invalid fd. Though I have used debugging tools to verify that the fd passed to it is the same as has been opened earlier so it should be valid. I cannot find any cause for this problem.
FIFO files are opened through this function:
int createOpenFifo(const std::string& path, int flags)
{
int fd = open(path.c_str(), flags);
if (fd < 0)
{
mkfifo(path.c_str(), 0777);
fd = open(path.c_str(), flags);
if (fd < 0)
{
return -1;
}
}
return fd;
}
And the FIFOs are then written to using the following function:
int write_serial(handle_t handle, size_t length, const uint8_t* pData)
{
SerialFifoData* data = static_cast<SerialFifoData*>(handle);
size_t written = 0;
while (written < length)
{
int result = write(data->writeFd, pData + written, length - written);
if (result < 0)
{
return -1;
}
written += result;
}
return 0;
}
And finally read from using this function:
int read_serial(handle_t handle, size_t buffer_size, uint8_t* pBuffer, size_t* bytes_read)
{
SerialFifoData* data = static_cast<SerialFifoData*>(handle);
int return_val = read(data->readFd, pBuffer, buffer_size);
if (return_val < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK) // Non-blocking, no data
// which flag is raised
// varies between POSIX
// implementations
{
*bytes_read = 0;
return -2;
}
return -1;
}
*bytes_read = return_val;
return 0;
}
I have verified that each function recieves correct input, and the read and write calls are nearly identical to those used for the actual serial port code (the only difference is how the FD is extracted from the handle) where they work just fine.

How to determine if code is running in signal-handler context?

I just found out that someone is calling - from a signal handler - a definitely not async-signal-safe function that I wrote.
So, now I'm curious: how to circumvent this situation from happening again? I'd like to be able to easily determine if my code is running in signal handler context (language is C, but wouldn't the solution apply to any language?):
int myfunc( void ) {
if( in_signal_handler_context() ) { return(-1) }
// rest of function goes here
return( 0 );
}
This is under Linux.
Hope this isn't an easy answer, or else I'll feel like an idiot.
Apparently, newer Linux/x86 (probably since some 2.6.x kernel) calls signal handlers from the vdso. You could use this fact to inflict the following horrible hack upon the unsuspecting world:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
uintmax_t vdso_start = 0;
uintmax_t vdso_end = 0; /* actually, next byte */
int check_stack_for_vdso(uint32_t *esp, size_t len)
{
size_t i;
for (i = 0; i < len; i++, esp++)
if (*esp >= vdso_start && *esp < vdso_end)
return 1;
return 0;
}
void handler(int signo)
{
uint32_t *esp;
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
/* XXX only for demonstration, don't call printf from a signal handler */
printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
}
void parse_maps()
{
FILE *maps;
char buf[256];
char path[7];
uintmax_t start, end, offset, inode;
char r, w, x, p;
unsigned major, minor;
maps = fopen("/proc/self/maps", "rt");
if (maps == NULL)
return;
while (!feof(maps) && !ferror(maps)) {
if (fgets(buf, 256, maps) != NULL) {
if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s",
&start, &end, &r, &w, &x, &p, &offset,
&major, &minor, &inode, path) == 11) {
if (!strcmp(path, "[vdso]")) {
vdso_start = start;
vdso_end = end;
break;
}
}
}
}
fclose(maps);
printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end);
}
int main()
{
struct sigaction sa;
uint32_t *esp;
parse_maps();
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART;
if (sigaction(SIGUSR1, &sa, NULL) < 0) {
perror("sigaction");
exit(1);
}
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
kill(getpid(), SIGUSR1);
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
return 0;
}
SCNR.
If we can assume your application doesn't manually block signals using sigprocmask() or pthread_sigmask(), then this is pretty simple: get your current thread ID (tid). Open /proc/tid/status and get the values for SigBlk and SigCgt. AND those two values. If the result of that AND is non-zero, then that thread is currently running from inside a signal handler. I've tested this myself and it works.
There are two proper ways to deal with this:
Have your co-workers stop doing the wrong thing. Good luck pulling this off with the boss, though...
Make your function re-entrant and async-safe. If necessary, provide a function with a different signature (e.g. using the widely-used *_r naming convention) with the additional arguments that are necessary for state preservation.
As for the non-proper way to do this, on Linux with GNU libc you can use backtrace() and friends to go through the caller list of your function. It's not easy to get right, safe or portable, but it might do for a while:
/*
* *** Warning ***
*
* Black, fragile and unportable magic ahead
*
* Do not use this, lest the daemons of hell be unleashed upon you
*/
int in_signal_handler_context() {
int i, n;
void *bt[1000];
char **bts = NULL;
n = backtrace(bt, 1000);
bts = backtrace_symbols(bt, n);
for (i = 0; i < n; ++i)
printf("%i - %s\n", i, bts[i]);
/* Have a look at the caller chain */
for (i = 0; i < n; ++i) {
/* Far more checks are needed here to avoid misfires */
if (strstr(bts[i], "(__libc_start_main+") != NULL)
return 0;
if (strstr(bts[i], "libc.so.6(+") != NULL)
return 1;
}
return 0;
}
void unsafe() {
if (in_signal_handler_context())
printf("John, you know you are an idiot, right?\n");
}
In my opinion, it might just be better to quit rather than be forced to write code like this.
You could work out something using sigaltstack. Set up an alternative signal stack, get the stack pointer in some async-safe way, if within the alternative stack go on, otherwise abort().
I guess you need to do the following. This is a complex solution, which combines the best practices not only from coding, but from software engineering as well!
Persuade your boss that naming convention on signal handlers is a good thing. Propose, for example, a Hungarian notation, and tell that it was used in Microsoft with great success.
So, all signal handlers will start with sighnd, like sighndInterrupt.
Your function that detects signal handling context would do the following:
Get the backtrace().
Look if any of the functions in it begin with sighnd.... If it does, then congratulations, you're inside a signal handler!
Otherwise, you're not.
Try to avoid working with Jimmy in the same company. "There can be only one", you know.
for code optimized at -O2 or better (istr) have found need to add -fno-omit-frame-pointer
else gcc will optimize out the stack context information

Kernel Panic after changes in sys_close

I'm doing a course on operating systems and we work in Linux Red Hat 8.0
AS part of an assignment I had to change sys close and sys open. Changes to sys close passed without an incident, but when I introduce the changes to sys close suddenly the OS encounters an error during booting, claiming it cannot mount root fs, and invokes panic. EIP is reportedly at sys close when this happens.
Here are the changes I made (look for the "HW1 additions" comment):
In fs/open.c:
asmlinkage long sys_open(const char * filename, int flags, int mode)
{
char * tmp;
int fd, error;
event_t* new_event;
#if BITS_PER_LONG != 32
flags |= O_LARGEFILE;
#endif
tmp = getname(filename);
fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
fd = get_unused_fd();
if (fd >= 0) {
struct file *f = filp_open(tmp, flags, mode);
error = PTR_ERR(f);
if (IS_ERR(f))
goto out_error;
fd_install(fd, f);
}
/* HW1 additions */
if (current->record_flag==1){
new_event=(event_t*)kmalloc(sizeof(event_t), GFP_KERNEL);
if (!new_event){
new_event->type=Open;
strcpy(new_event->filename, tmp);
file_queue_add(*new_event, current->queue);
}
}
/* End HW1 additions */
out:
putname(tmp);
}
return fd;
out_error:
put_unused_fd(fd);
fd = error;
goto out;
}
asmlinkage long sys_close(unsigned int fd)
{
struct file * filp;
struct files_struct *files = current->files;
event_t* new_event;
char* tmp = files->fd[fd]->f_dentry->d_name.name;
write_lock(&files->file_lock);
if (fd >= files->max_fds)
goto out_unlock;
filp = files->fd[fd];
if (!filp)
goto out_unlock;
files->fd[fd] = NULL;
FD_CLR(fd, files->close_on_exec);
__put_unused_fd(files, fd);
write_unlock(&files->file_lock);
/* HW1 additions */
if(current->record_flag == 1){
new_event=(event_t*)kmalloc(sizeof(event_t), GFP_KERNEL);
if (!new_event){
new_event->type=Close;
strcpy(new_event->filename, tmp);
file_queue_add(*new_event, current->queue);
}
}
/* End HW1 additions */
return filp_close(filp, files);
out_unlock:
write_unlock(&files->file_lock);
return -EBADF;
}
The task_struct defined in schedule.h was changed at the end to include:
unsigned int record_flag; /* when zero: do not record. when one: record. */
file_queue* queue;
And file queue as well as event t are defined in a separate file as follows:
typedef enum {Open, Close} EventType;
typedef struct event_t{
EventType type;
char filename[256];
}event_t;
typedef struct file_quque_t{
event_t queue[101];
int head, tail;
}file_queue;
file queue add works like this:
void file_queue_add(event_t event, file_queue* queue){
queue->queue[queue->head]=event;
queue->head = (queue->head+1) % 101;
if (queue->head==queue->tail){
queue->tail=(queue->tail+1) % 101;
}
}
if (!new_event) {
new_event->type = …
That's equivalent to if (new_event == NULL). I think you mean if (new_event != NULL), which the kernel folks typically write as if (new_event).
Can you please post the stackdump of the error. I don't see a place where queue_info structure is allocated memory. One more thing is you cannot be sure that process record_flag will be always zero if unassigned in kernel, because kernel is a long running program and memory contains garbage.
Its also possible to check the exact location in the function is occurring by looking at the stack trace.

Resources