Instrumenting Programs with Watches

Sometimes accesses to particular memory locations are of interest when instrumenting a program. For example, if we have a global data structure such as a scheduling queue, we might be interested in monitoring its activity. Or, if the program allocates buffers that may be modified through pointers in many parts of the program, it is more straightforward to instrument the buffer's memory locations rather than the code that might access the buffer through pointers.


Watch Example: The Stray Update

Watches are inserted at locations in the memory of an executing program, and implement an interface simulator to that implemented by a probe; the Simulator.Watch interface. This interface is nested inside the Simulator class and looks like this:

package avrora.sim;

public class Simulator {
    ...
    public interface Watch {
        public void fireBeforeRead(State state, int data_addr);
        public void fireBeforeWrite(State state, int data_addr, byte value);
        public void fireAfterRead(State state, int data_addr, byte value);
        public void fireAfterWrite(State state, int data_addr, byte value);
    }

    public void insertWatch(Watch p, int data_addr);
    public void removeWatch(Watch p, int data_addr);
}

Suppose we are trying to track down a bug in our program where a variable is getting set to an incorrect value, but we aren't sure when it is happening, or where in the code. We saw how in the previous section that we can use probes in the program to insert instrumentation code in various places in the code. But this time, we aren't sure which part of the code might be the culprit--it might be a stray pointer, it might be a dark corner of the code someone else wrote. We can't simply insert probes into all possible locations in the code, because that would be extremely tedious, and we can't simply dump every memory access, because we'd have to wade through the output looking for writes to the variable we are interested in. What we need is a watch.

We can write a new Java class that implements the interface and inside its fireBeforeWrite() method, we have access to the instruction doing the update and its address, as well as the value being written. If it is an incorrect or invalid value, we can print a message, log it, and continue. The simulator doesn't lie--all updates to that memory location will go through the watch, and we can catch the offender red-handed!


package demo;

import avrora.sim.Simulator;
import avrora.sim.State;
import avrora.core.Instr;
import avrora.util.Terminal;

public class StrayUpdateWatch extends Simulator.Watch.Empty {

    public void fireBeforeWrite(State state, int data_addr, byte value) {
        // RING ALARM BELLS
        Terminal.println("Write to "+data_addr+" from "+address);
    }
}

Just as probes, watches can be as simple or as complex as your application needs. Watches can enable and disable other watches, probes, or events. For example, perhaps we have a global variable that controls the overall state of our program, and we only want to profile the program when it is in one of the states. We can insert a watch on that variable, and when that variable is written to the state that we are interested in, we can enable the rest of the profiling probes and watches. When the variable is written back to another state, we can disable the instrumentation code.

More information:





[ Home | Download | Get Started | Online CVS | JavaDoc API ]


Copyright (c) 2004-2005, UCLA Compilers Group
The Avrora logo background is Copyright (c) 1996 Jan Curtis, used with permission.