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.
- Nothing: if we don't make any subsequent calls to malloc() or
free(), there won't be any problems.
- 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.
- 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
- Two points for part A.
- One points for drawing the 0 through 320.
- One point for drawing the 48 to head the chunk returned from malloc.
- One point for showing the free list pointers that have been modified.
- One point for stating that the incorrect block is on the free list.
- One point for stating that this block is 320 bytes.
- One point for saying nothing bad may happen.
- One point for saying that we get a segfault because the chunk may not be in memory.
- One point for saying that we may have weird stuff since we have overlapping chunks on the free list.
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
- Using a lock and CV: 2 points
- Correct initialization: 2 points
- Having the right wait/signal strategy: 4 points
- Using the mutex correctly with the CV: 1 point
Question 7
A nuts-and-bolts dup() question. At the time of the first read() call, we have:
- fd1 and 0 coming from f1.txt.
- fd4 coming from f4.txt.
- fd2 and 1 going to f2.txt.
- fd3 going to f3.txt.
So:
- The first read() comes from f1.txt (12341234) and goes to f2.txt.
- The second read() comes from f1.txt (23452345) and goes to f2.txt.
- The third read() comes from f4.txt (98765432) and goes to f3.txt.
- The scanf() comes from f4.txt, which has been dup()'d back onto
standard input, (97531864) and goes to f2.txt.
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:
- No output from a.out: 1 point
- Three lines to f2.txt: 2 points
- One line to f3.txt: 1 point
- 12341234 is there: 0.5 points
- 23452345 is there: 0.5 points
- 97531864 is there: 0.5 points
- 98765432 is there: 0.5 points
- Everything is correct: 2 more points
If you didn't have four lines of output, you lost points.