|
|
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
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.