COMP 3000 Lab 3 2012

From Soma-notes
Revision as of 15:07, 24 October 2012 by Afry (talk | contribs)

Instructions

In this lab you will learn about execve, monitoring system calls, dynamic libraries. This lab has 30 points in total.

You may use any machine running Ubuntu 12.04 or equivalent. You may need to install software if a given utility is not already installed; other than that, you do not need administrative access to the machine.

You should expect to complete Part A in tutorial. You should submit the answers to both Part A and Part B, however.

Indicate where you got the information to answer each question. This can be as simple as "the TA told me" or instead "I looked at chapter 7 in book X" or, just, "man page for ls". If you did some sort of experiment(s) to determine the answer, outline them briefly. If you do not include such information, you'll automatically have 10 points deducted from your grade.

You should turn in Lab 3 by 10 PM on Friday, October 12 via cuLearn. Your answers should be in plain text (the true UNIX file format) or PDF. No other formats are acceptable (and will result in a zero grade until re-submitted in the correct format). All your code should compile and run. Partial code fragments or explanations will not be sufficient.

Part A

  1. [1] Compile the program hello.c (below) with gcc -O hello.c -o hello-dyn and then run it using the command ltrace ./hello-dyn . What dynamic functions does the program call?
  2. [1] Compile the same program with gcc -O -static hello.c -o hello-static . Run this second binary with ltrace as before. What dynamic functions does the program now call?
  3. [2] Run strace on the static and dynamically compiled versions of hello. How many system calls do they each produce?
  4. [1] How can you make the output of hello.c go to the file "hello-output" by changing how it is invoked at the command line?
  5. [1] How can you make the output of hello.c go to the file "hello-output" by changing its code?
  6. Compile run-program.c with the command gcc -g run-program.c -o run-program and use it to answer the following questions.
    1. [2] Is the line "Is this line printed?" printed when you execute ./run-program /bin/ls -a /tmp? Why?
    2. [2] Change the program to use execve instead of execvp. What is the difference between the two functions?
  7. Linux signals is a simple form of IPC that is used for a variety of purposes.
    1. [2] What signal is generated when a program attempts to divide by zero? Give a simple program that generates such a signal.
    2. [1] How would you send a signal to a process to have it pause execution? Resume?
  8. [1] What command can lower the priority of an already running process?

Part B

  1. [2] Why do we "open" files? Specifically, to what extent is it possible to do file I/O without open and close operations - assuming you could change the UNIX API?
  2. [2] What is the relationship between function calls, system calls, and library calls?
  3. [2] Create run-program-skips.c that is the same as run-program.c except that it ignores every other argument to the program to be run (starting with the first non-program-name argument). Hence, ./run-program-skip /bin/ls ignore -a ignore /tmp will do the same as ./run-program /bin/ls -a /tmp.
  4. [2] Create sanitize-env.c that does the same as run-program.c except it exec's the program with all environment variables stripped except the PATH and TERM variables (assuming they are defined). Be sure to document how you tested your program.
  5. [2] Create a simple program receive-term.c that uses sigaction in order to print "Ouch!" when you send the program the TERM signal and then exits. Note that your program should not consume much CPU time while waiting (i.e., do not poll for the signal). As part of your answer, give the command for sending the signal.
  6. [2] Create a simple program nocrash.c that accesses an invalid pointer but then prints "Oops, my mistake" rather than crash.
  7. [2] Create a program run-program-dots.c that works the same as run-program.c, except that it prints one dot every second while a given program runs. Note that it should stop printing dots once the exec'd program terminates. Your solution should use the fork(), sleep(), execve(), and sigaction() calls.
  8. [1] What is the difference between the PR and NI columns in the top command? Explain in the context of running a CPU-intensive program. (Hint: you should try running a CPU intensive program or two and observe these columns...)

Program Listings

/* hello.c */
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
       pid_t p;

       p = getpid();
      
       printf("Hello, world!  I am process %d.\n", (int) p);
       return 0;
}

/* run-program.c */

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

int main( int argc, char *argv[], char *envp[] ) {

   pid_t p;

   p = getpid();
      
   printf("Hello again.  I am process %d.\n", (int) p);

   if( argc < 2 ) {
       printf( "Insufficient arguments.\n" );
       return -1;
   }
   execvp( argv[1], argv + 1 );
   printf( "Is this line printed?\n" );
   return 0;
}

