Annotation of researchv10dc/vol2/pi/obj.ms, revision 1.1.1.1

1.1       root        1: .so ../ADM/mac
                      2: .XX 24 397 "Pi: A Case Study in Object-Oriented Programming"
                      3: ...\" Wed May 21 16:37:07 EDT 1986
                      4: .de CW
                      5: \%\&\\$3\\f(CW\\$1\\fP\&\\$2
                      6: ..
                      7: ..
                      8: .TL
                      9: Pi: A Case Study in Object-Oriented Programming
                     10: .AU
                     11: T. A. Cargill
                     12: .MH
                     13: .AB
                     14: Pi is a debugger written in C++.
                     15: This paper explains how object-oriented programming in C++ has influenced Pi's
                     16: evolution.
                     17: The motivation for object-oriented programming was to experiment with a
                     18: browser-like graphical user interface.
                     19: The first unforeseen benefit was in the symbol table: lazy construction of an
                     20: abstract syntax-based tree gave a clean interface to the remainder of Pi,
                     21: with an efficient and robust implementation.
                     22: Next, though not in the original design, Pi was easily modified to control
                     23: multiple processes simultaneously.
                     24: Finally, Pi was extended to control processes
                     25: executing across multiple heterogeneous target processors.
                     26: .AE
                     27: .NH
                     28: Introduction
                     29: .PP
                     30: The subject of this paper is the impact of object-oriented programming on the
                     31: structure and capabilities of a complicated piece of software \- a debugger
                     32: called Pi.
                     33: The paper begins with observations about debugging in general and a description
                     34: of Pi, to motivate the introduction of object-oriented programming.
                     35: When the design of Pi began, object-oriented programming was chosen as
                     36: a means of achieving a style of user interface.
                     37: But the application of object-oriented techniques throughout had unforeseen
                     38: benefits with respect to symbol table structure, multi-process debugging and
                     39: target environment independence.
                     40: .NH
                     41: User Interfaces for Debugging
                     42: .PP
                     43: A debugger must provide many views of its subject.
                     44: The primary view of the subject program is static: the source text.
                     45: The primary view of the subject process is dynamic: the callstack, a sequence of
                     46: activation records.
                     47: The programmer's insight into the program's behavior comes from merging these
                     48: views: examining data within the process while controlling its progress through
                     49: the source text.
                     50: Debugging purely at the source level may be sufficient for a safe language,
                     51: correctly implemented, but in practice we also need views of the implementation.
                     52: Even if none of the subject is written in assembler, it is vital from time
                     53: to time to consider the process in terms of the instruction stream, registers
                     54: and uninterpreted memory.
                     55: .PP
                     56: A particular view is not used in isolation; it is related to other views.
                     57: The programmer moves rapidly among a set of related views.
                     58: This diversity complicates the user interface.
                     59: Any attempt to base an interface on a coherent model is confounded by the user's
                     60: need to switch among so many projections of the underlying subject.
                     61: For example, the keyboard input
                     62: .P1 0
                     63: 100
                     64: .P2
                     65: could mean ``display the context of line 100'' of the current source file.
                     66: It could also mean ``evaluate the expression 100'' or ``display the value of
                     67: the memory cell at location 100'' and so on.
                     68: There is no natural interpretation; it depends on the programmer's current focus.
                     69: If there is some simple model on which a user interface for such a
                     70: (necessarily) powerful tool could be based, no one has yet found it.
                     71: .PP
                     72: Switching among a set of input modes might help \- as long as there is no
                     73: confusion over what mode is current and how one changes mode.
                     74: However, experience with just two modes in text editors
                     75: suggests that a larger number of modes would not work.
                     76: On the other hand, modeless keyboard languages for debuggers tend to need many
                     77: qualifiers and options for each command, varying from verbose to cryptic and
                     78: from pedantic to treacherous.
                     79: But if they are useful, they are large and complicated.
                     80: Two recent debuggers use programmability of the user interface to permit the
                     81: custom definition of keyboard languages for different classes of users.
                     82: Kraut|reference(bruegge) (with ``path expressions'')
                     83: and Dbx|reference(bsdmanual) (with macros) let
                     84: new commands be defined in terms of a base language.
                     85: In contrast, Pi's user interface uses multi-window bitmap graphics, and cannot
                     86: be extended by the user.
                     87: .NH
                     88: Pi's User Interface
                     89: .PP
                     90: Pi is primarily an attempt to combine expressive power and ease of use in a
                     91: graphics interface.
                     92: The user browses through a network of views, each in its
                     93: own window with a specialized pop-up command menu and keyboard language.
                     94: The user selects a window by pointing at it with a mouse or by following a
                     95: menu or keyboard connection from a related window.
                     96: All the information within a window is textual, i.e., symbolic and numeric rather
                     97: than analog or geometric.
                     98: Moreover, each line of text is also part of the browsing network;
                     99: it defines its own menu and keyboard interface.
                    100: .PP
                    101: The details of the graphics are not essential, but will help make the discussion
                    102: more concrete.
                    103: The display has 100 monochrome pixels per inch.
                    104: Within Pi, windows may overlap and are positioned explicitly by the user.
                    105: A proportional scroll bar on the left shows how much of a window's text is
                    106: visible.
                    107: The current window has a thick border; the current line is video-inverted.
                    108: A three-button mouse is used.
                    109: The left button makes selections: to change
                    110: the current window, to scroll within it or to select a line.
                    111: The middle and right buttons raise the pop-up menus associated with the current
                    112: line and window, respectively.
                    113: A line of accumulated keyboard characters is displayed in a common space
                    114: below the other windows.
                    115: If the current line or window accepts keyboard input, its border
                    116: flashes at each keystroke.
                    117: At carriage return a complete line of input is sent to the current recipient.
                    118: .PP
                    119: The following example shows much of the user interface mechanics, but
                    120: only a few of Pi's features.
                    121: A more complete example appears in |reference(pi feel).
                    122: This trivial C program is taken from |reference(cbook):
                    123: .P1 0
                    124: #include <stdio.h>
                    125: 
                    126: main()    /* count lines in input */
                    127: {
                    128:     int c, nl;
                    129: 
                    130:     nl = 0;
                    131:     while ((c = getchar()) != EOF)
                    132:         if (c == '\en')
                    133:             ++nl;
                    134:     printf("%d\en", nl);
                    135: }
                    136: .P2
                    137: Assume this program has been compiled and invoked by a command interpreter
                    138: running elsewhere on the screen.
                    139: The process reads from the keyboard of its virtual terminal.
                    140: To bind Pi and the process, the user selects the process from a
                    141: list of accessible processes in Pi's master window.
                    142: To control this subject process Pi creates a Process window, for which the
                    143: user must sweep a rectangle with the mouse.
                    144: The Process window identifies the process and shows its state, as in Figure 1.
                    145: Process 27775 is in a
                    146: .CW read
                    147: system call, waiting for keyboard input for the call to
                    148: .CW getchar() .
                    149: .KF
                    150: .so fig1.so
                    151: .ce
                    152: Figure 1. A Process window and its menu.
                    153: .SP 
                    154: .KE
                    155: .PP
                    156: The Process window is the hub of a network of views of
                    157: the process.
                    158: Choosing
                    159: .CW src\ text ' `
                    160: from the Process window's pop-up menu creates a Source Text window for
                    161: the source file,
                    162: .CW count.c ,
                    163: shown in Figure 2.
                    164: .LP
                    165: A breakpoint is set by pointing at a source line
                    166: and choosing
                    167: .CW set\ bpt ' `
                    168: from the line's menu.
                    169: The presence of the breakpoint is recorded by
                    170: .CW >>>
                    171: at the left of the line (Figure 3).
                    172: .PP
                    173: The subject process reaches the breakpoint when the user supplies some keyboard
                    174: input and
                    175: .CW getchar()
                    176: returns a character.
                    177: The Process window announces the change of state and displays a callstack
                    178: traceback, in this case a degenerate stack of depth 1.
                    179: Pi shows the source text context by making the Source Text window current
                    180: and selecting the source line at which the program stopped.
                    181: Choosing
                    182: .CW open\ frame ' `
                    183: from the source line's menu creates a Frame window for evaluating expressions
                    184: with respect to the activation record associated with that source line.
                    185: .KF bottom
                    186: .so fig2.so
                    187: .ce
                    188: Figure 2. A Source Text window and a source line's menu.
                    189: .SP
                    190: .KE
                    191: .KF
                    192: .so fig3.so
                    193: .ce
                    194: Figure 3. A Frame window and an expression's menu at a breakpoint.
                    195: .SP
                    196: .KE
                    197: .PP
                    198: Expressions in a Frame window are built from menus or the keyboard.
                    199: Choosing an identifier, say
                    200: .CW c ', `
                    201: from the Frame's menu of local variables creates and evaluates a simple expression,
                    202: shown in Figure 3.
                    203: The variable's type determines the default format in which the value is
                    204: displayed: an integer in decimal.
                    205: It makes more sense to display
                    206: .CW c 's
                    207: value as an ASCII character.
                    208: Changing the format is one of the operations in the expression's menu.
                    209: Choosing
                    210: .CW format ' `
                    211: from the expression's menu and
                    212: .CW ascii\ on ' `
                    213: from a sub-menu of formats re-evaluates the expression and displays it as:
                    214: .P1 0
                    215: c='a'=97
                    216: .P2
                    217: .PP
                    218: In debugging a real program there are likely to be many more windows.
                    219: (Pi is used daily by dozens of programmers, often on programs of 10,000 lines
                    220: or more.)
                    221: The windows shown above may be instantiated arbitrarily often: a Source Text window
                    222: for each of the program's source files and a Frame window for each of the
                    223: callstack's activation records.
                    224: There may be only one instance of each of the other window types:
                    225: a Frame window bound to global scope rather than an activation record,
                    226: an Assembler window for controlling the process at the instruction level,
                    227: a Raw Memory window for manipulating memory as an uninterpreted array of cells,
                    228: and a Signals window for controlling process exceptions.
                    229: .PP
                    230: The resemblance between Pi and Dbxtool|reference(dbxtool) is superficial,
                    231: even though both debuggers use windows and menus on a bitmap display.
                    232: Dbxtool is a front-end that translates graphics commands
                    233: into the command language of a conventional debugger, Dbx.
                    234: Dbxtool provides a fixed set of windows, one of which, the ``Command Window,''
                    235: shows the Dbx commands and responses.
                    236: To see how this affects the user, consider trying to evaluate
                    237: expressions with respect to two different activations records at once.
                    238: In Pi, a window is opened for each activation record and the user
                    239: moves back and forth with the mouse, evaluating expressions in either window;
                    240: each window then contains a set of related expressions.
                    241: In Dbxtool, the user enters a mixture of commands that evaluate expressions and
                    242: move up or down the callstack; the Command Window then contains a transcript of
                    243: interleaved context changes and evaluated expressions.
                    244: .PP
                    245: Pi is also unlike the debugger in Smalltalk's ``integrated environment,''
                    246: where there is little distinction between the compiler, interpreter, browser and
                    247: debugger|reference(smalltalk80).
                    248: Smalltalk's tools cooperate through shared data structures and the
                    249: computer's uniformly defined graphics.
                    250: On the other hand, Pi is an isolated tool in a ``toolkit environment.''
                    251: Pi interacts with graphics, external data and other processes
                    252: through explicit interfaces.
                    253: Pi adapts the graphics techniques exemplified by Smalltalk to the toolkit
                    254: setting.
                    255: .NH
                    256: Object-Oriented Programming
                    257: .PP
                    258: The remainder of the paper concerns the implementation of Pi.
                    259: The object-oriented programming model is better suited to the implementation of
                    260: Pi than the sequential programming model or the multiple sequential processes
                    261: model.
                    262: .PP 
                    263: Under the sequential model, a process executes a program.
                    264: At any time the process is in some state, with control at some location in the
                    265: program.
                    266: When the process blocks for input from the user it is in the state from which
                    267: it resumes when the user responds.
                    268: This makes it difficult to drive the process with the flexibility described
                    269: above, where the user is allowed to refuse a given menu and leap to some
                    270: unrelated context.
                    271: The process must accept either the menu selection or the context switch.
                    272: The menu selection is a local operation in the local context;
                    273: the context switch is a global operation involving unrelated parts of the
                    274: program.
                    275: This might be achieved by letting the user traverse a network of contexts.
                    276: But there is then a tradeoff between ease of use and ease of programming:
                    277: a tree is easy to program, but tedious for the user to traverse;
                    278: a fully connected network might be easy to use, but calls for every
                    279: part of the program to be intimately coupled to every other part.
                    280: .PP
                    281: With multiple sequential processes a separate process could be associated with
                    282: each context.
                    283: As the user moves around the graphics screen, input is directed to the
                    284: appropriate process by some agent that maps screen locations to processes.
                    285: Processes are suspended until the user needs them;
                    286: they need know only about semantically related processes.
                    287: As a model this yields a natural architecture,
                    288: but its implementation is usually very expensive.
                    289: The overhead for creation and interaction of separate processes
                    290: might be acceptable for a small number of processes, say one per window.
                    291: But the desired user interface has each line within each window interacting
                    292: independently with the user.
                    293: Many hundreds, even thousands, of processes would be needed.
                    294: With conventional multi-process techniques the cost is prohibitive.
                    295: .PP
                    296: The object-oriented model lies between these two models.
                    297: Instead of a collection of processes there is a single process containing a
                    298: collection of
                    299: .I objects ,
                    300: each an instance of a type called a
                    301: .I class .
                    302: Each object has a copy of the
                    303: .I data\ members
                    304: defined in its class and can execute the
                    305: .I function\ members
                    306: of the class.
                    307: Unlike a process, an object has no permanent state other than its data.
                    308: Objects share a universal address space and communicate with one another
                    309: by invoking function members as procedures.
                    310: It is feasible to have many thousands of objects.
                    311: That objects cannot execute concurrently, as processes do by timeslicing a
                    312: physical processor, is not significant; such concurrency is not required.
                    313: .PP
                    314: The C++|reference(cplusplus) programming language supports this model.
                    315: C++ is a superset of C with Simula-like classes|reference(simula begin).
                    316: The members of a class are data and functions, in private and public sections:
                    317: .P1 0
                    318: class \fIidentifier\fP {
                    319:         \fIprivate_data_declarations\fP
                    320:         \fIprivate_function_declarations\fP
                    321: public:
                    322:         \fIpublic_data_declarations\fP
                    323:         \fIpublic_function_declarations\fP
                    324: };
                    325: .P2
                    326: (This grammar generates only a subset of C++ class declarations.)
                    327: The example of C++ code below defines a class
                    328: .CW Const
                    329: that privately represents an integer value; the value is rendered as
                    330: a hexadecimal, decimal or octal character string
                    331: by three member functions.
                    332: Similar code is found in Pi.
                    333: .P1 0
                    334: class Const {
                    335:         int      val;              // private data member
                    336: public:
                    337:                  Const(int);       // constructor
                    338:         char     *hex();           // public function members
                    339:         char     *dec();
                    340:         char     *oct();
                    341: };
                    342: 
                    343: void Const::Const(int v)           // constructor body
                    344: {
                    345:         val = v;
                    346: }
                    347: 
                    348: char *Const::hex()                 // function member body
                    349: {
                    350:         \fIbuild character string\fP
                    351:         return \fIpointer to character string\fP;
                    352: }
                    353: 
                    354:  ...
                    355: 
                    356: .P2
                    357: A member function whose name is the same as that of its class is a
                    358: .I constructor.
                    359: If defined, the constructor is invoked to initialize a new object.
                    360: .CW Const 's
                    361: constructor assigns its argument to the private representation of the value.
                    362: .CW Const 's
                    363: member functions
                    364: .CW hex() ,
                    365: .CW dec()
                    366: and
                    367: .CW oct()
                    368: return pointers to ASCII representations of the value.
                    369: The following client code prints 123 as
                    370: .CW 0x7B=123=0173 :
                    371: .P1 0
                    372: {
                    373:         Const c(123);             // c.Const(123) called implicitly
                    374:         printf("%s=%s=%s", c.hex(), c.dec(), c.oct());
                    375: }
                    376: .P2
                    377: Here
                    378: .CW Const\ c(123)
                    379: declares an object instantiated on the stack for the lifetime of the block;
                    380: the argument
                    381: .CW 123
                    382: is passed to the constructor.
                    383: The object could also be allocated from the free store by means of the operator
                    384: .CW new ,
                    385: in which case the variable's type is pointer-to-\f(CWConst\fP:
                    386: .P1 0
                    387: {
                    388:         Const *cp;
                    389:         cp = new Const(123);       // allocate from free store
                    390:         printf("%s=%s=%s", cp->hex(), cp->dec(), cp->oct());
                    391:         delete cp;                 // return to storage pool
                    392: }
                    393: .P2
                    394: .PP
                    395: In general, an executing C++ program consists of a network of objects,
                    396: referencing one another with pointers.
                    397: Subject to the encapsulation rules of C++,
                    398: objects may access one another's member data and invoke member functions.
                    399: Consider a Frame window that evaluates expressions with respect to an activation
                    400: record.
                    401: Figure 4 shows two objects:
                    402: .CW F ,
                    403: an instance of class
                    404: .CW Frame ,
                    405: and
                    406: .CW E ,
                    407: an instance of class
                    408: .CW Expr .
                    409: An arrow represents a pointer; an arrow labeled by a function call indicates
                    410: that the holder of the pointer may call the member function of the object to
                    411: which it points.
                    412: .KF
                    413: .PS
                    414: .ft CW
                    415: F: circle "F"
                    416: E: circle "E" at F.c+(0,-1)
                    417: arrow "       create_expr(symbol)" "" from F.e+(1,0) to F.e
                    418: arrow "           evaluate()" from F.s to E.n
                    419: .ft P
                    420: .PE
                    421: .ce
                    422: Figure 4. A sub-network of objects.
                    423: .KE
                    424: .PP
                    425: Here,
                    426: .CW create_expr()
                    427: is a member of
                    428: .CW Frame
                    429: and
                    430: .CW evaluate()
                    431: is a member of
                    432: .CW Expr :
                    433: .P1 0
                    434: class Frame{
                    435:         ...
                    436:         void create_expr(Symbol*);
                    437:         ...
                    438: };
                    439: 
                    440: class Expr{
                    441:         ...
                    442:         ErrMsg evaluate();
                    443:         ...
                    444: };
                    445: .P2
                    446: The
                    447: .CW Symbol*
                    448: argument to
                    449: .CW create_expr
                    450: is a pointer to a symbol table object describing a variable in
                    451: .CW F 's
                    452: activation record's scope, from which the
                    453: .CW Frame
                    454: creates an
                    455: .CW Expr
                    456: object
                    457: .CW E .
                    458: .CW F
                    459: might cause the expression to evaluate itself by calling
                    460: .CW E.evaluate() ,
                    461: and so forth.
                    462: .PP
                    463: The notation in Figure 4 suggests a message-passing model.
                    464: Indeed, the terminology of object-oriented programming in 
                    465: Smalltalk|reference(smalltalk80) refers to ``messages'' between objects.
                    466: However, the communication is by procedure call.
                    467: .NH
                    468: Object-Oriented User Interface
                    469: .PP
                    470: A user interface to such a network can be created by letting the user communicate
                    471: directly through the interfaces that objects present to one another|reference(smalltalk80).
                    472: The user must be able to select an object,
                    473: see a set of member function calls and select one to be invoked.
                    474: To receive a member function call from the user an object must describe
                    475: itself and a set of function calls to the software managing the display.
                    476: .PP
                    477: Figure 5 shows the object network of Figure 4 and a screen image.
                    478: .CW F
                    479: has associated itself with a window on the screen, and
                    480: .CW E
                    481: has associated itself with a line in that window.
                    482: The new ``pointers'' from images on the screen to objects in the program are
                    483: shown by dashed arrows.
                    484: If the user selects
                    485: .CW E 's
                    486: line, raises the line's menu and selects
                    487: .CW ascii\ on ', `
                    488: then the result is a call of
                    489: .CW E 's
                    490: member function:
                    491: .P1 0
                    492: E.reformat(ASCII_ON)
                    493: .P2
                    494: .KF
                    495: .PS
                    496: .ft CW
                    497: F: circle "F"
                    498: E: circle "E" at F.c+(0,-1)
                    499: arrow from F.s to E.n
                    500: SCREEN: spline from E.s+(2,-.8) \
                    501:        then up 1.2 \
                    502:        then up 0.1 right 0.1 then up 0.1 right 0.1 \
                    503:        then right 1 up 0.1 right 1 down 0.1 \
                    504:        then down 0.1 right 0.1 then down 0.1 right 0.1 \
                    505:        then down 2.4 \
                    506:        then left 0.1 down 0.1 then left 0.1 down 0.1 \
                    507:        then left 1 down 0.1 left 1 up 0.1 \
                    508:        then up 0.1 left 0.1 then up 0.1 left 0.1 \
                    509:        then up 1.2
                    510: WINDOW: box at SCREEN+(1,0.5) height 1 width 1.5
                    511: "count.c:9 main()" at WINDOW.nw+(.7,-0.1)
                    512: "c=97" at WINDOW.nw+(.2,-0.25)
                    513: arrow dashed from WINDOW.n to F chop 0 chop circlerad
                    514: arrow "reformat(ASCII_ON)" "" dashed from WINDOW.nw+(.05,-0.3) to E chop 0 chop circlerad
                    515: MENU: box "." "." "ascii on" "." "." at WINDOW+(.5,-1.2) height 1.2 width .7
                    516: line dashed from MENU+(-.1,.1) to WINDOW.nw+(.05,-0.3)
                    517: .ft P
                    518: .PE
                    519: .ce
                    520: Figure 5. Operations invoked by the user.
                    521: .KE
                    522: .PP
                    523: Three points of detail should be mentioned.
                    524: First, the user does not really see the program's internal interfaces,
                    525: i.e., the identifiers and types of member functions.
                    526: The text of a menu entry is chosen to support the user's view of the
                    527: function and need not reflect program's source literally.
                    528: For example, the user sees
                    529: .CW ascii\ on ' `
                    530: instead of
                    531: .CW reformat(ASCII_ON) '. `
                    532: Second, when invoked, a member function cannot distinguish a call
                    533: originated directly by the user from a call by another object.
                    534: There is only one procedure call mechanism.
                    535: Third, once an object has exposed its image and a set of member function calls
                    536: to the user, it must be prepared to receive any sequence of calls the user
                    537: chooses to make.
                    538: To inhibit the user, the object must remove itself or change the set of calls
                    539: in the menu.
                    540: Changes in the menu reassure the user about the state of the object:
                    541: the menu of a line of source text on which the user has already set a breakpoint
                    542: shows
                    543: .CW clear\ bpt ' `
                    544: instead of
                    545: .CW set\ bpt '. `
                    546: .PP
                    547: The interaction between the user and program is now conducted without a
                    548: ``user interface'' in the conventional sense.
                    549: There is no part of the program responsible for reading, parsing and interpreting a
                    550: sequence of commands from the user.
                    551: The user sees a graphical image of the program and the program sees the user as an
                    552: active participant in its object-oriented world,
                    553: with implicit software mapping between the two.
                    554: .NH
                    555: Implementation
                    556: .PP
                    557: The interface between the graphics and the network of objects is a pair of C++
                    558: classes:
                    559: .CW Window
                    560: and
                    561: .CW Menu .
                    562: These classes in turn interact with a graphics package.
                    563: To create its window, the
                    564: .CW Frame
                    565: object executes:
                    566: .P1 0
                    567: w = new Window;            // Save the pointer to a new
                    568:                            // Window in Frame's data member w.
                    569: w->bind(this);             // Pass a pointer to this (a C++ keyword)
                    570:                            // instance of Frame to the Window.
                    571: w->title(description());   // Frame's member function description()
                    572:                            // yields a string like "count:9 main()".
                    573: w->makecurrent();          // To make a newly created window current
                    574:                            // the user must sweep a rectangle for it.
                    575: .P2
                    576: To display its value in the window an
                    577: .CW Expr
                    578: object executes:
                    579: .P1 0
                    580: Menu m;                    // Instantiate a Menu on the local stack.
                    581:  .
                    582:  .
                    583: m.append("octal on", &reformat, OCTAL_ON);
                    584: m.append("ascii on", &reformat, ASCII_ON);       // Build the menu.
                    585: m.append("float on", &reformat, FLOAT_ON);
                    586:  .
                    587:  .
                    588: w->insert(line, this, m, evaltext());   // Insert line in window.
                    589: .P2
                    590: Each call to
                    591: .CW m.append()
                    592: adds an entry to the menu.
                    593: Each entry consists of the text string the user sees when the menu is raised,
                    594: the member function to be called and the argument to be passed.
                    595: The call to
                    596: .CW insert() ,
                    597: using a copy of
                    598: .CW w
                    599: passed to the
                    600: .CW Expr
                    601: by the
                    602: .CW Frame ,
                    603: creates a new line of text in the window.
                    604: The text string, like
                    605: \f(CW"c=97"\fP,
                    606: is obtained from
                    607: .CW Expr 's
                    608: member function
                    609: .CW evaltext() .
                    610: The
                    611: .CW line
                    612: argument is a numeric key that determines where this line will appear
                    613: relative to other lines in the window.
                    614: Not shown here is how 
                    615: .CW ascii\ on ' `
                    616: would be made to appear in a sub-menu, as in Figure 3.
                    617: Instead of being bound to a window or line of text, one
                    618: .CW Menu
                    619: may be embedded in another.
                    620: The embedded sub-menu pops up when the cursor is positioned over a sub-menu
                    621: indicator in the super-menu.
                    622: .PP
                    623: Note that the code from
                    624: .CW Frame
                    625: and
                    626: .CW Expr
                    627: does not reflect the terminal's physical attributes.
                    628: The abstractions are windows and menus, not coordinates and mouse buttons.
                    629: Pi has no knowledge of, or control over, details such as the placement of
                    630: windows, the scrolling of text or the assignment of mouse buttons.
                    631: Though not used here, the most concrete graphics request is that a line of text
                    632: be made visible to the user.
                    633: This means that the graphics and mouse protocol described above is just one of
                    634: many possible choices; the user's view might be quite different in an
                    635: implementation for a one-button mouse.
                    636: .PP
                    637: Operations on
                    638: .CW Window
                    639: objects are translated into a stream of messages passed to a separate
                    640: process responsible for the real-time graphics.
                    641: Operations from the user are transmitted back to the main process, received by
                    642: the package and invoked on the Pi's objects.
                    643: The two processes execute asynchronously on separate processors: the main
                    644: process on a
                    645: .UX
                    646: system, ``the host,'' and the real-time graphics process on
                    647: a Teletype DMD 5620 bitmap terminal, ``the terminal'' (Figure 6).
                    648: .KF
                    649: .PS
                    650: HOST: circle "Pi and" "window" "package" rad .5
                    651: TERM: circle at HOST+(2,0) "real-time" "graphics" rad .5
                    652: TOTERM: arc -> cw from HOST.ne to TERM.nw
                    653: TOHOST: arc -> cw from TERM.sw to HOST.se
                    654: "definitions" at TOTERM.n+(0,.1)
                    655: "operations" at TOHOST.s-(0,.1)
                    656: LINE: line dashed from TOTERM.n-(0,.1) to TOHOST.s+(0,.1)
                    657: ABOVE: line dashed from TOTERM.n+(0,.2) to TOTERM.n+(0,0.4)
                    658: BELOW: line dashed from TOHOST.s-(0,.2) to TOHOST.s-(0,1.5)
                    659: SUBJ: circle at HOST-(0,1.5) "subject" "process" rad .5
                    660: arrow "      OS support" <-> from HOST.s to SUBJ.n
                    661: "host" at SUBJ.s-(0,0.2)
                    662: "terminal" at TERM.s-(0,1.7)
                    663: .PE
                    664: .ce
                    665: Figure 6. Inter-process communication.
                    666: .KE
                    667: .PP
                    668: Global control in the graphics program lies in a loop that polls the
                    669: host and user for commands.
                    670: The following pseudo-code approximates the loop:
                    671: .P1 0
                    672: for(;;){                          // forever
                    673:     if( mouse activity ){
                    674:         update screen
                    675:         if( remote operation selected )
                    676:             send message to host
                    677:     }
                    678:     if( message from host ){
                    679:         receive from host
                    680:         update screen
                    681:     }
                    682:     if( real-time clock event ){   // see below   
                    683:         send message to host
                    684:     }
                    685: }
                    686: .P2
                    687: Global control in the host process lies in a loop in
                    688: the window package that blocks waiting for messages from the terminal:
                    689: .P1 0
                    690: for(;;){
                    691:         read <object, operation, operand> from terminal
                    692:         invoke object->operation(operand)
                    693: }
                    694: .P2
                    695: Usually, an invoked operation in turn causes definitions or redefinitions
                    696: of objects to be sent to the terminal, to show the user some kind of result.
                    697: The messages from the host arrive asynchronously with respect to the user's
                    698: interaction with the graphics.
                    699: An experienced user need not wait for changes from each operation to be reflected
                    700: on the screen before invoking another operation.
                    701: For example, having requested a particular context within a source file to be
                    702: displayed, the user can select a source line and set a breakpoint on it
                    703: as soon as that line is visible, even if the host has not finished sending
                    704: all of the lines needed to fill the window.
                    705: Some users take advantage of the asynchrony as the natural way to function;
                    706: others operate as though the communications were half-duplex.
                    707: .NH
                    708: Pi's Architecture
                    709: .PP
                    710: The debugger is a network of objects as described above.
                    711: An object of class
                    712: .CW Process
                    713: is created to take overall control of the subject process.
                    714: The
                    715: .CW Process
                    716: object creates two major objects to serve it: a symbol table object of class
                    717: .CW SymTab
                    718: and a core image access object of class
                    719: .CW Core .
                    720: The
                    721: .CW SymTab
                    722: is responsible for reading the symbol table as left by the compiler,
                    723: assembler and loader and providing that information to the rest of the
                    724: debugger, as discussed below.
                    725: The
                    726: .CW Core
                    727: is responsible for all access to the address space of the subject process and
                    728: the operating system's control information.
                    729: Details of the physical processor, operating system and compiler generated code
                    730: are encapsulated in
                    731: .CW Core .
                    732: .KF
                    733: .PS 5
                    734: .ft CW
                    735: .ps -2
                    736: PROC: circle "Process" rad 0.4
                    737: CIRC: circle at PROC rad 1.5 invis
                    738: CORE: circle "Core" at CIRC.w rad 0.4
                    739: SYMTAB: circle "SymTab" at CIRC.nw rad 0.4
                    740: BPTS: circle "BreakPts" at CIRC.n rad 0.4
                    741: ASM: circle "Assembler" at CIRC.ne rad 0.4
                    742: MEM: circle "Memory" at CIRC.e rad 0.4
                    743: STACK: circle "CallStack" at CIRC.sw rad 0.4
                    744: FRAME: circle "Frame" at CIRC.s rad 0.4
                    745: SIGNALS: circle "Signals" at CIRC.se rad 0.4
                    746: line from CORE to PROC chop 0.4
                    747: line from SYMTAB to PROC chop 0.4
                    748: line from BPTS to PROC chop 0.4
                    749: line from ASM to PROC chop 0.4
                    750: line from MEM to PROC chop 0.4
                    751: line from STACK to CORE chop 0.4
                    752: line from FRAME to STACK chop 0.4
                    753: line from SIGNALS to PROC chop 0.4
                    754: FRAMEN: circle "Frame" at FRAME-(0,1.5) rad 0.4
                    755: "." "." "." at 1/2 <FRAME.s,FRAMEN.n>
                    756: line from STACK to FRAMEN chop 0.4
                    757: EXPR: circle "Expr" at FRAMEN+(1.5,0) rad 0.4
                    758: EXPRN: circle "Expr" at EXPR+(1.5,0) rad 0.4
                    759: line from FRAMEN to EXPR chop 0.4
                    760: ". . ." at 1/2 <EXPR.e,EXPRN.w>
                    761: SRC: circle "SrcText" at SYMTAB+(0,1.5) rad 0.4
                    762: SRCN: circle "SrcText" at SRC+(-1.5,0) rad 0.4
                    763: line from SYMTAB to SRC chop 0.4
                    764: line from SYMTAB to SRCN chop 0.4
                    765: ". . ." at 1/2 <SRC.w,SRCN.e>
                    766: .ft P
                    767: .ps +2
                    768: .PE
                    769: .ce
                    770: .ft R
                    771: Figure 7. Pi's object network
                    772: .KE
                    773: .PP
                    774: Of these three classes, only
                    775: .CW Process
                    776: opens a window, the Process window, from which the user may then open other
                    777: windows.
                    778: To display a callstack, the
                    779: .CW Process
                    780: obtains a
                    781: .CW CallStack
                    782: object from the
                    783: .CW Core
                    784: and extracts each activation record it needs as a
                    785: .CW Frame
                    786: object from the
                    787: .CW CallStack.
                    788: If a
                    789: .CW Frame
                    790: is referenced by the user, it opens a Frame window.
                    791: As the user creates expressions and derives new ones, each expression is an
                    792: .CW Expr
                    793: object which displays itself as a line in its
                    794: .CW Frame 's
                    795: window.
                    796: Figure 7 shows the network.
                    797: The lines indicate the primary permanent links between objects, but
                    798: pointers are passed around as needed.
                    799: .PP
                    800: It is the
                    801: .CW Process
                    802: that monitors the state of subject process.
                    803: When the subject is running, its state must be polled to see if it reaches a
                    804: breakpoint or some other exception.
                    805: The
                    806: .CW Process
                    807: therefore periodically executes an operation that reads the current
                    808: state of the subject from the
                    809: .CW Core .
                    810: This operation re-invokes itself by sending to the terminal
                    811: a message requesting that the terminal invoke it after a specified delay.
                    812: The invocation returned from the terminal is interleaved with, and
                    813: indistinguishable from, invocations made directly by the user.
                    814: The
                    815: .CW Process
                    816: can monitor the state of the subject process while the user asynchronously
                    817: evaluates expressions, sets breakpoints and so on.
                    818: This technique creates a few bytes per second of extra host-terminal traffic,
                    819: since it would be possible to use a clock interrupt on the host instead.
                    820: However, to guarantee that operations are invoked fairly is much simpler if
                    821: the only source of operations is a single stream coming from the terminal.
                    822: Also, the terminal is a better place for real-time programming, both in terms
                    823: of operating system support and expendable processor resources.
                    824: .NH
                    825: Symbol Tables
                    826: .PP
                    827: A debugger's needs of its symbol table are similar to those of a compiler,
                    828: for example, to determine the variables in scope at some point in the program.
                    829: If the symbol tables prepared by the translator suite reflect the data
                    830: structures used in the compiler,
                    831: the debugger is much simplified|reference(cardell swat).
                    832: If the tables have been ``flattened'' by an assembler or loader, the debugger
                    833: suffers the loss of information.
                    834: Reconstructing an acceptable data structure can be very time-consuming
                    835: |reference(beander).
                    836: Working with the flattened tables complicates those parts of the debugger
                    837: that make non-trivial use the symbol table|reference(blit debugger spe).
                    838: .PP
                    839: For Pi, it seemed likely that versions of the debugger would be used with
                    840: compilers and assemblers that produced at least two distinct flattened formats.
                    841: So the first goal was to find a format-independent internal representation
                    842: that could be built from either format and was well-suited to debugging.
                    843: Given that the debugger was being written in C++, it seemed a good
                    844: opportunity to experiment with object-oriented programming.
                    845: In contrast to the user interface, there was no overall design paradigm guiding the
                    846: symbol table; the software evolved as different ideas were tried.
                    847: .PP
                    848: From the outset, the symbol table was a sub-network of objects.
                    849: Its structure follows the abstract structure of the subject program \- a tree.
                    850: The root of the tree is the
                    851: .CW SymTab
                    852: object.
                    853: It reads the file prepared by the loader and builds a tree, as shown in Figure 8.
                    854: Below the
                    855: .CW SymTab
                    856: there is a
                    857: .CW SrcFile
                    858: object for each separately compiled source file that contributed to the
                    859: program.
                    860: (There is a one-to-one correspondence between the
                    861: .CW SrcFile s
                    862: and the
                    863: .CW SrcText s
                    864: of Figure 7; a
                    865: .CW SrcText
                    866: is created and opens a window when the user decides to examine the source text
                    867: from the corresponding
                    868: .CW SrcFile .)
                    869: Below each
                    870: .CW SrcFile
                    871: there is a
                    872: .CW Function
                    873: for each function defined in that file.
                    874: Below each
                    875: .CW Function
                    876: there is a
                    877: .CW Block
                    878: of arguments and a
                    879: .CW Block
                    880: of local variables.
                    881: Each
                    882: .CW Block
                    883: has a list of
                    884: .CW Variables .
                    885: Each
                    886: .CW Function
                    887: also has a list of
                    888: .CW Statements ,
                    889: the source statements in the function.
                    890: Some symbols can also be accessed directly from a hash table whose entries point
                    891: into tree's interior.
                    892: .PP
                    893: Like any object, a member of the symbol table can receive operations from the user.
                    894: For example, the object associated with a line of source text in a Source Window
                    895: is a
                    896: .CW Statement .
                    897: To set a breakpoint the user communicates directly with the symbol table.
                    898: .PP
                    899: Though not shown in the figure, each node in the tree has four pointers: to its
                    900: parent, leftmost child and right and left sibling.
                    901: These make it easy to traverse the tree.
                    902: C++'s type inheritance is used to capture the common properties of all symbol
                    903: nodes in a
                    904: .I base
                    905: .I class
                    906: called
                    907: .CW Symbol
                    908: from which other classes of symbols are
                    909: .I derived :
                    910: .P1 0
                    911: class Symbol {
                    912: public:
                    913:         Symbol  *parent;
                    914:         Symbol  *leftmost_child;
                    915:         Symbol  *right_sibling;
                    916:         Symbol  *left_sibling;
                    917:         Address addr;
                    918:         char    *id;
                    919: virtual char    *text();
                    920: };
                    921: .P2
                    922: .KF
                    923: .PS 4
                    924: .ft CW
                    925: .ps -2
                    926: SYMTAB: circle "SymTab" rad 0.4
                    927: SRC2: circle "SrcFile" at SYMTAB-(0,1.2) rad 0.4
                    928: SRC1: circle "SrcFile" at SRC2-(1.5,0) rad 0.4
                    929: SRC3: circle "SrcFile" at SRC2+(2,0) rad 0.4
                    930: arrow from SYMTAB to SRC1 chop 0.4
                    931: arrow from SRC1 to SRC2 chop 0.4
                    932: ". . ." at 1/2 <SRC2,SRC3>
                    933: FUNC2: circle "Function" at SRC2-(0,1.2) rad 0.4
                    934: FUNC1: circle "Function" at FUNC2-(1.5,0) rad 0.4
                    935: FUNC3: circle "Function" at FUNC2+(2,0) rad 0.4
                    936: arrow from SRC2 to FUNC1 chop 0.4
                    937: arrow from FUNC1 to FUNC2 chop 0.4
                    938: ". . ." at 1/2 <FUNC2,FUNC3>
                    939: ARGS: circle "Block" at FUNC2-(0,1.2) rad 0.4
                    940: LCLS: circle "Block" at ARGS-(0,1.2) rad 0.4
                    941: arrow from FUNC2 to ARGS chop 0.4
                    942: arrow from ARGS to LCLS chop 0.4
                    943: VAR1: circle "Variable" at LCLS+(1.5,0) rad 0.4
                    944: VAR2: circle "Variable" at VAR1+(1.5,0) rad 0.4
                    945: arrow from LCLS to VAR1 chop 0.4
                    946: ". . ." at 1/2 <VAR1,VAR2>
                    947: STMT2: circle "Statement" at LCLS-(0,1.2) rad 0.4
                    948: STMT1: circle "Statement" at STMT2-(1.5,0) rad 0.4
                    949: STMT3: circle "Statement" at STMT2+(1.5,0) rad 0.4
                    950: arrow from FUNC2 to STMT1 chop 0.4
                    951: arrow from STMT1 to STMT2 chop 0.4
                    952: ". . ." at 1/2 <STMT2,STMT3>
                    953: line dashed "" "\fPlazy\f(CW" from FUNC1-(0,0.6) to FUNC3-(0,0.6)
                    954: .ft P
                    955: .ps +2
                    956: .PE
                    957: .ce
                    958: .ft R
                    959: Figure 8. The symbol table hierarchy
                    960: .KE
                    961: The
                    962: .CW addr
                    963: and
                    964: .CW id
                    965: data members record address information and an identifier for each symbol.
                    966: The function
                    967: .CW text()
                    968: returns a textual representation of the node, which is just the identifier:
                    969: .P1 0
                    970: char *Symbol::text()
                    971: {
                    972:         return id;
                    973: }
                    974: .P2
                    975: There are no instances of class
                    976: .CW Symbol
                    977: in the tree; each node of the symbol table is of a type derived from
                    978: .CW Symbol .
                    979: For example:
                    980: .P1 0
                    981: class Variable : public Symbol {
                    982: public:
                    983:         Storage     storage;
                    984:         DataType    type;
                    985: };
                    986: .P2
                    987: As a derived class,
                    988: .CW Variable
                    989: inherits all the data and functions of
                    990: .CW Symbol ;
                    991: it has two additional data members specific to its needs.
                    992: In
                    993: .CW Symbol ,
                    994: the function
                    995: .CW text()
                    996: is declared
                    997: .I virtual.
                    998: This means that a derived class may override the base version with its own.
                    999: .CW Variable
                   1000: has no need to do this; it is adequately served by the base version that returns
                   1001: the identifier.
                   1002: However,
                   1003: .CW Statement
                   1004: does define its own
                   1005: .CW text() :
                   1006: .P1 0
                   1007: class Statement : public Symbol {
                   1008:         ...
                   1009:         int     line_number;
                   1010:         char    *text();
                   1011: };
                   1012: .P2
                   1013: Instead of returning the identifier (which is not used by
                   1014: .CW Statement ),
                   1015: .CW Statement::text()
                   1016: walks up the tree to find its
                   1017: .CW SrcFile 
                   1018: node and returns a string identifying the statement's source file and line number,
                   1019: like:
                   1020: .P1 0
                   1021: "count.c:9"
                   1022: .P2
                   1023: In general, code traversing the tree to extract textual
                   1024: information does not depend on whether the class of a given node
                   1025: defines its own version of
                   1026: .CW text() .
                   1027: For example,
                   1028: .CW Statement::text()
                   1029: applies
                   1030: .CW text()
                   1031: to a
                   1032: .CW SrcFile
                   1033: pointer to obtain the pathname of the source file to embed in the string being
                   1034: built; it does not know whether
                   1035: .CW SrcFile
                   1036: has defined its own version of
                   1037: .CW text() .
                   1038: The choice of implementation of an operation by the object on which the operation
                   1039: is invoked, rather than the invoker, is common to all object-oriented
                   1040: programming.
                   1041: The technique is used throughout Pi.
                   1042: .NH
                   1043: Lazy Symbol Table Construction
                   1044: .PP
                   1045: If the symbol table as described above were really built it would consume
                   1046: enormous time and space.
                   1047: Early versions of Pi did build it and could only be applied to small programs.
                   1048: Time and space performance was improved dramatically by delaying construction
                   1049: of the subtree of local symbols below each
                   1050: .CW Function
                   1051: node until needed \- ``lazy'' construction.
                   1052: .PP
                   1053: When the debugger picks up a process it scans the flattened symbol table
                   1054: to build the tree only down to the
                   1055: .CW Function
                   1056: level \- the part of the tree above the dashed line in Figure 8.
                   1057: From a
                   1058: .CW Function
                   1059: the only public access to its sub-tree is through function members.
                   1060: The members of
                   1061: .CW Function
                   1062: detect that the sub-tree is missing and call on the
                   1063: .CW SymTab
                   1064: to build it before returning a pointer to a requested
                   1065: .CW Block
                   1066: or
                   1067: .CW Statement .
                   1068: Once the sub-tree has been built it remains; pointers into it may have been
                   1069: passed to, and retained by, other objects.
                   1070: .PP
                   1071: Lazy construction allows large symbol tables to be presented to the rest
                   1072: of the debugger in a manner that is encapsulated naturally and efficiently.
                   1073: The real-time initialization delay is about one second per thousand lines of source
                   1074: text, on a VAX-11/750\(dg processor.
                   1075: .FS
                   1076: \(dg VAX is a trademark of Digital Equipment.
                   1077: .FE
                   1078: The cost of building a sub-tree on demand is negligible and very
                   1079: few are built.
                   1080: Local tables are needed for only those functions on the callstack
                   1081: or visible in a Source window.
                   1082: It is exceptional to need tables for more than about 15 functions.
                   1083: When Pi, a 500-function program, is used to debug itself, it typically builds
                   1084: 1%\-5% of the local tables.
                   1085: .PP
                   1086: This style of lazy table construction could be implemented in any programming
                   1087: language, but reliably only in a language that enforces its data abstraction.
                   1088: In C++, because the only public access from a
                   1089: .CW Function
                   1090: to its sub-tree is through member functions, the lazy operation of the symbol
                   1091: table does not depend on cooperation from clients.
                   1092: Very few problems have arisen with this code.
                   1093: .PP
                   1094: A similar lazy method is used to defer the construction of the tables of
                   1095: user-defined types; the savings are comparable.
                   1096: .NH
                   1097: Generators that Traverse the Symbol Table
                   1098: .PP
                   1099: Clients of the symbol table need to perform various traversals to extract
                   1100: information.
                   1101: For example, a menu built by a
                   1102: .CW Frame
                   1103: contains the local variables visible from a function.
                   1104: This might be implemented by a natural traversal of the data structure
                   1105: in Figure 8.
                   1106: However, the symbol table can be better encapsulated by providing a class
                   1107: .CW VisibleVars ,
                   1108: an instance of which performs such a traversal:
                   1109: .P1 0
                   1110: class VisibleVars {
                   1111:  ...
                   1112: public:
                   1113:                   VisibleVars(Block*);
                   1114:         Variable  *gen();
                   1115: };
                   1116: .P2
                   1117: The constructor takes an argument pointing to a
                   1118: .CW Block .
                   1119: The only public function,
                   1120: .CW gen() ,
                   1121: returns a pointer to a different
                   1122: .CW Variable
                   1123: on each call, ending with a null pointer.
                   1124: Client code with a pointer to a
                   1125: .CW Block
                   1126: from somewhere:
                   1127: .P1 0
                   1128: Block *b;
                   1129: .P2
                   1130: creates an instance of
                   1131: .CW VisibleVars
                   1132: and iterates through the generated variables:
                   1133: .P1 0
                   1134: {
                   1135:         VisibleVars vv(b);
                   1136:         Variable *var;
                   1137:        
                   1138:         while( var = vv.gen() ){
                   1139:                 ...
                   1140:         }
                   1141: }
                   1142: .P2
                   1143: This iteration is built on general purpose data abstraction in C++,
                   1144: rather than a built-in iteration primitive|reference(icon book)
                   1145: |reference(alphard cacm).
                   1146: .PP
                   1147: Parts of Pi use nested iteration through the variables visible from a function.
                   1148: Nested iteration arises in the code that warns the user of ambiguity when an
                   1149: identifier occurs more than once in a menu.
                   1150: To determine if an identifier is non-unique, the menu builder searches
                   1151: the identifiers of the variables that its generator will produce later in the
                   1152: iteration by taking a
                   1153: .I copy
                   1154: of the generator in its current state and iterating through the copy:
                   1155: .P1 0
                   1156: {
                   1157:         VisibleVars vv(b);
                   1158:         Variable *v;
                   1159:        
                   1160:         while( v = vv.gen() ){
                   1161:                 ...
                   1162:                 VisibleVars copy(0);  // initialized with null block
                   1163:                 copy = vv;
                   1164:                 Variable *w;
                   1165:                 while( w = copy.gen() ){
                   1166:                        if( strcmp(v->id, w->id) )
                   1167:                                ...
                   1168:                 }
                   1169:                 ...
                   1170:         }
                   1171: }
                   1172: .P2
                   1173: (In this case quadratic running time is acceptable.)
                   1174: .NH
                   1175: Debugging Multiple Processes
                   1176: .PP
                   1177: The initial design of Pi did not consider letting the user examine
                   1178: more than a single process at a time.
                   1179: The
                   1180: .CW Process
                   1181: class (Figure 7) had been introduced in order to follow the object-oriented
                   1182: paradigm uniformly throughout the program.
                   1183: The intention was to instantiate
                   1184: .CW Process
                   1185: only once.
                   1186: Other parts of the debugger were made a little more complicated by this decision
                   1187: because they had to fetch data from the
                   1188: .CW Process
                   1189: that might otherwise have been stored in global variables.
                   1190: This version of the debugger did not have a master window or dynamic
                   1191: binding to processes; the subject process was fixed by a command line
                   1192: argument.
                   1193: .PP
                   1194: Once Pi was working reliably, I realized that with trivial modification
                   1195: it could instantiate an arbitrary set of
                   1196: .CW Process
                   1197: objects to examine an arbitrary set of subject processes.
                   1198: The user would need only one copy of the debugger, no matter how many processes
                   1199: were to be examined.
                   1200: It took only a few days to implement.
                   1201: A
                   1202: .CW Master
                   1203: object creates a
                   1204: .CW Process
                   1205: object for each subject process that the user chooses to examine,
                   1206: as shown in Figure 9.
                   1207: Each
                   1208: .CW Process
                   1209: and its sub-network knows nothing of any others that might exist.
                   1210: The user interface package sees no qualitative change \- just more objects.
                   1211: The technique whereby a
                   1212: .CW Process
                   1213: polls its subject is also unaffected; the delayed invocations of the
                   1214: polling operation from each
                   1215: .CW Process
                   1216: are interleaved with one another.
                   1217: None of the problems described in |reference(dbxtool) of implementing and using a
                   1218: multi-process debugger have been encountered.
                   1219: .KS
                   1220: .PS
                   1221: .ft CW
                   1222: .ps -2
                   1223: MAST: circle "Master" rad 0.3
                   1224: PROCSW: circle "Process" at MAST+(-1,-1) rad 0.3
                   1225: line from MAST to PROCSW chop 0.3
                   1226: PROCS: circle "Process" at MAST+(0,-1) rad 0.3
                   1227: line from MAST to PROCS chop 0.3
                   1228: PROCSE: circle "Process" at MAST+(1,-1) rad 0.3
                   1229: line from MAST to PROCSE chop 0.3
                   1230: .ft P
                   1231: .ps +2
                   1232: .PE
                   1233: .ce
                   1234: Figure 9. Object network for multi-process debugging.
                   1235: .KE
                   1236: .PP
                   1237: Multiple process debugging could also have been achieved by instantiating
                   1238: multiple debugger processes rather than multiple
                   1239: .CW Process
                   1240: objects within a single debugger.
                   1241: The advantage of instantiating only a single debugger is the reduced overhead
                   1242: for both the user and the computers.
                   1243: With only one instance of the debugger the user has less to manage on the screen.
                   1244: When multiple processes are debugged the set of debugging windows are the
                   1245: same whether they are together in one debugger's window or in separate instances
                   1246: of the debugger.
                   1247: But when the user wants to treat the debugging environment as a whole, it is
                   1248: better to deal with a single tool.
                   1249: For example, removing a single debugger is simpler than removing a set of
                   1250: debuggers. 
                   1251: Less machine resources on the host and terminal are required to execute
                   1252: only a single process in each.
                   1253: In special circumstances (such as debugging a debugger), multiple instances
                   1254: of the debugger are needed.
                   1255: .NH
                   1256: Multiple Target Environments
                   1257: .PP
                   1258: The initial design did anticipate versions of Pi that would operate in
                   1259: different target environments.
                   1260: Those parts of the debugger dependent on the target processor were encapsulated in
                   1261: the
                   1262: .CW Core
                   1263: and
                   1264: .CW Assembler
                   1265: classes, those dependent on the operating system in
                   1266: .CW Core ,
                   1267: and those dependent on the external symbol table format in
                   1268: .CW SymTab .
                   1269: The original version of Pi was for a VAX processor running the Eighth Edition of
                   1270: the
                   1271: .UX
                   1272: system.
                   1273: The intention was to tailor versions to different target environments
                   1274: as the need arose.
                   1275: The first demand was for a version to debug processes in the DMD 5620 bitmap
                   1276: terminal: an AT&T WE32000 processor running a virtual terminal multiplexor
                   1277: called Mux.
                   1278: .PP
                   1279: As a further experiment with object-oriented programming, I decided
                   1280: to build both of these versions as a single program \- so that one instance
                   1281: of Pi could simultaneously examine processes in both target environments.
                   1282: For each target-dependent class there must be a base class, with a derived class
                   1283: for each target environment.
                   1284: Everywhere a target-dependent object is instantiated it must be of the
                   1285: appropriate derived class, but its target-independent clients need not know
                   1286: from which derivation.
                   1287: The classes derived from
                   1288: .CW Core
                   1289: are
                   1290: .CW HostCore
                   1291: and
                   1292: .CW TermCore ,
                   1293: for the host and terminal, respectively.
                   1294: The class hierarchy for
                   1295: .CW Core
                   1296: is shown in Figure 10.
                   1297: .KF
                   1298: .PS
                   1299: .ft CW
                   1300: CORE: "Core"
                   1301: HOSTCORE: "HostCore" at CORE+(-0.75,-0.75)
                   1302: TERMCORE: "TermCore" at CORE+( 0.75,-0.75)
                   1303: line from CORE.s-(0,.1) to HOSTCORE.n+(0,.1)
                   1304: line from CORE.s-(0,.1) to TERMCORE.n+(0,.1)
                   1305: .ft P
                   1306: .PE
                   1307: .ce
                   1308: Figure 10. Class hierarchy for \f(CWCore\fP.
                   1309: .KE
                   1310: .PP
                   1311: The main process of Pi is still a process in the timesharing host.
                   1312: To access memory in the terminal, an instance of
                   1313: .CW TermCore
                   1314: (executing on the host) communicates with an additional agent
                   1315: process in the terminal.
                   1316: (The agent process need not be in the same terminal as the
                   1317: real-time graphics process, but usually it is.)
                   1318: This communication is based on remote procedure calls from the host to
                   1319: the terminal.
                   1320: The debugger is now three processes altogether: the host process, the real-time
                   1321: graphics process and the terminal access agent, as shown in Figure 11.
                   1322: Implementation experience with a previous debugger |reference(blit debugger spe)
                   1323: indicated that
                   1324: bandwidth between the host and terminal would limit performance.
                   1325: This influenced the design of the host/terminal protocol and
                   1326: satisfactory performance was achieved.
                   1327: For example, when a
                   1328: .CW TermCore
                   1329: requests a callstack traceback from the terminal, the terminal computes the
                   1330: callstack locally, compares it to the last callstack sent to the host and
                   1331: transmits the difference \- usually only the deepest activation record has
                   1332: changed.
                   1333: .KS
                   1334: .PS
                   1335: HOST: circle "Pi and" "window" "package" rad .5
                   1336: TERM: circle at HOST+(2,0) "real-time" "graphics" rad .5
                   1337: TOTERM: arc -> cw from HOST.ne to TERM.nw
                   1338: TOHOST: arc -> cw from TERM.sw to HOST.se
                   1339: "definitions" at TOTERM.n+(0,.1)
                   1340: "operations" at TOHOST.s-(0,.1)
                   1341: LINE: line dashed from TOTERM.n-(0,.1) to TOHOST.s+(0,.1)
                   1342: ABOVE: line dashed from TOTERM.n+(0,.2) to TOTERM.n+(0,0.4)
                   1343: BELOW: line dashed from TOHOST.s-(0,.2) to TOHOST.s-(0,1.5)
                   1344: SUBJ: circle at HOST-(0,1.5) "host" "subject" "process" rad .5
                   1345: arrow "      OS support" <-> from HOST.s to SUBJ.n
                   1346: PIDOTM: circle at HOST+(-2,0) "terminal" "agent" rad .5
                   1347: TOPIDOTM: line "remote" "proc call" <-> from HOST.w to PIDOTM.e
                   1348: TERMSUBJ: circle at PIDOTM-(0,1.5) "terminal" "subject" "process" rad .5
                   1349: arrow "      OS support" <-> from PIDOTM.s to TERMSUBJ.n
                   1350: UP: line dashed from TOPIDOTM.c+(0,0.2) to TOPIDOTM.c+(0,1)
                   1351: DOWN: line dashed from TOPIDOTM.c-(0,0.2) to TOPIDOTM.c-(0,2.1)
                   1352: "host" at SUBJ.s-(0,0.2)
                   1353: "terminal" at TERM.s-(0,1.7)
                   1354: "terminal" at TERMSUBJ.s-(0,0.2)
                   1355: "        . . ." at SUBJ.e
                   1356: "        . . ." at TERMSUBJ.e
                   1357: .PE
                   1358: .ce
                   1359: Figure 11. Pi's three processes with two subject processes.
                   1360: .KE
                   1361: .PP
                   1362: How
                   1363: .CW Core
                   1364: finds the value of the subject's program counter is a simple example of
                   1365: inheritance and virtual functions at work.
                   1366: Consider part of the definition of
                   1367: .CW Core :
                   1368: .P1 0
                   1369: class Core {
                   1370:  ...
                   1371: public:
                   1372: virtual int  pc_index();        // register number for program counter
                   1373: virtual long reg_save(int r);   // address at which register r saved
                   1374: virtual long peek_long(long a); // fetch value from memory at address a
                   1375: virtual long pc();              // fetch value of program counter
                   1376:  ...
                   1377: };
                   1378: .P2
                   1379: The code here has been somewhat simplified to eliminate irrelevant complications;
                   1380: for example, it doesn't handle errors.
                   1381: .CW Core::pc()
                   1382: is target-independent, though it calls the target-dependent functions
                   1383: .CW pc_index() ,
                   1384: .CW reg_save() ,
                   1385: and 
                   1386: .CW peek_long()
                   1387: to obtain its result:
                   1388: .P1 0
                   1389: long Core::pc()
                   1390: {
                   1391:        return peek_long( reg_save( pc_index() ) );
                   1392: }
                   1393: .P2
                   1394: .CW pc_index() ,
                   1395: .CW reg_save()
                   1396: and
                   1397: .CW peek_long()
                   1398: must be implemented for each of
                   1399: .CW HostCore
                   1400: and
                   1401: .CW TermCore .
                   1402: For example, the program counter is register 15 on the VAX:
                   1403: .P1 0
                   1404: int HostCore::pc_index()
                   1405: {
                   1406:        return 15;
                   1407: }
                   1408: .P2
                   1409: .CW reg_save()
                   1410: and
                   1411: .CW peek_long()
                   1412: have the target-dependent code to find the location at which an arbitrary register
                   1413: has been saved and read the contents of an arbitrary memory location, respectively.
                   1414: .PP
                   1415: Note that
                   1416: .CW Core::pc()
                   1417: is virtual; the derived classes
                   1418: .I may
                   1419: also redefine it.
                   1420: So, even though
                   1421: .CW TermCore
                   1422: could inherit this functionally correct
                   1423: .CW pc()
                   1424: from
                   1425: .CW Core ,
                   1426: it has its own version.
                   1427: The base version reads memory every time it needs the value of the program
                   1428: counter.
                   1429: For the terminal, this would mean a remote procedure call to the terminal every
                   1430: time.
                   1431: As an optimization,
                   1432: .CW TermCore
                   1433: keeps a copy of the program counter, updating it each time the  state of the
                   1434: subject process is checked;
                   1435: .CW TermCore::pc()
                   1436: simply returns this cached value.
                   1437: The semantics are slightly different: if the program counter is manually patched
                   1438: while the program is halted,
                   1439: .CW TermCore::pc()
                   1440: will report the old value.
                   1441: In practice this discrepancy is less significant than the minor differences that
                   1442: arise from operating system idiosyncrasies.
                   1443: No user has ever noticed it.
                   1444: .PP
                   1445: Finding suitable target-independent base abstractions and implementing
                   1446: the derived classes took several months; simply building a new version of
                   1447: Pi specifically for the new target environment would have taken a few weeks.
                   1448: The
                   1449: .CW SymTab
                   1450: class was relatively straightforward, but tedious because of arbitrary
                   1451: differences in the detailed representation of the symbol tables.
                   1452: Two hard parts of finding an acceptable inheritance for
                   1453: .CW Core
                   1454: were byte ordering and function calling.
                   1455: The problems encountered with byte ordering are instructive \- the original
                   1456: scheme did not work on either machine, for reasons that no amount of forethought
                   1457: (by me) would have revealed.
                   1458: The scheme is to read memory from the subject process and create objects from
                   1459: which various types of data (byte, short, long, float, double) can be extracted
                   1460: later by clients of
                   1461: .CW Core .
                   1462: The VAX version failed when it tried to set up arbitrary bit patterns as
                   1463: candidates for extraction as floating point values; the operand of a floating
                   1464: move instruction must be a valid floating point number, of which 1 in 256 bit
                   1465: patterns is not.
                   1466: The WE32000 version did not work because the processor does not use the same
                   1467: byte ordering for code and data fetches; a multi-byte constant embedded in code
                   1468: is not the same bit pattern as that constant in data.
                   1469: Neither of these problems
                   1470: was hard to fix, but they indicate the difficulty of finding
                   1471: machine-independent abstractions for hardware.
                   1472: Harder was the interface through which the expression evaluator calls a function
                   1473: in the subject.
                   1474: The mechanisms for the host and terminal are quite different.
                   1475: For the host architecture, the debugger arranges that the subject process
                   1476: execute the function using the user's stack; in the terminal the function is
                   1477: executed directly by the debugger's agent process in the terminal on its own
                   1478: stack.
                   1479: The operation is broken down into a series of steps, each performed by
                   1480: a member of the respective derived
                   1481: .CW Core ,
                   1482: such that
                   1483: .CW Expr
                   1484: can detect no difference.
                   1485: The five steps supplied by
                   1486: .CW Core
                   1487: are: save context, allocate argument area, call function,
                   1488: determine location of returned result, restore context.
                   1489: .PP
                   1490: A further derivation from
                   1491: .CW HostCore
                   1492: has been added for examining core dumps from the
                   1493: .UX
                   1494: kernel on the VAX.
                   1495: .CW KernelCore
                   1496: differs very little from 
                   1497: .CW HostCore ;
                   1498: the major change is that memory fetches must be mapped through the
                   1499: kernel's page tables.
                   1500: A derivation of
                   1501: .CW Core
                   1502: for S-Net, a multi-processor computer based on the Motorola MC68000,
                   1503: is being implemented at the time of writing.
                   1504: The current
                   1505: .CW Core
                   1506: hierarchy is shown in Figure 12.
                   1507: .KS
                   1508: .PS
                   1509: .ft CW
                   1510: CORE: "Core"
                   1511: HOSTCORE: "HostCore" at CORE+(-0.75,-0.75)
                   1512: TERMCORE: "TermCore" at CORE+( 0.75,-0.75)
                   1513: SNETCORE: "SNetCore" at CORE+( 0,-0.75)
                   1514: line from CORE.s-(0,.1) to HOSTCORE.n+(0,.1)
                   1515: line from CORE.s-(0,.1) to TERMCORE.n+(0,.1)
                   1516: line from CORE.s-(0,.1) to SNETCORE.n+(0,.1)
                   1517: KERNCORE: "KernelCore" at HOSTCORE+(0,-0.75)
                   1518: line from HOSTCORE.s-(0,.1) to KERNCORE.n+(0,.1)
                   1519: .ft P
                   1520: .PE
                   1521: .ce
                   1522: Figure 12. Current \f(CWCore\fP hierarchy.
                   1523: .KE
                   1524: .PP
                   1525: A single debugger that handles multiple target environments
                   1526: has been a success for both the users and the implementer.
                   1527: The user is guaranteed to see the same interface when debugging in all
                   1528: environments.
                   1529: When changes are made to target-independent parts of Pi
                   1530: they are usually only tested for one target before being installed
                   1531: \- they almost always work correctly for the others.
                   1532: This was true of adding a trace history of breakpoints, for example.
                   1533: More complicated changes, involving target-dependent parts, take some time
                   1534: before a clean compilation can be achieved, because several derived classes must
                   1535: be changed consistently.
                   1536: It often takes days to get an error-free compilation.
                   1537: It is frustrating to be unable to test new code for machine X because
                   1538: the code for machine Y is out of date and cannot compile.
                   1539: The discipline introduced is that thought must be given to all target
                   1540: environments simultaneously; this results in earlier exposure of
                   1541: target environment inconsistencies.
                   1542: .NH
                   1543: Deficiencies in C++
                   1544: .PP
                   1545: Though indispensable in the construction of Pi, C++ is deficient in two respects.
                   1546: First, derived classes may inherit from only a single base class.
                   1547: Second, the benefits provided by classes come at the expense of considerable
                   1548: compilation overhead.
                   1549: .PP
                   1550: Though each class derived from
                   1551: .CW Core
                   1552: is a single step from its parent in the type hierarchy, the step embodies
                   1553: several independent changes: the processor, the operating system and the
                   1554: compiler.
                   1555: If Pi had to support all four target environments possible with
                   1556: operating systems A and B on processors P and Q,
                   1557: the class hierarchy would be that of Figure 13.
                   1558: As a result, each derived class would contain target-dependent code replicated in
                   1559: two others.
                   1560: .KS
                   1561: .PS
                   1562: .ft CW
                   1563: CORE: "Core"
                   1564: AP: "APCore" at CORE+(-1.5,-1)
                   1565: BP: "BPCore" at CORE+( -.5,-1)
                   1566: AQ: "AQCore" at CORE+(  .5,-1)
                   1567: BQ: "BQCore" at CORE+( 1.5,-1)
                   1568: line from CORE.s-(0,.1) to AP.n+(0,.1)
                   1569: line from CORE.s-(0,.1) to BP.n+(0,.1)
                   1570: line from CORE.s-(0,.1) to AQ.n+(0,.1)
                   1571: line from CORE.s-(0,.1) to BQ.n+(0,.1)
                   1572: .ft P
                   1573: .PE
                   1574: .ce
                   1575: Figure 13. Multiplicity of Derived Classes.
                   1576: .KE
                   1577: .LP
                   1578: This is a situation in which
                   1579: .I multiple
                   1580: .I inheritance
                   1581: could be used to eliminate replication.
                   1582: Under multiple inheritance a derived class may inherit from more than
                   1583: one base class.
                   1584: The derivation graph could be as shown in Figure 14.
                   1585: Target-dependent code would be confined to a single appearance in one of
                   1586: the base classes:
                   1587: .CW ACore ,
                   1588: .CW BCore ,
                   1589: .CW PCore
                   1590: and
                   1591: .CW QCore .
                   1592: The derived classes would need no additional code.
                   1593: .KF
                   1594: .PS
                   1595: .ft CW
                   1596: ACORE: "ACore"
                   1597: PCORE: "PCore" at ACORE+(1,0)
                   1598: BCORE: "BCore" at PCORE+(1,0)
                   1599: QCORE: "QCore" at BCORE+(1,0)
                   1600: AP: "APCore" at ACORE+(0,-1)
                   1601: BP: "BPCore" at PCORE+(0,-1)
                   1602: BQ: "BQCore" at BCORE+(0,-1)
                   1603: AQ: "AQCore" at QCORE+(0,-1)
                   1604: line from ACORE.s-(0,.1) to AP.n+(0,.1)
                   1605: line from ACORE.s-(0,.1) to AQ.n+(0,.1)
                   1606: line from BCORE.s-(0,.1) to BP.n+(0,.1)
                   1607: line from BCORE.s-(0,.1) to BQ.n+(0,.1)
                   1608: line from PCORE.s-(0,.1) to AP.n+(0,.1)
                   1609: line from PCORE.s-(0,.1) to BP.n+(0,.1)
                   1610: line from QCORE.s-(0,.1) to BQ.n+(0,.1)
                   1611: line from QCORE.s-(0,.1) to AQ.n+(0,.1)
                   1612: .ft P
                   1613: .PE
                   1614: .ce
                   1615: Figure 14. Multiple Inheritance.
                   1616: .KE
                   1617: .PP
                   1618: No pair of target environments have yet shared a common component,
                   1619: but time will certainly change that.
                   1620: Since C++ does not provide multiple inheritance, some other means of factoring
                   1621: the program must be found to avoid duplicating code.
                   1622: Of course, the success of multiple inheritance cannot be guaranteed without
                   1623: practical experience, but it is certainly worth pursuing.
                   1624: .PP
                   1625: A more severe problem that has been encountered is the cost of
                   1626: recompilation triggered by the modification of class declarations.
                   1627: The declaration of a class,
                   1628: .CW X ,
                   1629: places both the public and private components of its interface in
                   1630: a single syntactic unit.
                   1631: The declaration is usually stored in a ``header file,''
                   1632: .CW X.h .
                   1633: Two compilation problems arise with respect to
                   1634: .I clients
                   1635: of
                   1636: .CW X ,
                   1637: that is, other classes that depend only on
                   1638: .CW X 's
                   1639: public interface.
                   1640: Before processing the source text of a client the compiler must read the header
                   1641: file,
                   1642: .CW X.h .
                   1643: It therefore reads both the public and private declarations in
                   1644: .CW X ,
                   1645: even though the client's source is denied reference to the private declarations.
                   1646: (At the implementation level, the client might depend on this private information:
                   1647: to generate client code, the compiler needs to know the
                   1648: .I size
                   1649: of the private data in
                   1650: .CW X ,
                   1651: if an instance of
                   1652: .CW X
                   1653: appears in a client's stack frame.)
                   1654: If the private declarations in
                   1655: .CW X.h
                   1656: in turn depend on other header files, they must also be included, and so on.
                   1657: Compilation of the client therefore depends on many header files,
                   1658: even though the client does not need the information from those header files.
                   1659: This makes client compilation more expensive, because of the additional
                   1660: header files that must be processed.
                   1661: Moreover, using conventional Make |reference(feldman make) dependencies,
                   1662: client source is
                   1663: frequently recompiled after the modification of private declarations in
                   1664: classes unreferenced by the client.
                   1665: The subterfuge that partially overcomes this problem is unworthy of description.
                   1666: .NH
                   1667: Conclusion
                   1668: .PP
                   1669: Object-oriented programming in C++ has worked very well in Pi.
                   1670: Pi's ability to examine multiple processes over multiple target environments
                   1671: follows from the object-oriented model and class inheritance mechanism used in
                   1672: the implementation.
                   1673: At the outset the goal was to experiment with the user interface.
                   1674: Had an object-oriented programming language not been available,
                   1675: I doubt that Pi would have evolved beyond experiments at that level.
                   1676: .NH
                   1677: Acknowledgements
                   1678: .PP
                   1679: The success of Pi owes much to the ideas and software of
                   1680: Bjarne Stroustrup, Tom Killian and Rob Pike.
                   1681: Thanks also to Brian Kernighan, Doug McIlroy and John Linderman for
                   1682: their comments on drafts of this paper.
                   1683: .NH
                   1684: References
                   1685: .LP
                   1686: |reference_placement

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.