Chapter 2: The Use of the Library as a Debugging Tool (a) Introduction This chapter demonstrates debugging techniques made possible by the use of this library. We shall consider the case of debugging a text-based windowing system. Such a system requires saving areas of the text screen to memory buffers. These buffers are of at least two kinds: a buffer to hold the background of a window at the time it is opened, and a buffer to hold the current state of the window. The system presented here allows writing to a window even if it is in the background (that is, partially or totally covered by another window). In such a case writing to a window will affect the screen display only for those parts of the window not covered by at least one another window. A window buffer, since it holds the actual or possible contents of a rectangular area of the screen, may be thought of as a 2- dimensional array, with rows and columns corresponding to the rows and columns on the screen. Each element at a particular row and column position in such an array is the character-attribute byte pair (making an int) which occurs at the appropriate position in the video RAM when that window is displayed. (The attribute occupies the high byte of the word.) Furthermore, it is convenient to represent the video memory as a 2- dimensional array, which we can do by means of the array2_sim_- alloc() library function. This makes it possible to reference the character-attribute integer at a particular location in screen memory by using the term screen[row][column]. In what follows, first the goal will be stated, that is, the specification for the windowing system to be developed will be given. A program will then be presented which is designed to implement this goal. This first attempt has six serious bugs, and it will be shown how the debugging facilities in this library may be used to track these bugs down. (b) The Windowing System A full-featured windowing system is a fairly complex piece of work. Here we shall aim at developing a set of windowing functions which is simpler but which yet allow the following: (a) The programmer may open a window anywhere in the 25-row, 80- column, text screen, and of any size, such that the window fits entirely within the screen area. (b) The window may have either no frame or a frame of a specified width. Color attributes may be specified independently for the frame and the text area. (c) Windows may be opened on top of previously-opened windows. (d) The programmer can write text to an open window at a specified position relative to the top left corner of the text area of the window. This text will appear on-screen except when the text is written to a part of the window which is covered by another window. (e) A window may be closed, in which case it is replaced on-screen by what one would expect to find under it. In particular, when a window, which partially or wholly covers another window is closed, text which has been written to the underlying window will appear on-screen. Two restriction in this system, which would not be found in a full- featured windowing system, are: (i) Frames may consist only of the space character, with color attribute. (ii) Windows must be closed in the order in which they were opened. It should be remembered that the purpose here is less to present a windowing system than to illustrate techniques of debugging such a system. For our purposes here it may be supposed that, after much reflection on a method of implementing the above goal, the following program has been arrived at: 1: /* 2: * WIN1.C 3: * windowing functions with bugs 4: * 5: */ 6: 7: #include 8: #include 9: #include 10: #include 11: #include 12: 13: typedef struct wndw_t 14: Ú{ 15: ³int w_ur; /* upper row of window */ 16: ³int w_lc; /* left col of window */ 17: ³int w_num_rows; /* no. of window rows */ 18: ³int w_num_cols; /* no. of window cols */ 19: ³int frame_width; /* width of frame */ 20: ³int t_attr; /* text attribute */ 21: ³int fr_attr; /* frame attribute */ 22: ³/* values above are defined, below calculated */ 23: ³int w_lr; /* lower row of window */ 24: ³int w_rc; /* right col of window */ 25: ³int t_num_rows; /* number of text rows */ 26: ³int t_num_cols; /* number of text cols */ 27: ³int far * far *backgr; /* ptr to background */ 28: ³int far * far *buffer; /* ptr to window buffer */ 29: ³int far * far *text; /* ptr to text area */ 30: ³char handle; /* handle for window */ 31: À}; 32: 33: #define NUM_WINDOWS 4 34: #define HIDE_CURSOR FALSE 35: #define COLOR TRUE 36: /* */ 37: 38: #if COLOR 39: #define SCREEN 0xB8000000 40: #else 41: #define SCREEN 0xB0000000 42: #endif 43: 44: struct wndw_t wndw[NUM_WINDOWS] = 45: Ú{ 46: ³ Ú{ 47: ³ ³#if HIDE_CURSOR /* original screen */ 48: ³ ³0, 0, 25, 80, 0, 0, 0 /* window 0 */ 49: ³ ³/* text attribute is black on black */ 50: ³ ³#else 51: ³ ³0, 0, 25, 80, 0, 7, 0 52: ³ ³/* text attribute is white on black */ 53: ³ ³#endif 54: ³ À}, 55: ³ Ú{ 56: ³ ³5, 5, 10, 64, 2, 43, 112 /* window 1 */ 57: ³ À}, 58: ³ Ú{ 59: ³ ³10, 14, 8, 33, 1, 31, 79 /* window 2 */ 60: ³ À}, 61: ³ Ú{ 62: ³ ³6, 23, 13, 26, 0, 94, 0 /* window 3 */ 63: ³ À} 64: À}; 65: 66: int err_flag; /* global error flag */ 67: char far * far *screen_state; /* windows visible */ 68: int far * far *screen; /* screen as array */ 69: char msg[81]; 70: 71: void main(void); 72: int open_window(struct wndw_t far *w); 73: void w_clear(struct wndw_t far *w); 74: void refresh_window_display(struct wndw_t far *w); 75: int close_window(struct wndw_t far *w); 76: void w_print(struct wndw_t far *w, int row, 77: int col, char *str); 78: void press_a_key(int i); 79: 80: /*-----------*/ 81: void main(void) 82: { 83: int i,r; 84: char minus_one=-1; 85: 86: #if !COLOR 87: /* adjust attributes for monochrome monitor */ 88: for ( i=0; i 1 ) 123: ³ Ú{ 124: ³ ³/* write to preceding window */ 125: ³ ³w_print(&wndw[i-1],3,0,"This was written while" 126: ³ ³ " window partially covered."); 127: ³ ³refresh_window_display(&wndw[i-1]); 128: ³ ³press_a_key(i); 129: ³ À} 130: À} 131: 132: /* close the windows */ 133: for ( i=NUM_WINDOWS-1; i>=0; i-- ) 134: Ú{ 135: ³close_window(&wndw[i]); 136: ³if ( i > 1 ) 137: ³ press_a_key(i-1); 138: À} 139: 140: array_free(screen_state,&err_flag); 141: array_free(screen,&err_flag); 142: } 143: 144: /*---------------------------------*/ 145: int open_window(struct wndw_t far *w) 146: { 147: int r,c, char_attr; 148: static char last_wndw_handle = -1; 149: 150: /* first save background */ 151: w->backgr = (int far * far *)array_alloc(&err_flag, 152: sizeof(int),NULL,2,w->w_num_rows,w->w_num_cols); 153: 154: if ( err_flag ) 155: return ( err_flag ); 156: 157: /* */ 158: /* save background */ 159: for ( r=w->w_ur; r<=w->w_lr; r++ ) 160: Ú{ 161: ³/* 162: ³*/ 163: ³memcpy_f(w->backgr[r],&screen[r][w->w_lc], 164: ³ w->w_num_cols*sizeof(int),&err_flag); 165: ³/* 166: ³*/ 167: À} 168: 169: /* allocate space for window buffer and initialize */ 170: char_attr = (w->fr_attr<<8) + ' '; 171: w->buffer = (int far * far *)array_alloc(&err_flag, 172: sizeof(int),&char_attr,2,w->w_num_rows,w->w_num_cols); 173: 174: if ( err_flag ) 175: return ( err_flag ); 176: 177: w->w_lr = w->w_ur + w->w_num_rows - 1; 178: w->w_rc = w->w_lc + w->w_num_cols - 1; 179: w->t_num_rows = w->w_num_rows - 2*w->frame_width; 180: w->t_num_cols = w->w_num_cols - 4*w->frame_width; 181: 182: /* represent text area as 2-d array */ 183: w->text = 184: (int far * far *)array2_sim_alloc(w->buffer[0] 185: + w->frame_width*w->w_num_cols 186: + w->frame_width*2,w->w_num_rows,w->w_num_cols, 187: sizeof(int),NULL,&err_flag); 188: 189: w_clear(w); 190: /* give this window a handle */ 191: w->handle = ++last_wndw_handle; 192: 193: /* update screen state buffer */ 194: for ( r=w->w_ur; r<=w->w_lr; r++ ) 195: for ( c=w->w_lc; c<=w->w_rc; c++ ) 196: screen_state[r][c] = w->handle; 197: 198: /* copy new window to screen */ 199: refresh_window_display(w); 200: return ( 0 ); 201: } 202: 203: /* clears text area of window */ 204: /*-------------------------------*/ 205: void w_clear(struct wndw_t far *w) 206: { 207: int r; 208: 209: /* clear first line of text area of window buffer */ 210: memset_int_f(w->text[0],(w->t_attr<<8)|0x20, 211: w->t_num_cols,&err_flag); 212: 213: if ( !err_flag ) 214: for ( r=1; rw_num_rows; r++ ) 215: memcpy_f(w->text[r],w->text[0], 216: 2*w->t_num_cols,&err_flag); 217: } 218: 219: /*---------------------------------------------*/ 220: void refresh_window_display(struct wndw_t far *w) 221: { 222: register int r,c; 223: /* */ 224: 225: /* copy to screen those parts of the buffer 226: * which should be visible 227: */ 228: for ( r=w->w_ur; r<=w->w_lr; r++ ) 229: Ú{ 230: ³for ( c=w->w_lc; c<=w->w_rc; c++ ) 231: ³ Ú{ 232: ³ ³if ( screen_state[r][c] == -1 ) 233: ³ ³ Ú{ 234: ³ ³ ³screen_state[r][c] = w->handle; 235: ³ ³ ³/* */ 236: ³ ³ À} 237: ³ ³if ( screen_state[r][c] == w->handle ) 238: ³ ³ Ú{ 239: ³ ³ ³screen[r][c] = w->buffer[r][c]; 240: ³ ³ ³/* 241: ³ ³ ³*/ 242: ³ ³ À} 243: ³ À} 244: À} 245: } 246: 247: /*----------------------------------*/ 248: int close_window(struct wndw_t far *w) 249: { 250: register int r,c; 251: int i; 252: 253: /* copy background to screen */ 254: for ( r=w->w_ur; r<=w->w_lr; r++ ) 255: Ú{ 256: ³memcpy_f(&screen[r][w->w_lc],w->backgr[r], 257: ³ w->w_num_cols*sizeof(int),&err_flag); 258: ³/* */ 259: À} 260: 261: for ( r=w->w_ur; r<=w->w_lr; r++ ) 262: for ( c=w->w_lc; c<=w->w_rc; c++ ) 263: screen_state[r][c] = -1; 264: 265: for ( i=w->handle-1; i>0; i-- ) 266: refresh_window_display(&wndw[i]); 267: 268: array_free(w->text,&err_flag); 269: if ( !err_flag ) 270: Ú{ 271: ³array_free(w->buffer,&err_flag); 272: ³if ( !err_flag ) 273: ³ array_free(w->backgr,&err_flag); 274: À} 275: 276: return ( err_flag ); 277: } 278: 279: /*------------------------------*/ 280: void w_print(struct wndw_t far *w, 281: int row, 282: int col, 283: char *str) 284: { 285: int attr,j,s=strlen(str); 286: 287: /* determine number of bytes to copy 288: * so as not to write outside of text area 289: */ 290: if ( col + s > w->t_num_cols ) 291: s = w->t_num_cols - col; 292: 293: if ( s > 0 ) 294: Ú{ 295: ³for ( j=0; jtext[row][col+j] & 0xFF00; 298: ³ ³w->text[row][col+j] = attr | str[j]; 299: ³ À} 300: À} 301: } 302: 303: /*-------------------*/ 304: void press_a_key(int i) 305: { 306: w_print(&wndw[i], 307: wndw[i].t_num_rows-1,0,"Press a key ... "); 308: refresh_window_display(&wndw[i]); 309: getch(); 310: w_print(&wndw[i], 311: wndw[i].t_num_rows-1,0," "); 312: refresh_window_display(&wndw[i]); 313: } The Microsoft C Compiler was used in program development, and four Microsoft header files are included at lines 7-10. At line 11 the special Dolphin header file L_MEM.H is included. Some lines are blank comment lines to allow insertion of statements during debugging without changing the overall line numbering. The program compiles equally well (with no error messages) with the Turbo C++ Compiler, and almost everything said in this chapter is true regardless of which compiler is used. This program uses five functions from the Far Memory Manager library. Their function declarations are as follows: (i) void far *array_alloc(int *err_flag, unsigned int element_size, void *init_val, unsigned int num_dim, unsigned int n1, ...); This function dynamically allocates memory from the far heap and returns a pointer which may be cast appropriately so as to be used as if it were an array of num_dim dimensions (up to five dimensions are supported). Although pointers are not arrays, we can in practice treat the variable in which the return value is stored as if were an array name. Because of what array_alloc() does, the returned pointer can be subscripted num_dim times, and so we can speak of "elements" of the "array", as in a[i1][i2][i3] (where a is a variable holding the value returned by the function), and i1, i2, etc., as "indices" of the array. Furthermore, if init_val is not NULL the function initializes the array using the value pointed to by init_val (assumed to consist of element_size bytes). Most of the functions in the Far Memory Manager library take as an argument a pointer to an integer, passed as the address of an integer variable, and return an error code as the value of that variable. (ii) void far *array2_sim_alloc(void far *ptr, unsigned int nrows, unsigned int ncols, unsigned int size, void *init_val, int *err_flag); This function is used to represent the memory at ptr as a 2- dimensional array. It "simulates" the allocation of the data space performed by array_alloc() with num_dim == 2. In particular, the statements: int err_flag; screen = (int far * far *)array2_sim_alloc ((void far *)0xB8000000,25,80,1,NULL,&err_flag); allow us to treat the video screen (assuming page 0 with a color video adaptor) as a 2-dimensional array such that screen[row][col] is the character-attribute combination at the specified row and column on the screen. If the function succeeds (it requires the allocation in far memory of an array of pointers) then on return err_flag == 0, otherwise err_flag holds an error code (defined in L_MEM.H). (iii) void array_free(void far *a, int *err_flag); This function is used to free an "array" allocated using array_alloc(). It also frees the pointer array allocated by array2_sim_alloc(). (iv) void far *memcpy_f(void far *dest, void far *src, unsigned long count, int *err_flag); This function copies a region of far memory to some destination in far memory. The count bytes at src are copied to dest. Unlike the Microsoft C and Turbo C memcpy() functions the Dolphin memcpy_f() does error checking, and can detect if such a copy operation corrupts the far heap. (v) void far *memset_int_f(void far *ptr, int i, unsigned long count, int *err_flag); This function sets the count ints at ptr to the value i, and also does error checking. The design of the program above will now be explained. Recall that the purpose of this chapter is not so much to present a windowing system as to explain methods of debugging which are here illustrated by debugging this particular windowing system. The statement at line 44: struct wndw_t wndw[NUM_WINDOWS] = ... declares and initializes an array of NUM_WINDOWS structures. Each element in this array is a structure of the type wndw_t as defined in the program. The first seven items in the structure specify the qualities of a window to be opened: Position of window: w_ur (upper row) and w_rc (right column) are the screen coordinates of the top left corner of the window (which may be part of the frame). Size of window: The window has w_num_rows rows and w_num_cols columns. If the window has a frame then the number of rows and columns in the text area (the area within the frame ) will be fewer than the number of rows and columns in the window. Frame: The window may have a frame of a certain width. Since character positions appear twice as high as they are wide, the vertical parts of the frame are made up of double-spaces to ensure uniform width. If frame_width == 0 then the window lacks a frame. Color: The color attribute of the frame is fr_attr, and the color attribute of the text area is t_attr. The values of the five variables w_ur, w_lc, w_num_rows, w_num_cols and frame_width determine the values of w_lr, w_rc, t_num_rows, and t_num_cols. Three pointers are maintained for three buffers and each window has a handle which is a number in the range 0-255. The program is intended to exercise four windowing functions. These are functions to: (i) open a window (with or without a frame), (ii) clear the text area of a window, (iii) write a string of text at a specified row and column of the window (relative to the top left corner of the text area), and (iv) close a window (with the appearance of portions of any windows previously covered by this window). If the program shows that these functions work properly then they can be used in other applications. Since WIN1.C has serious bugs (discussed below) it is not supplied in executable form. The debugged version is on the disk as WIN2.EXE. The program is intended to perform as follows (a keypress separates each stage): (i) The first window appears, with "Window 1" written on each line with increasing indents and the "Press a key ..." prompt on the bottom line. (ii) The second window is opened with "Window 2" written on each line. (iii) A line of text, "This was written while window partially covered." is written to the first window, but this line is partially covered by the second window. (iv) The third window is opened, with "Window 3" on each line. (v) The same line of text is written to the second window, mostly concealed by window 3. (The screen now looks as in Figure 1.) (vi) The third window is closed, and window 2 is uncovered. (vii) The second window is closed, and window 1 is uncovered. (vii) Finally the first window is closed and the original screen is restored. The working of the program may be described briefly as follows: At line 101 a buffer is allocated to hold the screen state, which we represent as a 2-dimensional array of bytes such that screen_state- [row][column] is the handle of the window currently visible at the specified screen location. Then (at line 106) a pointer, screen, is returned by array2_sim_alloc() which allows us to treat the screen as a 2-dimensional array of ints. At line 109 the first window, which consists of the entire screen is opened. This saves the screen upon entry, for later restoration. Then each window is opened, text is written and a keypress awaited, as outlined above; thereafter the windows are closed in reverse order. The open_window() function at line 151 allocates a buffer for the background of the window, and saves the background. A buffer for the window is allocated at line 171. The window is cleared to the frame attribute by passing the address of char_attr (defined at line 170) as a pointer to the initialization value. Three pointers are set up which allows us to treat the background buffer, the current-state buffer, and the text area of the window as 2-dimensional arrays. These pointers are stored in the window structure. Proceeding now with the remainder of the open_window() function, the text area of the window is cleared to the t_attr attribute using w_clear(). The window is given a handle, and the screen state is updated to show that this window is now open. Finally the current-state buffer is copied to the video screen; it is at this point that the window actually becomes visible on the screen. The w_clear(), refresh_window_display() and w_print() functions require no explanation. In the close_window() function first the contents of the background buffer are copied to the screen. The screen state is updated to show that this area is now up for grabs by other windows. We work backwards through the previously opened windows, filling in the lacuna on the screen with material from the previous windows (updating the screen state at each point). Finally the allocated buffers for the window are freed. (c) The Debugging Process The program above may be compiled with either the Microsoft C compiler (Version 5.10/6.00) or the Turbo C/C++ Compiler. In each case there are no compilation errors. To create an executable file the object module must be linked with one of the .LIB files from the Far Memory Manager library. Unfortunately, despite the logical design of the window functions, when the program is run it hangs and is totally unresponsive to the keyboard. Debugging is called for. The debugging process occurs in nine stages. The process described below occurs when the Microsoft C Compiler (Version 6.00) is used. Some details might differ if the Turbo C/C++ Compiler were used, but not significantly. When HIDE_CURSOR, at line 34, is defined as TRUE, opening window 0 causes the attributes for the whole screen to be set to black on black, which normally renders the cursor invisible. During debugging HIDE_CURSOR is defined as FALSE so that if the program terminates with an error message it will be visible on the screen. Stage 1. The program is compiled and linked without error messages. When run, a green rectangle appears with a white band toward the right side and "a key ..." displayed down the left side. After one or two keypresses the system hangs and the following messages appear: memory control block(s) destroyed DOS error message: Memory allocation error Cannot load COMMAND, system halted The system must then be rebooted. This dreaded DOS error message is what normally appears when the far heap becomes corrupted by memory overwrites. The message in the first line is produced by the Dolphin library. Stage 2. To begin the actual debugging process we do two things: (i) Insert the following Dolphin function calls at lines 96-97: set_mem_log(TRUE); set_termination_log_output(PRINTER_OUTPUT,REPORT_1_ONLY); The first turns on the memory allocation log and the second says that if a critical memory error occurs (i.e. if the memory control blocks are overwritten) then the first memory log report should be sent to the printer (there are two kinds of reports available). TRUE, PRINTER_OUTPUT and REPORT_1_ONLY are defined in the Dolphin header file L_MEM.H. (ii) Use the _L_ macro on all error-reporting Dolphin functions. As explained above, most of the Dolphin functions take as an argument a pointer to an integer and return an error code as the value of that integer. For example, the function call (at line 140) which frees the array allocated to hold the screen state is as follows: array_free(screen_state,&err_flag); In the program above err_flag is a global integer variable. Its address is passed in many Dolphin functions. Upon return err_flag will be zero if no error occurred, otherwise it will hold a non- zero error code. The definitions of 27 codes for errors caught by the Dolphin functions are contained in L_MEM.H. The _L_ macro takes a statement as its argument, as in: _L_(array_free(screen_state,&err_flag);) The semi-colon is optional. This macro is defined in L_MEM.H as follows: #define _L_(st) \ { __line__=__LINE__; __file__=__FILE__; st; __line__=0; } What this does is simply to insert assignments to variables of the line number of the current statement and of the name of the source file. This allows reporting of the line number and source file if this statement generates an error. We now use the _L_ macro on all Dolphin functions which report an error in the err_flag variable. These occur at lines 101, 106, 140, 141, 151, 163, 171, 183, 210, 215, 256, 268, 271 and 273 in the program. Although the Dolphin functions will automatically catch a critical error generated by a statement whether or not the _L_ macro is used with that statement, its use is necessary if the offending statement is to be identified by source file and line number. When we compile and run this program vertical bars of multi-colored garbage appear on the screen, and after one keypress the following appears: At line 152 of file WIN2.C: memory control block(s) destroyed Sending report to printer ... DOS error message: Memory allocation error Cannot load COMMAND, system halted At lines 151-152 of the program is the statement: _L_(w->backgr = (int far * far *)array_alloc(&err_flag, sizeof(int),NULL,2,w->w_num_rows,w->w_num_cols);) In addition we receive the printed report shown below. The numbers in square brackets are line numbers in the source file. Entries marked with ">>>" are the constitutent memory block allocations that accomplish the array allocation requested by the Dolphin function call at the stated line in the program. Number of entries in the memory allocation log: 21 1 [103] "Allocate 2-dimensional array (element size = 1 byte)." Done. 2000 elements in array; data space occupies 2000 bytes. 2 >>> "Allocate 2000 bytes." 125 (0x7D) paragraphs (2000 bytes) allocated at 3CFD:0000. Address of first byte beyond block is 3D7A:0000. 3 >>> "Allocate 320 bytes." 20 (0x14) paragraphs (320 bytes) allocated at 3D7E:0000. Address of first byte beyond block is 3D92:0000. 4 [107] "Allocate 1-dimensional array (element size = 4 bytes)." Done. 80 elements in array; data space occupies 320 bytes. 5 >>> "Allocate 320 bytes." 20 (0x14) paragraphs (320 bytes) allocated at 3D99:0000. Address of first byte beyond block is 3DAD:0000. 6 [152] "Allocate 2-dimensional array (element size = 2 bytes)." Done. 2000 elements in array; data space occupies 4000 bytes. 7 >>> "Allocate 4000 bytes." 250 (0xFA) paragraphs (4000 bytes) allocated at 3DB4:0000. Address of first byte beyond block is 3EAE:0000. 8 >>> "Allocate 100 bytes." 7 (0x7) paragraphs (112 bytes) allocated at 3EB2:0000. Address of first byte beyond block is 3EB9:0000. 9 [172] "Allocate 2-dimensional array (element size = 2 bytes)." Done. 2000 elements in array; data space occupies 4000 bytes. 10 >>> "Allocate 4000 bytes." 250 (0xFA) paragraphs (4000 bytes) allocated at 3EC0:0000. Address of first byte beyond block is 3FBA:0000. 11 >>> "Allocate 100 bytes." 7 (0x7) paragraphs (112 bytes) allocated at 3FBE:0000. Address of first byte beyond block is 3FC5:0000. 12 [187] "Allocate 1-dimensional array (element size = 4 bytes)." Done. 25 elements in array; data space occupies 100 bytes. 13 >>> "Allocate 100 bytes." 7 (0x7) paragraphs (112 bytes) allocated at 3FCC:0000. Address of first byte beyond block is 3FD3:0000. 14 [152] "Allocate 2-dimensional array (element size = 2 bytes)." Done. 640 elements in array; data space occupies 1280 bytes. 15 >>> "Allocate 1280 bytes." 80 (0x50) paragraphs (1280 bytes) allocated at 3FDA:0000. Address of first byte beyond block is 402A:0000. 16 >>> "Allocate 40 bytes." 3 (0x3) paragraphs (48 bytes) allocated at 402E:0000. Address of first byte beyond block is 4031:0000. 17 [172] "Allocate 2-dimensional array (element size = 2 bytes)." Done. 640 elements in array; data space occupies 1280 bytes. 18 >>> "Allocate 1280 bytes." 80 (0x50) paragraphs (1280 bytes) allocated at 4038:0000. Address of first byte beyond block is 4088:0000. 19 [11040] Invalid action parameter: 11040 Log entry corrupted. This gives us something to think about. The number 11040 appears accidentally (since the log report is overwritten). In hexadecimal this number is 0x2B20, the space character (32) combined with the text attribute value (43) of window 1. We can conclude that the w_clear() function is writing to a region of memory that it should not be writing to. The sequence of line numbers in the log is: 103, 107, 152, 172, 187, 152, 172 When we consult the source code we see that lines 101-103 and 106-107 are the places where (i) a 2-dimensional array is allocated for the screen state buffer and (ii) a pointer is established that allows the screen to be represented as a 2-dimensional array (this is done by the allocation of a 1-dimensional array of pointers into the video memory). Lines 151-152, 171-172 and 183-187 are the places where (i) a 2- dimensional array is allocated to save the background when a window is opened, (ii) a 2-dimensional array is allocated for the window buffer and (iii) a pointer is established that allows the text area of the window to be represented as a 2-dimensional array (again this requires the allocation of a 1-dimensional array). The sequence 152, 172, 187 occurs when window 0 (covering the whole screen) is opened. The second occurs when window 1 is opened, but part of the memory allocation log is overwritten, and we are not sure of what happens with window 1 after 172. We have a clue, however, in that a critical error is detected when the statement at line 152 is executed. This must be when window 2 is opened. Since the error was detected at 152, the error almost certainly occurred either there or at the memory allocation immediately preceding it at 187. Thus we are led to scrutinize these two statements carefully. When we do this we see that the statement at line 183-187: w->text = (int far * far *)array2_sim_alloc(w->buffer[0] + w->frame_width*w->w_num_cols + w->frame_width*2, w->w_num_rows,w->w_num_cols,sizeof(int),NULL,&err_flag); should really be: w->text = (int far * far *)array2_sim_alloc(w->buffer[0] + w->frame_width*w->w_num_cols + w->frame_width*2, w->t_num_rows,w->w_num_cols,sizeof(int),NULL,&err_flag); The pointer buffer[0], in the structure pointed to by w, points to the start of the first row of the window buffer. To get a pointer to the start of the text area of the buffer we must add the number of columns in the window times the width of the frame and then add twice the width of the frame. The text area, as visible on-screen, consists of t_num_rows rows and t_num_cols columns, but to represent it as a 2-dimensional array we must allow the rows of the array to include also the frame at the end of the line and the frame at the beginning of the next line. Thus the array has t_num_rows rows, but the length of each row is not t_num_cols but w_num_cols. Stage 3. We replace w->w_num_rows in line 187 with w->t_num_rows and proceed to compile and run the program. Different things may happen on different occasions. Often a green rectangle appears with "Window 1" displayed twice, then garbage at different places on the screen. With each keypress the screen changes in accord with the expected positions and sizes of the windows, but the rectangular areas are filled with multi-colored garbage, which finally fills the entire screen. The system does not hang, so apparently the memory control blocks are not being overwritten (i.e. a critical error is not occurring), a small consolation. Previously the Dolphin functions had been checking only for a critical error. These functions are capable of returning any of 27 different error codes, and it is possible to specify any of these error codes (or any subset of the 27) as "termination errors". This means that the if a Dolphin function detects such an error it will terminate the program, print an error message and (optionally) print a memory allocation log report. This is done by means of the Dolphin function: int set_termination_errors(int *term_errors); This takes a pointer to a null-terminated array of ints. Each value in that array is then assumed to be a termination error code. If, however, the pointer term_errors points to the value ALL_ERRORS (defined as -32767 in L_MEM.H) then all errors are treated as termination errors. So we can change line 83 to: int i,r,term_errors=ALL_ERRORS; and insert at line 95: set_termination_errors(&term_error); Now the Dolphin functions will cause the program to terminate when any of the 27 errors is detected. We can also turn off array expansion by changing the value of the parameter in set_array_- expansion() to FALSE. Lines 95-98 of the program are now as follows: set_termination_errors(&term_error); set_mem_log(TRUE); set_termination_log_output(PRINTER_OUTPUT,REPORT_1_ONLY); set_array_expansion(FALSE); When we compile and run the program one of the following messages appears (since the program does not always behave in exactly the same way): At line 216 of file WIN3.C memcpy or memset address at or above upper bound Sending report to printer ... At line 216 of file WIN3.C memcpy or memset address below lower bound Sending report to printer ... Since we have turned off array expansion, the log sent to the printer is now as below. Number of entries in the memory allocation log: 8 1 [103] "Allocate 2-dimensional array (element size = 1 byte)." Done. 2000 elements in array; data space occupies 2000 bytes. 2 [107] "Allocate 1-dimensional array (element size = 4 bytes)." Done. 80 elements in array; data space occupies 320 bytes. 3 [152] "Allocate 2-dimensional array (element size = 2 bytes)." Done. 2000 elements in array; data space occupies 4000 bytes. 4 [172] "Allocate 2-dimensional array (element size = 2 bytes)." Done. 2000 elements in array; data space occupies 4000 bytes. 5 [187] "Allocate 1-dimensional array (element size = 4 bytes)." Done. 25 elements in array; data space occupies 100 bytes. 6 [152] "Allocate 2-dimensional array (element size = 2 bytes)." Done. 640 elements in array; data space occupies 1280 bytes. 7 [172] "Allocate 2-dimensional array (element size = 2 bytes)." Done. 640 elements in array; data space occupies 1280 bytes. 8 [187] "Allocate 1-dimensional array (element size = 4 bytes)." Done. 6 elements in array; data space occupies 24 bytes. The sequence of line numbers is now: 103, 107, 152, 172, 187, 152, 172, 187 and the program is terminated sometime after the third of the allocations involved in opening window 1. Line 216, where the error is detected, occurs within the function w_clear() which is called at line 189. At lines 214-216 is the for-loop: for ( r=1; rw_num_rows; r++ ) _L_(memcpy_f(w->text[r],w->text[0], 2*w->t_num_cols,&err_flag);) The Dolphin memcpy_f() and memset_int_f() functions check that the destination address is not outside of certain bounds. The default upper bound is E000:0000 and the default lower bound is the bottom of the far heap. (There are Dolphin functions to set the upper and lower bounds to other values if desired.) In this case memcpy_f() has apparently attempted to copy to an address above E000:0000 or to an address below the bottom of the far heap. (There is a Dolphin function to find the address of the bottom of the far heap, but normally there is no need to know what this is.) When we scrutinize this for-loop we see that the limit for r should be not w->w_num_rows but rather w->t_num_rows. Silly mistake! We are clearing only the text area, and the text area has t_num_rows rows not w_num_rows rows. Stage 4. We replace w->w_num_rows in line 214 with w->t_num_rows and proceed to compile and run the program, after having turned off the memory log by changing line 96 to: set_mem_log(FALSE); As at Stage 3, rectangles of the size of the three windows appear, with a couple of lines of text at the top and then multi-colored garbage. The system does not hang. Our only clue is that garbage is being written to the screen, so we should look at those places in the program which directly affect the screen display. The program writes to the screen only in one function, namely, the refresh_window_display() function. Further- more, there is only one statement in that function that writes to the screen, the statement at line 240: screen[r][c] = w->buffer[r][c]; Could there be a problem with the indices? The Dolphin library has a function for checking array bounds when reading a value from an array, namely: void far *array_get(int *err_flag,void far *a,int n1,...); This says: Return a far pointer to array a[n1]..., and check that the indices are OK. An error code will be returned in the integer pointed to by err_flag if an attempt is made to read a value from outside the array's data space. If no error occurs then the function returns a far pointer to the value (which should then be typecast appropriately). Thus we can replace the assignment above with: screen[r][c] = *(int far *)array_get(&err_flag,w->buffer,r,c); When we compile and run the program this error message appears: At line 241 of file WIN4.C: invalid array index We are now sure that the indices r and c are invalid, and scrutinizing this part of the program reveals that instead of r and c we should have r-(w->w_ur) and c-(w->lc). This is because the initial values of the r and c for-loop indices are w->w_ur and w->lc respectively, and the character-attribute int being copied to screen[w->w_ur][w->w_lc] should be w->buffer[0][0], and so on. Stage 5. Using the corrected statement (at line 240): _L_(screen[r][c] = *(int far *)array_get(&err_flag,w->buffer, r-(w->w_ur),c-(w->lc));) we compile and run the program. Now there is definite progress! The windows are opening correctly, with the frames appearing as expected, and the text being displayed properly. But when the windows are closed, garbage appears, and finally the screen is filled with multi-colored garbage. Clearly the place to check next is the code for close_window(). The code for this function begins with the for-loop at line 254: /* copy background to screen */ for ( r=w->w_ur; r<=w->w_lr; r++ ) _L_(memcpy_f(&screen[r][w->w_lc],w->backgr[r], w->w_num_cols*sizeof(int),&err_flag);) There is a Dolphin function for checking one or more array indices, which we can apply to the term w->backgr[r]. The function is declared as follows: int array_bounds_check(int *err_flag, void far *a, unsigned int num_to_check, int n1, ...); This says: Check the specified number of indices (num_to_check) for array a, using index values n1, ... An error code is returned in *err_flag. In this case we can insert the statement: array_bounds_check(&err_flag,w->backgr,1,r); in the for-loop, which becomes: for ( r=w->w_ur; r<=w->w_lr; r++ ) { _L_(array_bounds_check(&err_flag,w->backgr,1,r);) _L_(memcpy_f(&screen[r][w->w_lc],w->backgr[r], w->w_num_cols*sizeof(int),&err_flag);) } When we compile and run this program, the windows open correctly as before, and when we press a key to close window 3 the top half fills with garbage - but then the program terminates with the message: At line 256 of file WIN5.C: invalid array index Clearly r is the wrong index to use. The first row of the background buffer that we wish to restore to the address &screen[r][w->w_lc] in video memory is not w->backgr[w->w_ur] but w->backgr[0]. Thus w->backgr[r] should be replaced by w->backgr[r- (w->w_ur)]. Stage 6. We change the for-loop at lines 254-259 appropriately: for ( r=w->w_ur; r<=w->w_lr; r++ ) { _L_(array_bounds_check(&err_flag,w->backgr,1,r-(w->w_ur));) _L_(memcpy_f(&screen[r][w->w_lc],w->backgr[r-(w->w_ur)], w->w_num_cols*sizeof(int),&err_flag);) } and recompile and run the program. This time the previous error message does not appear but again the windows, when closed, are filled with multi-colored garbage, and finally the whole screen is so filled. Could it be that the background is not being saved properly when the window is opened? To test this we can use the hexdump_f() function in the Dolphin library to dump a region of far memory. The declaration for this function is: void hexdump_f(char *remark, void far *start, unsigned long num_bytes, FILE *output, int *err_flag); This dumps num_bytes bytes at start to the specified output (screen, printer or disk file), optionally preceded by a remark. An error code is returned in *err_flag. So we replace the for-loop at line 159, in the open_window() function, with the following: for ( r=w->w_ur; r<=w->w_lr; r++ ) { if ( r==w->w_ur ) hexdump_f("Before save",w->backgr[r],64L,stdprn,&err_flag); _L_(memcpy_f(w->backgr[r],&screen[r][w->w_lc], w->w_num_cols*sizeof(int),&err_flag);) if ( r==w->w_ur ) hexdump_f("After save",w->backgr[r],64L,stdprn,&err_flag); } When the program is recompiled and run the hexdump below is sent to the printer: Before save 3D9A:0000 35 FD 01 FB FD 0F FD 04 - FD 0A FE 10 FB FD 31 FD 5.............1. 3D9A:0010 02 FD 01 FD 00 FD 01 FD - 00 FB FD 31 FD 02 FD 01 .... ... ..1.... 3D9A:0020 FD 00 FD 02 FC FF FF FF - FF FB FD F1 FE 10 FB FE . .............. 3D9A:0030 35 FD 0B FB FE 35 FD 06 - FB FE 35 FD 07 00 C0 FD 5....5....5.. .. After save 3D9A:0000 20 07 20 07 20 07 66 07 - 69 07 6C 07 65 07 6E 07 . . .f.i.l.e.n. 3D9A:0010 61 07 6D 07 65 07 20 07 - 28 07 72 07 65 07 71 07 a.m.e. .(.r.e.q. 3D9A:0020 75 07 69 07 72 07 65 07 - 64 07 29 07 20 07 2D 07 u.i.r.e.d.). .-. 3D9A:0030 20 07 74 07 68 07 65 07 - 20 07 6E 07 61 07 6D 07 .t.h.e. .n.a.m. Clearly the background is being saved to the background buffer, so apparently this is not the source of the bug. But wait! Shouldn't this before/after hexdump have been printed four times, once for each window that is opened? Yes, so in fact the backgrounds for windows 1-3 are not being saved at all! Closer inspection of the code in open_window() reveals that the limit of the for-loop index r at line 159, namely w->w_lr, is not defined until line 177! It presumably has the value 0 at line 159, which is why the statements in the body of the for-loop at line 159 are executed for window 0 (for which w_ur has the value 0) but not for the other windows (which have a non-zero w_ur value). Stage 7. We move line 177: w->w_lr = w->w_ur + w->w_num_rows - 1; to line 157, preceding the for-loop. When this program is compiled and run the hexdump below is printed, showing that the background appears to be saved correctly for windows 0 and 1. Before save 3D9A:0000 41 54 41 00 53 74 61 63 - 6B 20 41 6C 6C 6F 63 61 ATA Stack Alloca 3D9A:0010 74 69 6F 6E 20 3D 20 25 - 75 20 62 79 74 65 73 0D tion = %u bytes. 3D9A:0020 0A 00 2A 2A 2A 2A 20 50 - 41 52 53 45 20 44 45 46 . **** PARSE DEF 3D9A:0030 49 4E 49 54 49 4F 4E 53 - 20 46 49 4C 45 20 2A 2A INITIONS FILE ** After save 3D9A:0000 20 07 20 07 20 07 20 07 - 20 07 20 07 20 07 20 07 . . . . . . . . 3D9A:0010 20 07 20 07 20 07 20 07 - 20 07 20 07 20 07 20 07 . . . . . . . . 3D9A:0020 20 07 20 07 20 07 20 07 - 20 07 20 07 20 07 20 07 . . . . . . . . 3D9A:0030 20 07 20 07 20 07 20 07 - 20 07 20 07 20 07 20 07 . . . . . . . . Before save 3FA8:0280 00 00 24 00 00 00 00 00 - 00 00 00 00 00 00 00 00 $ 3FA8:0290 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 3FA8:02A0 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 3FA8:02B0 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 After save 3FA8:0280 20 07 20 07 20 07 20 07 - 20 07 20 07 20 07 20 07 . . . . . . . . 3FA8:0290 20 07 20 07 20 07 20 07 - 20 07 20 07 20 07 20 07 . . . . . . . . 3FA8:02A0 20 07 20 07 20 07 20 07 - 20 07 20 07 20 07 20 07 . . . . . . . . 3FA8:02B0 20 07 20 07 20 07 20 07 - 20 07 20 07 20 07 20 07 . . . . . . . . But now we receive the message: At line 164 of file WIN7.C: memcpy or memset address below lower bound The offending statement at lines 163-164 is: _L_(memcpy_f(w->backgr[r],&screen[r][w->w_lc], w->w_num_cols*sizeof(int),&err_flag);) Having learned something from tracking down the previous bugs we immediately suspect the array indices. We could introduce the statement: array_bounds_check(&err_flag,w->backgr,1,r); to check on the array index r in the destination address w->backgr[r], but there is no need to do this because we see immediately that it should be w->backgr[r-(w->w_ur)]. Stage 8. With the for-loop at line 159 now as follows: w->w_lr = w->w_ur + w->w_num_rows - 1; /* save background */ for ( r=w->w_ur; r<=w->w_lr; r++ ) { if ( r==w->w_ur ) hexdump_f("Before save",w->backgr[r-(w->w_ur)], 64L,stdprn,&err_flag); _L_(memcpy_f(w->backgr[r-(w->w_ur)],&screen[r][w->w_lc], w->w_num_cols*sizeof(int),&err_flag);) if ( r==w->w_ur ) hexdump_f("After save",w->backgr[r-(w->w_ur)], 64L,stdprn,&err_flag); } we compile and run the program. Four before/after hex dumps are now printed, one for each of the four windows and finally everything works properly! All bugs apparently have been eliminated. Stage 9. Here we remove the Dolphin functions which were introduced during debugging. We don't have to remove the _L_ macro since it can be disabled by placing at line 36: #define DISABLE_L_MACRO The effect of this is that _L_(st) is now equivalent to its argument, st. The defined value of HIDE_CURSOR at line 34 should be changed to TRUE. This will generally have the effect of hiding the cursor, although it may not work with some hardware. If it were desired to remove all Dolphin error-checking then line 83 could be changed to: int i,r,term_errors=NO_ERROR; Lines 96-98 can be removed, since these concern only the memory allocation log, which is not required in the debugged version of the program. The hexdump functions in the for-loop at line 159 are removed. The line at 240 with the array_get() function: _L_(screen[r][c] = array_get(&err_flag, w->buffer,r-(w->w_ur),c-(w->w_lc));) is replaced (for the sake of speed) by: screen[r][c] = w->buffer[r-(w->w_ur)][c-(w->w_lc)]; Line 256: _L_(array_bounds_check(&err_flag,w->backgr,1,r-(w->w_ur));) is removed. We have now arrived at the final, debugged, form of the program. When compiled and run it functions as desired. Six bugs in the original program were identified. They are listed below in line number order, with the changes made to the original program. (i) The argument w->backgr[r] in the memcpy_f() function at line 163 was replaced by w->backgr[r-(w->w_ur)]. This bug was discovered when the Dolphin memcpy_f() realized that it was acting improperly and displayed the error message: At line 164 of file WIN7.C: memcpy or memset address below lower bound (ii) Line 177 in the program: w->w_lr = w->w_ur + w->w_num_rows - 1; was moved to line 157, prior to the for-loop at line 159. This bug was discovered by using the hexdump_f() function to verify that the background for windows 1, 2 and 3 were not being saved when these windows were opened. (iii) The argument w->w_num_rows in the array2_sim_alloc() function at line 183 was replaced by w->t_num_rows. This bug was discovered by reflecting upon the Dolphin error message: At line 152 of file WIN2.C: memory control block(s) destroyed combined with information obtained from the memory allocation log report. (iv) The index limit w->w_num_rows in the for-loop at line 214 was replaced by w->t_num_rows. This bug was found as a result of the Dolphin error message: At line 216 of file WIN3.C: memcpy or memset address at or above upper bound (v) The r-value w->buffer[r][c] at line 239 was replaced by the value w->buffer[r-(w->w_ur)][c-(w->w_lc)]. This bug was found by using the array_get() function, which produced the error message: At line 240 of file WIN4.C: invalid array index (vi) The argument w->backgr[r] in the memcpy_f() function at line 256 was replaced by w->backgr[r-(w->w_ur)]. This bug was found by using the function array_bounds_check(), which produced the error message: At line 256 of file WIN5.C: invalid array index Thus it has been shown how the use of this library can facilitate debugging of applications involving complex buffer management in far memory. Copyright 1991 Dolphin Software