Go4Expert

Go4Expert (http://www.go4expert.com/)
-   C (http://www.go4expert.com/articles/c-tutorials/)
-   -   Nonreentrant Functions (http://www.go4expert.com/articles/nonreentrant-functions-t26871/)

poornaMoksha 8Oct2011 16:32

Nonreentrant Functions
 
Before we start with Non re-entrant Functions, Can you guess the problem with this piece of code :

Code:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>

int flag;
char *s;


void func()
{
    char *ptr = realloc(s,10);

    if(ptr)
        s = ptr;
}

void sig_handler(int signo)
{
  flag = 1;
  func();
  if (signo == SIGUSR1)
      printf("received SIGUSR1\n");
  else if (signo == SIGUSR2)
      printf("received SIGUSR2\n");
  else
      printf("ERR : received signal %d\n", signo);
 
}


int main(void)
{
    if (signal(SIGUSR1, sig_handler) == SIG_ERR)
        printf("\ncan't catch SIGUSR1\n");
    if (signal(SIGUSR2, sig_handler) == SIG_ERR)
        printf("\ncan't catch SIGUSR2\n");
    int i = 0;

    while(i<10)  // Simulate a dummy wait
    {
        func();
        i++;
    }

    free(s);
    return 0;
}

Its a bonus 10 points :-) for any one who guessed it. Bonus points because pointing a flaw in this function requires understanding on realloc function and knowledge about non-re-entrant functions.

For those who still don't know lets discuss it :

realloc() : This function changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged to the minimum of the
old and new sizes; newly allocated memory will be uninitialized. If ptr is NULL, then the call is equivalent to malloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then the call is equivalent to free(ptr). Unless ptr is NULL, it must have been returned by an earlier call to malloc(), calloc() or realloc(). If the area pointed to was moved, a free(ptr) is done.

realloc() returns a pointer to the newly allocated memory, which is suitably aligned for any kind of variable and may be different from ptr, or NULL if the request fails. If size was equal to 0, either NULL or a pointer suitable to be passed to free() is returned. If realloc() fails the original block is left untouched; it is not freed or moved.

Non-ReEntrant Functions : In a layman's language, those functions whose results become undetermined when called again from same context while already
being executed. Like for example, there is a function that operates on some global values multiple times in a function. Lets say this function was being executed in a process. Now, if due to some interrupt, the same function gets called (from signal handler function) then due to the fact that the previous function execution of the same function was in between and suddenly due to call from signal handler, this function is called again then the global values on which it was operating could have been in undetermined state and the call from signal handler could result the function operating on undesired global values. A function which does not take care of this kind of scenario is known as non-reentrant function.

So, coming back to the piece of code posted above. The function func() is non-reentrant. No bonuses this time :-) for guessing. Its simple, because suppose this function was being called from main() and the flow was between realloc() function call and the statement next to it (which initializes the global pointer 's' with new address returned from realloc() ). Now, lets say at this very time a signal USR1 occurred which got caught and the same function func() got called again. Now since 's' was not updated in the previous call context from main() (that got interrupted). So, calling realloc with same 's' would cause realloc to fail (or some undetermined results) and hence the problem with this code.

Its a bit difficult to reproduce this problem by running this code as one would have to generate signal USR1 manually while code would run fast and the process would end. So, I have tweaked this code a bit so that one can reproduce the same problem easily. Here is the piece of code :

Code:

#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>

int flag;
char *s;


void func()
{
    printf("\n Calling realloc \n");
    char *ptr = realloc(s,10);
    printf("\n realloc call done, freeing global ptr\n");
    free(s);      // realloc done, free the current global ptr
 
    if(flag == 0)
    {
        unsigned int i = 0;
        for(;i< (0xFFFFFFFF);i++); //consume time so that user can generate USR1 or USR2 signal from command line
    }
    if(ptr)
    {
        *ptr = 'a';
        printf("\n Updating global ptr\n");
        s = ptr; // Update the global ptr with new value
    }
}

void sig_handler(int signo)
{
  flag = 1;
  if (signo == SIGUSR1)
      printf("received SIGUSR1\n");
  else if (signo == SIGUSR2)
      printf("received SIGUSR2\n");
  else
      printf("ERR : received signal %d\n", signo);
 
  func();
}


int main(void)
{
    if (signal(SIGUSR1, sig_handler) == SIG_ERR)
        printf("\ncan't catch SIGUSR1\n");
    if (signal(SIGUSR2, sig_handler) == SIG_ERR)
        printf("\ncan't catch SIGUSR2\n");
    int i = 0;

    while(i<10)  // Simulate a dummy wait
    {
        func();
        i++;
    }

    free(s);
    return 0;
}

In the above piece, suppose if a signal USR1 or USR2 is generated in between, when 's' was freed and was yet to be updated with new value. The new call to this function would try to use the value of 's' that has already been freed and hence there would definitely be a crash or seg-fault.

All you need to do is :
  1. Run the above piece of code
    Code:

    ./<name of binary>
  2. Find the PID of this process
    Code:

    ps -aef | grep <name of binary>
  3. Kill it using USR1 signal
    Code:

    kill USR1 <pid>
On my machine I got output like :

Code:

$ ./signal

 Calling realloc

 realloc call done, freeing global ptr
received SIGUSR1

 Calling realloc

 realloc call done, freeing global ptr

 Updating global ptr

 Updating global ptr

 Calling realloc

 realloc call done, freeing global ptr

 Updating global ptr

 Calling realloc

 realloc call done, freeing global ptr
*** glibc detected *** ./signal: double free or corruption (fasttop): 0x00000000018cb010 ***
======= Backtrace: =========
/lib/libc.so.6(+0x775b6)[0x7ff3ced7f5b6]
/lib/libc.so.6(cfree+0x73)[0x7ff3ced85e83]
./signal[0x400697]
./signal[0x40078e]
/lib/libc.so.6(__libc_start_main+0xfd)[0x7ff3ced26c4d]
./signal[0x400599]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:05 1445488                            /home/himanshu/practice/signal
00600000-00601000 r--p 00000000 08:05 1445488                            /home/himanshu/practice/signal
00601000-00602000 rw-p 00001000 08:05 1445488                            /home/himanshu/practice/signal
018cb000-018ec000 rw-p 00000000 00:00 0                                  [heap]
7ff3c8000000-7ff3c8021000 rw-p 00000000 00:00 0
7ff3c8021000-7ff3cc000000 ---p 00000000 00:00 0
7ff3ceaf1000-7ff3ceb07000 r-xp 00000000 08:05 262224                    /lib/libgcc_s.so.1
7ff3ceb07000-7ff3ced06000 ---p 00016000 08:05 262224                    /lib/libgcc_s.so.1
7ff3ced06000-7ff3ced07000 r--p 00015000 08:05 262224                    /lib/libgcc_s.so.1
7ff3ced07000-7ff3ced08000 rw-p 00016000 08:05 262224                    /lib/libgcc_s.so.1
7ff3ced08000-7ff3cee82000 r-xp 00000000 08:05 265935                    /lib/libc-2.11.1.so
7ff3cee82000-7ff3cf081000 ---p 0017a000 08:05 265935                    /lib/libc-2.11.1.so
7ff3cf081000-7ff3cf085000 r--p 00179000 08:05 265935                    /lib/libc-2.11.1.so
7ff3cf085000-7ff3cf086000 rw-p 0017d000 08:05 265935                    /lib/libc-2.11.1.so
7ff3cf086000-7ff3cf08b000 rw-p 00000000 00:00 0
7ff3cf08b000-7ff3cf0ab000 r-xp 00000000 08:05 265932                    /lib/ld-2.11.1.so
7ff3cf288000-7ff3cf28b000 rw-p 00000000 00:00 0
7ff3cf2a7000-7ff3cf2aa000 rw-p 00000000 00:00 0
7ff3cf2aa000-7ff3cf2ab000 r--p 0001f000 08:05 265932                    /lib/ld-2.11.1.so
7ff3cf2ab000-7ff3cf2ac000 rw-p 00020000 08:05 265932                    /lib/ld-2.11.1.so
7ff3cf2ac000-7ff3cf2ad000 rw-p 00000000 00:00 0
7fff5aba6000-7fff5abbb000 rw-p 00000000 00:00 0                          [stack]
7fff5abff000-7fff5ac00000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Aborted

Another thing to be taken care of in these kind of situations is the value of 'errno' variable. As it is a global variable which is updated every time an error is thrown by system function. So, programmers should take this thing in mind that any failure in signal handler may change its value. So, value of 'errno' variable must always be carefully used in case we are dealing with signal handlers in our program.

Conclusion



To conclude, one must be extremely careful about the common functions being called by the signal handlers and the rest of the program. These common functions should be re-entrant else it could cause havoc to the program when it is run. Also, the same care must be taken for the 'errno' variable if we are using system functions which commit to change its value in case of some error.

lionaneesh 8Oct2011 18:15

Re: Nonreentrant Functions
 
YaY! I got those 10 Points this time! Thanks for the tutorial..

Warhammer 22Oct2011 07:04

Re: Nonreentrant Functions
 
Thank you for sharing this.:happy:


All times are GMT +5.5. The time now is 15:42.