Answers

  1. __libc_start_main __printf_chk
  2. none (it was compiled statically)
  3. for hello-static:
     
     strace -c ./hello-static 
     Hello, world!  I am process 5504.
     % time     seconds  usecs/call     calls    errors syscall
     ------ ----------- ----------- --------- --------- ----------------
       -nan    0.000000           0         1           write
       -nan    0.000000           0         1           execve
       -nan    0.000000           0         1           getpid
       -nan    0.000000           0         4           brk
       -nan    0.000000           0         1           uname
       -nan    0.000000           0         1           mmap2
       -nan    0.000000           0         1           fstat64
       -nan    0.000000           0         1           set_thread_area
     ------ ----------- ----------- --------- --------- ----------------
     100.00    0.000000                    11           total
     
    strace -c ./hello-dyn
    Hello, world!  I am process 5507.
    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
      -nan    0.000000           0         1           read
      -nan    0.000000           0         1           write
      -nan    0.000000           0         2           open
      -nan    0.000000           0         2           close
      -nan    0.000000           0         1           execve
      -nan    0.000000           0         1           getpid
      -nan    0.000000           0         3         3 access
      -nan    0.000000           0         1           brk
      -nan    0.000000           0         1           munmap
      -nan    0.000000           0         3           mprotect
      -nan    0.000000           0         7           mmap2
      -nan    0.000000           0         3           fstat64
      -nan    0.000000           0         1           set_thread_area
    ------ ----------- ----------- --------- --------- ----------------
    100.00    0.000000                    27         3 total
    
    
  4. ./hello-static > hello-output
  5. /* hello-modified-to-output-to-file.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { FILE *fp; pid_t p; p = getpid(); /* File open Error checking code */ if ((fp=fopen("hello-output", "w")) == NULL) { printf("Cannot open file.\n"); exit(1); } fprintf(fp,"Hello, world! I am process %d.\n", (int) p); return 0; }
    1. No, because when execvp is called the program file given by the first argument will be loaded into the caller (process)'s address space and overwrite the program there. the original program in the caller's address space is gone and is replaced by the new program. Like all of the exec functions, execvp replaces the calling process image with a new process image. This has the effect of running a new program with the process ID of the calling process. Note that a new process is not started; the new process image simply overlays the original process image. The execvp function is most commonly used to overlay a process image that has been created by a call to the fork function. A successful call to execvp does not have a return value because the new process image overlays the calling process image. However, a -1 is returned if the call to execvp is unsuccessful.
    2. /* run-program.c */ #include <unistd.h> #include <stdio.h> int main( int argc, char *argv[], char *envp[] ) { pid_t p; p = getpid(); printf("Hello again. I am process %d.\n", (int) p); if( argc < 3 ) { printf( "Insufficient arguments.\n" ); return -1; } execve( argv[1], argv + 1, envp); printf( "Is this line printed?\n" ); return 0; }
    1. runtime error - Floating point exception (core dumped) #include <stdio.h> #include <stdlib.h> int main( void ) { int dividend = 50; int divisor = 0; int quotient; quotient = dividend / divisor; }
    2. to pause SIGSTOP (ctrl-s) or SIGTSTP (ctrl-z) to resume SIGCONT or fg kill -SIGSTOP [pid] kill -SIGCONT [pid]
  6. renice {priority} pid Users can only change the nice value of processes which they own. User cannot start processes with nice values less than 20. User cannot lower the nice values of their processes after they've raised them. As usual root has full access to renice command.
  7. Part B

    1. We open files to prevent multiple programs from accessing the same file at the same time and corrupting the input to one program, if the other program happens to change the file contents.

    tell() returns where the cursor is in the file. It has no parameters, just type it in (like what the example below will show). This is infinitely useful, for knowing what you are refering to, where it is, and simple control of the cursor. To use it, type fileobjectname.tell() - where fileobjectname is the name of the file object you created when you opened the file (in openfile = open('pathtofile', 'r') the file object name is openfile). readline() reads from where the cursor is till the end of the line. Remember that the end of the line isn't the edge of your screen - the line ends when you press enter to create a new line. This is useful for things like reading a log of events, or going through something progressively to process it. There are no parameters you have to pass to readline(), though you can optionally tell it the maximum number of bytes/letters to read by putting a number in the brackets. Use it with fileobjectname.readline(). readlines() is much like readline(), however readlines() reads all the lines from the cursor onwards, and returns a list, with each list element holding a line of code. Use it with fileobjectname.readlines().

    (Reference: http://www.sthurlow.com/python/lesson10/)

    1. Functions - part of the standard C library are known as library functions.

    Examples include standard string manipulation functions like strlen(). Those functions that change the execution mode of the program from user mode to kernel mode are system calls. These functions require some service from the kernel itself. They act as an entry point to the OS kernel. An example of this could be hardware interaction (wireless driver, sound driver). malloc() is not a system call. The function call malloc() is a library function call that further uses the brk() or sbrk() system call for memory allocation.

    (Reference: http://www.thegeekstuff.com/2012/07/system-calls-library-functions/)


    /* run-program-skip.c */
    
    #include <errno.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>  
    
    int main( int argc, char **argv, char *envp[] ) {
            char **newargs;
    	int newargc;
    	int lessargs = 0;
            int i = 0;
    	int count = 1;
    	int j = 0;
    	int k = 0;
    
      if( argc < 2 ) {
          printf( "Insufficient arguments.\n" );
          return -1;
      }
      for (j=1;j < argc;j++)
      {
    	if (j % 2 == 0)
    	{
    	}
    	else
    	{
    		lessargs++;
    	}
      }
      	newargc =  argc-lessargs;
    	newargc++;
            newargs = (char**)malloc(newargc*sizeof(char**));
    	newargs[0] = (char*)malloc(sizeof(argv[0])*strlen(argv[0])); 
    	strcpy(newargs[0],argv[0]);
    	printf("%s\n\n",newargs[0]);
    
    //	printf("lessargs: %d \nold arguments list number: %d \nnew arguments list number: %d \noriginal program argument:  %s\n", lessargs,argc, newargc, newargs[0]);
            for(i = 1; i  < argc; i++)
            {
    		if (i % 2 == 0)
    		{
    //		printf("removed %s", argv[i]);		
    		}
    		else
    		{
    //		printf("%s",argv[i]);
    		newargs[count] = (char*)malloc(sizeof(argv[i])*strlen(argv[i]));
                    strcpy(newargs[count],argv[i]);
    		count++;
    		}
    	}
    
            
    	for (k=0; k<newargc;k++)
    	{
    		//printf("%s",newargs[k]);
    	}
    //        newargs[argc-1] = NULL;
            if(execvp(newargs[0],newargs + 1) == -1)
    	{
                    printf("error! = %s",strerror(errno));
      	}
            printf( "Is this line printed?\n" );
      return 0;
    }
    

    /* sanitize-env.c */
    
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>  
    
    int main( int argc, char *argv[], char *envp[] ) {
            extern char **environ;
            int i = 0;   
    
            char *path = getenv("PATH");
            char *term = getenv("TERM");
            if(clearenv())
                    exit(-1);
            setenv("PATH",path,1);
            setenv("TERM",term,1);  
    
            //Check now.
            while(environ[i] != NULL) {
                    printf("environ[%d] = %s\n",i,environ[i++]); 
            } 
    
      if( argc < 2 ) {
          printf( "Insufficient arguments.\n" );
          return -1;
      }
      execvp(argv[1], argv + 1);
      printf( "Is this line printed?\n" );
      return 0;
    }
    

    /* receive-term.c */
    
    #include <sys/wait.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h> 
    
    int child;
    void sig_handler(int signum)
    {
            if(signum == SIGTERM)
                   printf("Ouch!.\n");
       fflush(stdout);
    }
    
    int main()
    {
     struct sigaction act;
           sigset_t set;
     
     act.sa_handler = sig_handler;
     act.sa_flags = 0;
     sigemptyset(&act.sa_mask); 
    
     sigaction(SIGTERM, &act, NULL);
     sigfillset(&set);//Block on all signals
     sigdelset(&set,SIGTERM);//except this one
     sigsuspend(&set);//now wait.
     return 0;  
    }
    

    /* receive-term.c */
    
    #include <sys/wait.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h> 
    
    int child;
    void sig_handler(int signum)
    {
            if(signum == SIGTERM)
                   printf("Ouch!.\n");
      fflush(stdout);
    }
    
    int main()
    {
     struct sigaction act;
           sigset_t set;
     
     act.sa_handler = sig_handler;
     act.sa_flags = 0;
     sigemptyset(&act.sa_mask); 
    
     sigaction(SIGTERM, &act, NULL);
     sigfillset(&set);//Block on all signals
     sigdelset(&set,SIGTERM);//except this one
     sigsuspend(&set);//now wait.
     return 0;  
    }
    

    /* run-program-dots.c */
    
    #include <sys/wait.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <signal.h>
    
    int child;
    void sigchild_handler(int signum)
    {
           exit(0); 
    }
    
    int main()
    {
      struct sigaction act;
    
      act.sa_handler = sigchild_handler;
      act.sa_flags = 0;
      sigemptyset(&act.sa_mask);
    
      child = fork();
    
     if(child == 0) {
            execve("program",NULL,NULL);
       return 0;
     }
     else {
       sigaction(SIGCHLD, &act, NULL); 
            while(1)
            {
                   printf(".");
                   fflush(stdout);
                   sleep(1);
            } 
       return 0;  
     }
    }
    

    where my program.c is

    #include<stdlib.h>
    int main()
    {
            sleep(8);
            return 0;
    }
    

  8. NI is the nice value of a process, which can be changed by the user. It ranges from -20 to 19 and is used as an input to the scheduler. Depending on all other processes and available resources, the scheduler decides the priority of a process and displays its calculated value in the PR column.