CS360 Final Exam, May 11, 2010 - Answers and Grading

James S. Plank


Question 1

Although the student's name was not Waluigi, this is indeed an email that I got earlier in the semester. The proper response:
"Dear Waluigi,

Your program calls f(), which prints a line then calls g().  G() in
turn prints a line and calls h().  H() prints a line and then
decrements the counter, whose value will be two, which means that h()
will call longjmp(buf, 4). Unfortunately, setjmp() has never been
called on buf, so the values to which the registers are restored
or undefinied.  Either a bad pc, sp or fp will cause a segmentation
violation.  Your output will be:

Entered function = f
Entered function = g
Entered function = h
Segmentation violation

Love and kisses, 

Dr. Plank"

Grading

6 points: 3 for the output and 3 for the explanation. I wanted something more precise than "longjmp is called before setjmp." You should state the sequence of procedure calls and something about the registers.


Question 2

cproc:
  push #4           // Allocate y

  mv #7 -> %r0   // Push 7 on the stack
  st %r0 -> [sp]--

  ld [fp+12] -> %r0  // Calculate &(x[15])
  mv #60 -> %r1
  add %r0, %r1 -> %r0

  ld [r0] -> %r0     // Dereference it and put it on the stack
  st %r0 -> [sp]--

  jsr b               // Call the subroutine & pop args
  pop #8              

  st %r0 -> [fp]    // Store the return value in y

  ld [fp] -> %r0    // Calculate y+3
  mv #3 -> %r1
  add %r0, %r1 -> %r0   // Make sure the result is in r0
  ret

Grading -- one put for each of my comments above. If you didn't get the entire part, you got zero -- for example, if you called jsr but not "pop 8", you got zero for "Call the subroutine & pop args."

A bunch of you calculated x[15] before pushing 7 on the stack. While that ends up working, it's not the correct order -- arguments are calculated and pushed on the stack in reverse order. If you don't believe me, try:

#include <stdio.h>

main()
{
  int i;
  i = 0;

  printf("%d, %d\n", i++, i++);
}


Question 3

Part A: The bug is that you are calling free() on a pointer that you did not malloc().

Part B: To start with, x is 0x13f40 (or at least that's what I meant -- I had the printf statement before the malloc, which was wrong. I was hoping you'd assume that x started at 0x13f68. If you didn't, you still had to give a logically correct answer). The size of the allocated chunk will be 48 bytes (40 bytes plus 8 for padding), so the value 48 will be put 8 bytes before x, which is at address 0x13f38. The program will write the values 0, 40, 80, etc in each four-byte memory slot, starting with 0x13f40 and ending with 0x13f64. When it's done, x will equal 0x13f68. When free() is called on x, it will look at address 0x13f60 for the size, which will be 320. That means that free() will hook a 320-byte chunk starting at address 0x13f60 onto the free list. The value 320 will be undisturbed, but a free-list pointer will be put at 0x13f64, and potentially another will be put at 0x13f68. Since the chunk is on the free list, there will be a free list pointer pointing to that chunk. Here is the state of memory both before and after the free(x) call:

Part C: Now the nasty part. I can see three potential outcomes.

  1. Nothing: if we don't make any subsequent calls to malloc() or free(), there won't be any problems.

  2. Seg fault: Suppose that chunk was at or near the end of the buffer for malloc. You'll note that we're close to address 0x14000 which will start a new page, and therefore we could be at the end of sbrk(). If the program subsequently calls malloc() and gets that chunk, then writes past address 0x14000 will cause a segmentation violation.

  3. Overlapping chunks: Before the free() call, there was a chunk at address 0x13f68 -- regardless of whether it was free or allocated, we have a problem because there is now a chunk of size 320 starting at 0x13f60. If it gets allocated, we're going to have two pointers to the same region. We can get anything because of this: seg faults, bus errors, strange output. These bugs are very difficult to track down.

Grading


Question 4

SIGPIPE is generated when you write to the write end of a pipe or socket connection which has no read ends open. Here's the easiest program I could think of: sigpipe.c

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

main()
{
  int p[2];
  char c;
 
  pipe(p);
  close(p[0]);
  c = 0;

  while (1) write(p[1], &c, 1);
}

You should have the while() loop because some operating systems will buffer a few write() calls before generating SIGPIPE (see the Signal Lecture Notes for more explanation). Some of you tried to write to a closed file descriptor -- that will not generate SIGPIPE. Intsead, the operating system will return -1 from the write() call and set errno to EBADF: "Bad file descriptor".

Grading

5 points. If you didn't use a while() loop or write something really big, I took off a point.


Question 5

All of the variants of execve do the same thing: they overwrite the current address space with a new program, which they will start executing with main(). Let's simply take execv as the example. Execv() takes two arguments -- an executable program, and a NULL-terminated argv array. It will overwrite the address space so that the code and globals come from the executable file and the heap is empty. The stack is set up so that main() is running with a value of argc derived from the argv array, and with the argv array.

Open files remain open across the execv call.

If successful, execv never returns. Why? Because the address space, in particular the stack, has been overwritten -- there's no way for it to return.

Grading

Eight points. Things you had to mention: Overwriting the address space, code (& globals) from the file, stack set up so that main is running with argc/argv, never returns if successful.


Question 6

You will need to use a mutex, a condition variable, and a counter. At the "Your Code Here: B" location, you'll need to test the counter. If the current thread is not the last thread to reach that part of the code, then it should wait on the condition variable. If it is the last thread, then it should signal all of the others to wake them up:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

typedef struct {
  int id;
  int n;
  pthread_t tid;
  int *ndone;
  pthread_mutex_t *lock;
  pthread_cond_t *cond;
} Thread_Info;

void *thread(void *arg)
{
  Thread_Info *ti;

  ti = (Thread_Info *) arg;
  do_random_stuff(ti);

  pthread_mutex_lock(ti->lock);
  if (*(ti->ndone) < ti->n-1) {
    *(ti->ndone) = *(ti->ndone)+1;
    pthread_cond_wait(ti->cond, ti->lock);
  } else {
    while (*(ti->ndone) >= 0) {
      pthread_cond_signal(ti->cond);
      *(ti->ndone) = *(ti->ndone)-1;
    }
  }
  pthread_mutex_unlock(ti->lock);

  do_more_random_stuff(ti);
}

main(int argc, char **argv)
{
  Thread_Info *ti;
  int i;
  int n;
  pthread_mutex_t m;
  pthread_cond_t c;
  int ndone;

  ndone = 0;
  pthread_mutex_init(&m, NULL);
  pthread_cond_init(&c, NULL);

  n = atoi(argv[1]); 

  for (i = 0; i < n; i++) {
    ti = (Thread_Info *) malloc(sizeof(Thread_Info));
    ti->id = i;
    ti->n = n;
    ti->ndone = &ndone;
    ti->lock = &m;
    ti->cond = &c;

    pthread_create(&(ti->tid), NULL, thread, (void *) ti);
  }

  pthread_exit(NULL);
}

Grading


Question 7

A nuts-and-bolts dup() question. At the time of the first read() call, we have: So: The program has no output to its original standard output. Thus:
UNIX> ./a.out < f4.txt
UNIX> cat f2.txt
12341234
23452345
97531864
UNIX> cat f3.txt
98765432
UNIX> 

Grading

Well, there were four print statements -- you had to have them coming from the right input and going to the right output. Here's how I graded: If you didn't have four lines of output, you lost points.