Stanford University Computer Science Department CS 295 midterm May 14, 2008 This is an open-book exam. You have 75 minutes. Write all of your answers directly on the paper. Make your answers as concise as possible. Sentence fragments ok. NOTE: We will take off points if a correct answer also includes incorrect or irrelevant information. (I.e., don t put in everything you know in hopes of saying the correct buzzword.) Question 1-11 (45 points) 12-13 (30 points) total Score Stanford University Honor Code In accordance with both the letter and the spirit of the Honor Code, I did not cheat on this exam nor will I assist someone else cheating. Name and Stanford ID: Signature: 1
Answer 9 of the following 11 questions and, in a sentence or two, say why your answer holds. (5 points each). 1. Gabriel uses pc-lusering as an example of how worse is better can triumph even when it makes an interface more complex. Taking his explanation of how Unix works at face value: explain how a user-level library could make the Unix interface into the Right Thing. Just have the library check the system call result and retry on error. 2. You see a DD graph (in the style of those given in the two papers) with a long horizontal line: what is going on? Inconsistency. Horizontal = no progress, which is because inconsistent results give us no real information about what change led to an error. 3. My qsort does not work. I try to run a DD variant on it to find the problem. Why do we expect this to be a stupid thing to do in general? Give an example error in my qsort that DD could potentially isolate. If my qsort is broken, then removing lines will in general not fix it. If my qsort was originally working and then had some line added that broke it, then one of the DD s should find it since this is exactly the case they handle (working program + broken change). 4. Consider Table 2 in yesterday my program worked... : is it possible that testing 3 6 produces an OK rather than X? Yes: the intersection of the failing cases is 3 5 6, so we do not know in fact if 3 6 will fail or if 5 is necessary. 5. Give two ways that the statistical leak paper improves on Purify, including one error that it will find that Purify will miss. Three ways: (1) speed, (2) error reporting, (3) more bugs. One bug it will find: memory allocated and still reachable, but unused for a long time. 6. Does the statistical leak paper instrument every malloc call, or does it sample them? What would happen if it did the opposite? It instruments all malloc calls. Otherwise it could miss leaks if the allocation happened during sampling. 7. The Therac code in Figure 4 on page 34 shows an overflow error involving the Class3 variable. Ironically, while illustrating the difficulty in doing correct concurrency, the authors made a mistake in their pseudo-code, introducing an unintentional race condition between Set-up Test and Lmtchk. Describe it. (Note: this error is not the overflow error.) Set-up Test depends on Lmtchk running between the increment of Class3 and the check of f$mal, but there is nothing in the code to enforce this. 8. Explain why Eraser flags the use of kill queries in Alta Vista as an error. Give a possible sequence of thread accesses to kill queries for this error, and the exact 2
state transition sequence that Eraser follows in this case. Initialized by one thread (exclusive), read by many (shared), written by first thread (error). 9. The Eraser paper claims in Section 2.2 that a write access from a new thread changes the state from Exclusive or Shared to the Shared-Modified state... But Figure 4 says that a write by any thread in the Shared state takes it to the Shared-Modified state. Which is right? The figure is right. Looking at the later description of the implementation, any write will take it to shared-modified. Once it is shared it is running the lockset algorithm without giving warnings, which means that the per-variable shadow area contains the lockset pointer, so it can no longer be keeping track of the thread number of the original writer. We can also reason from what it should do. If anyone is writing into a variable that at least one other thread has been reading from, we have a possibility of a race, so we had better we raising alerts if the locking protocol is violated. (a legalistic reading of the text can claim that it is technically accurate; it is true that a write access from a new thread in the Shared state does take it to the Shared-Modified state; they just didn t bother to mention that a write access from the old thread in the Shared state also takes the variable to the Shared-Modified state. Under that interpretation the sin is that the authors forgot to mention one important case.) 10. Say concretely what will happen when we compile the following code with the failure oblivious compiler and run it: void foo(void) { char dst[4]; const char src[3] = { x, y, z }; strcpy(dst, src); printf("dst=<%s>\n", dst); } Also: Does this code satisfy or violate the requirements that they state are needed for failure oblivious to work well? strcpy copies until you hit a 0 byte. In this example there is garbage after the third character of src (which b/c it is a character array is not null terminated) so could overrun dst. The Rinard compiler will copy the three characters in src and then make up a value for the 4th. Since the first value it returns is 0, this will make dst nullterminated and also make strcpy terminate. They want code to have short error propagation distances with irrelevant results this code is a best case, other than the user might act on the output. 11. You rip out the object tracking code in the failure oblivious system and replace it with the statistical leak paper s object lookup code (which uses an address tree to determine what object, if any, a pointer is contained within). Will failure oblivious likely work better, worse, or no different? (Make sure you state why!) Will work worse. The failure-oblivious compiler tracks when a pointer goes out of 3
bounds from the object it was intended to point to even if the pointer then points to a different (wrong) object. The stat leak object tracking will not do this, so will allow garbage writes to occur that the failure oblivious compiler will block. E.g.,: p = malloc(10); q = malloc(10); p += 10000; // goes out of bounds, but assume now points to q *p = x; // since this points to valid object, write succeeds. Here the pointer p goes out of bounds far enough to point to the object associated w/ q. The write succeeds, when it should not. 4
(15 points) Problem 12: Purify You have the following pieces of code. For each, say whether Purify will (1) always catch the error, (2) might catch it, or (3) will miss it. Make sure to justify your answer. 1. Overflow: int *p = malloc(100); p[1000] = 5; May catch the error: if the pointer goes beyond the end of the red zone inserted by purify, will miss it. 2. Overflow(s): struct foo { int x[10]; int bar; }; struct foo *x = malloc(sizeof *x); x->x[11] = 1; The question has an (unintentional) off-by-one error, so we accepted both: will miss and will catch, as long as the reason given was sensible. For will miss: it does not know the internal layout of structures, so does not know the write goes past the end of the x array. For will catch: the array reference x[11] will extend past the end of the structure so purify will catch it. 3. Uninitialized: struct foo { unsigned x:1; unsigned bar:1; }; unsigned bar(void) { struct foo f; f.bar = 1; return f.x; } Misses this error: does not catch unitialized bitfields. The first write will initialize the whole word, so does not know that the second read is not valid. 4. Overflow: void foo(void) { char buf[10]; int x; memset(buf, \0, 14);... 5
Assuming the variables are allocated on the stack in this order (and it grows down) stack purify will miss this error since it does not know internal stack variable layout. We also took the answer that purify might catch the error in the case that buf is the last variable on the stack, since purify should catch a write into another activation record. 6
(15 points) Problem 13: Atom You decide to use Atom to detect when memory allocated with malloc is read without being initialized. Write the Instrument routine and pseudo-code for the analysis routines it will insert calls to. Make sure you say what you are trying to do rather than just give bare code! You can use pseudo-code for your own code, but the Atom calls should be roughly compilable C code. Main idea: 1. Grab every malloc call and allocate shadow memory that you set to uninitialized. 2. On every store, lookup address and if have shadow object mark written memory as initialized. 3. On every load, lookup addr and if have shadow object and its uninitialized complain. Have to grab all loads and stores, instrument all malloc calls (or malloc itself). The main points taken off were because of not showing how exactly to get the malloc address or looking for malloc instructions rather than function calls. 7