home *** CD-ROM | disk | FTP | other *** search
- /* Copyright (C) 1989, 1990 Aladdin Enterprises. All rights reserved.
- Distributed by Free Software Foundation, Inc.
-
- This file is part of Ghostscript.
-
- Ghostscript is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY. No author or distributor accepts responsibility
- to anyone for the consequences of using it or for whether it serves any
- particular purpose or works at all, unless he says so in writing. Refer
- to the Ghostscript General Public License for full details.
-
- Everyone is granted permission to copy, modify and redistribute
- Ghostscript, but only under the conditions described in the Ghostscript
- General Public License. A copy of this license is supposed to have been
- given to you along with Ghostscript so you can know your rights and
- responsibilities. It should be in a file named COPYING. Among other
- things, the copyright notice and this notice must be preserved on all
- copies. */
-
- /* trace.c */
- /* Tracing package for Turbo C */
- #include <stdio.h>
- #include <ctype.h>
- #include <stdlib.h>
- #include <stdarg.h>
- #include <dos.h>
- #include <setjmp.h>
- #include "memory_.h"
- typedef unsigned char byte;
-
- /*
- * NOTE: this package is extremely specialized. It works only
- * with the Borland Turbo C 2.0 compiler, with large code models,
- * on 80x86 and 80x88 processors and compatibles with x>0.
- * Use at your own risk.
- */
-
- /*
- * To trace a procedure, call
- * trace(proc, "procname", NULL, retsize);
- * or
- * trace_name("_PROCNAME", mapfile, NULL, retsize);
- * where retsize is sizeof the return value type.
- * To print arguments formatted (with vprintf), use
- * trace(proc, "procname", "argformat", retsize);
- * or
- * trace_name("_PROCNAME", mapfile, "argformat", retsize);
- *
- * If trace_flush_flag is true, for crash-prone programs,
- * the trace output is flushed after every call or return.
- *
- * Trace output goes to the file trace_output_file, or to stdout
- * if trace_output_file is not initialized.
- */
-
- /*
- * To map through a symbol (.MAP) file, open the file with
- * mapfile = trace_open_map(filename, &reloc);
- * which returns NULL if the map file isn't available.
- * reloc is set to the relocation offset (i.e., the difference between
- * actual procedure addresses and the values in the map file);
- * if &reloc = NULL, nothing is stored. Then call
- * sym = trace_next_symbol(&segaddr, mapfile);
- * until it returns NULL.
- * To look up a symbol in the file, call
- * segaddr = trace_find_symbol(name, mapfile);
- */
-
- #ifdef TRACEDEBUG
-
- /* Main program for testing */
- main()
- { int fib(int);
- char far *reloc;
- FILE *trace_open_map();
- FILE *mapf = trace_open_map("trace.map", NULL);
- char far *pfib;
- char far *trace_find_symbol();
- pfib = trace_find_symbol("_FIB", mapf);
- printf("fib at %lx\n", pfib);
- trace_name("_FIB", mapf, "(%d)", sizeof(int));
- fib(5);
- }
- int
- fib(n)
- int n;
- { if ( n <= 2 ) return 1;
- return fib(n-1) + fib(n-2);
- }
- /* End of main program */
-
- #endif
-
- /* ------ Symbol file mapping ------ */
-
- #define max_sym 40
- static char sym[max_sym+1];
-
- FILE *
- trace_open_map(mapname, preloc)
- char *mapname;
- long *preloc;
- { FILE *mapf = fopen(mapname, "rt");
- char *trace_find_symbol(char *, FILE *);
- if ( mapf == NULL ) return NULL; /* can't open */
- if ( preloc != NULL )
- { extern main();
- char far *main_addr = trace_find_symbol("_MAIN", mapf);
- if ( main_addr == NULL )
- { fclose(mapf); /* can't find _main */
- return NULL;
- }
- *preloc = (long)main - (long)main_addr;
- }
- return mapf;
- }
-
- char *
- trace_next_symbol(paddr, mapf)
- char far **paddr;
- FILE *mapf;
- { /* A symbol definition line looks like this: */
- /* ssss:oooo namestring */
- /* with exactly 7 spaces between the address and the name. */
- while ( 1 )
- { char ch;
- unsigned parm[2];
- static char term[2] = ": ";
- int pi, i;
- if ( (ch = getc(mapf)) != ' ' ) goto skip;
- for ( pi = 0; pi < 2; pi++ )
- { unsigned p = 0;
- for ( i = 0; i < 4; i++ )
- { ch = getc(mapf);
- if ( !isxdigit(ch) ) goto skip;
- p = (p << 4) + (isdigit(ch) ? ch - '0' :
- (ch - 'A' + 10) & 0xf);
- }
- if ( (ch = getc(mapf)) != term[pi] ) goto skip;
- parm[pi] = p;
- }
- for ( i = 1; i < 7; i++ )
- if ( (ch = getc(mapf)) != ' ' ) goto skip;
- i = 0;
- while ( (ch = getc(mapf)) != '\n' )
- { if ( i == max_sym ) goto skip; /* name too long */
- sym[i++] = ch;
- }
- sym[i] = 0;
- /* Success. Adjust the segment and return. */
- *paddr = MK_FP(parm[0] + FP_SEG(abort), parm[1]);
- return sym;
- skip: /* Syntax didn't match, skip the rest of the line. */
- while ( ch != '\n' )
- { if ( ch == (char)EOF && feof(mapf) ) return NULL;
- ch = getc(mapf);
- }
- }
- }
-
- /* Look up a symbol in a file */
- char far *
- trace_find_symbol(name, mapf)
- char *name;
- FILE *mapf;
- { char *s;
- char far *r;
- rewind(mapf);
- while ( (s = trace_next_symbol(&r, mapf)) != NULL )
- if ( !strcmp(s, name) ) return r;
- return (char far *)NULL;
- }
-
- /* ------ Instruction and register set ------ */
-
- /* Opcodes */
- #define op_ADD_IMM 0x81
- #define op_ADD_IMM8 0x83
- #define op_ADD_IMMX_X 0
- #define op_CALL_FAR 0x9a
- #define op_CMP_MR 0x39
- #define op_CMP_RM 0x3b
- #define op_ENTER 0xc8
- #define op_INC_REG 0x40
- #define op_JMP_FAR 0xea
- #define op_JMP_FAR_IND 0xff
- #define op_JMP_FAR_IND_X 0x28
- #define op_MOV_MR 0x8b
- #define op_MOV_RM 0x89
- #define op_NOP 0x90
- #define op_POP_REG 0x58
- #define op_PUSH_IMM 0x68
- #define op_PUSH_REG 0x50
- #define op_RETF 0xcb
- #define op_SUB_IMM 0x81
- #define op_SUB_IMM8 0x83
- #define op_SUB_IMMX_X 0x28
- /* Register numbers */
- #define r_AX 0
- #define r_CX 1
- #define r_DX 2
- #define r_BX 3
- #define r_SP 4
- #define r_BP 5
- #define r_SI 6
- #define r_DI 7
-
- /*
- * The standard entry sequence generated by Turbo C consists of:
- * If there are no arguments and no local variables, nothing;
- * if there are args but no local variables, PUSH BP, MOV BP,SP;
- * if there are local variables, ENTER size,0, or
- * PUSH BP, MOV BP,SP, SUB SP,<size>
- * Optionally, PUSH SI;
- * if the PUSH SI is present, optionally PUSH DI.
- * If the frame is larger than a certain size (0x80?),
- * CMP BP,SP; JB .+8.
- * CMP [_STKLEN],SP; JA <ok>.
- */
-
- /* ------ Stack parsing ------ */
-
- /* Record for BP and return */
- typedef struct bp_ret_s {
- unsigned _ss *bp;
- char far *ret;
- } bp_ret;
-
- /* Forward declarations */
- char *stack_next_frame(char *);
- void fprint_block(int *, int, FILE *);
-
- /* Get the address of the caller's frame */
- char *
- stack_top_frame()
- { jmp_buf buf;
- setjmp(buf); /* acquire registers */
- return stack_next_frame((char *)(char _ss *)buf[0].j_bp);
- }
-
- /* Get the return address of a frame. */
- unsigned long
- stack_return(bp)
- char *bp;
- { return (unsigned long)*(char far **)(bp + sizeof(char _ss *));
- }
-
- /* Get the address of the next higher frame, */
- /* or 0 if there is none. */
- char *
- stack_next_frame(bp)
- char *bp;
- { char _ss *nbp = *(char _ss **)bp;
- if ( (char *)nbp < bp ) return 0;
- return nbp;
- }
-
- /* ------ Dynamic tracing ------ */
-
- /* Flush output flag */
- int trace_flush_flag = 0;
-
- /* The file to use for tracing output */
- FILE *trace_output_file = NULL;
-
- /* The code sequence that replaces the standard procedure entry: */
- typedef struct trace_entry_code_s {
- byte JMP_FAR;
- char far *entry;
- } trace_entry_code;
- static trace_entry_code trace_entry_template = {
- op_JMP_FAR
- };
-
- /* The entry code in the trace record */
- typedef struct entry_code_s {
- byte PUSH_BP;
- byte MOV_BP_SP, mov_X;
- byte PUSH_BP2;
- byte PUSH_seg; unsigned short rec_seg;
- byte PUSH_off; unsigned short rec_off; /*struct trace_rec_s near **/
- byte CALL; char far *print_args;
- /* print_args moves the old return and BP above the args, */
- /* and returns a new fake BP as the value in AX. */
- byte MOV_BP_AX, mov_X2;
- byte INC_SP, INC_SP2; /* pop 1 excess word */
- /* The largest possible finishing code is: */
- /* PUSH BP, MOV SP,BP, SUB SP,size, PUSH SI, PUSH DI, */
- /* CMP [_STKLEN],SP, JMP FAR real_entry */
- byte finish[18];
- } entry_code;
- static entry_code entry_template = {
- op_PUSH_REG+r_BP,
- op_MOV_MR, 0xc0+(r_BP<<3)+r_SP,
- op_PUSH_REG+r_BP,
- op_PUSH_IMM, 0,
- op_PUSH_IMM, 0,
- op_CALL_FAR, 0L,
- op_MOV_MR, 0xc0+(r_BP<<3)+r_AX,
- op_INC_REG+r_SP, op_INC_REG+r_SP
- };
- /* The exit code in the trace record */
- typedef struct exit_code_s {
- byte PUSH_BP;
- byte MOV_BP_SP, mov_X;
- byte PUSH_BP2;
- byte PUSH_DX;
- byte PUSH_AX;
- byte PUSH_seg; unsigned short rec_seg;
- byte PUSH_off; unsigned short rec_off; /*struct trace_rec_s near **/
- byte CALL; char far *print_result;
- /* print_result shuffles things back again */
- byte ADD_SP, add_X, c6w;
- byte MOV_BP_SP2, mov_X2;
- byte POP_BP;
- byte RETF;
- } exit_code;
- static exit_code exit_template = {
- op_PUSH_REG+r_BP,
- op_MOV_MR, 0xc0+(r_BP<<3)+r_SP,
- op_PUSH_REG+r_BP,
- op_PUSH_REG+r_DX,
- op_PUSH_REG+r_AX,
- op_PUSH_IMM, 0,
- op_PUSH_IMM, 0,
- op_CALL_FAR, 0L,
- op_ADD_IMM8, op_ADD_IMMX_X+0xc0+r_SP, 12,
- op_MOV_MR, 0xc0+(r_BP<<3)+r_SP,
- op_POP_REG+r_BP,
- op_RETF
- };
-
- /* Trace information record */
- typedef struct trace_rec_s trace_rec;
- struct trace_rec_s {
- entry_code entry_code;
- exit_code exit_code;
- trace_rec *next;
- char *name;
- char *arg_format;
- int retsize;
- long count;
- };
-
- static trace_rec *trace_list = 0;
-
- /* Trace a named procedure */
- int
- trace_name(name, mapf, arg_format, retsize)
- char *name;
- FILE *mapf;
- char *arg_format;
- int retsize;
- { char far *proc = trace_find_symbol(name, mapf);
- if ( proc == (char far *)NULL ) return -1; /* name not found */
- return trace(proc, name, arg_format, retsize);
- }
-
- /* Trace a procedure */
- int
- trace(proc, name, arg_format, retsize)
- void (*proc)();
- char *name;
- char *arg_format;
- int retsize;
- { char far *pcode = (char far *)proc;
- char far *pc = pcode;
- char far *pt;
- int len;
- trace_entry_code far *ptrace = (trace_entry_code far *)pcode;
- bp_ret *trace_print_arguments();
- long trace_print_result();
- trace_rec *rec = (trace_rec *)malloc(sizeof(trace_rec));
- if ( !rec ) return -1;
- rec->entry_code = entry_template;
- rec->exit_code = exit_template;
- /* Compare the procedure's entry sequence against the */
- /* standard one. If they differ, we can't trace the procedure. */
- pt = (char far *)&rec->entry_code.finish;
- if ( *pc == op_ENTER )
- pc += 4;
- else if ( *pc == op_PUSH_REG+r_BP && pc[1] == op_MOV_MR &&
- pc[2] == 0xc0+(r_BP<<3)+r_SP )
- { pc += 3;
- if ( *pc == op_SUB_IMM && pc[1] == op_SUB_IMMX_X+0xc0+r_SP )
- pc += 4;
- else if ( *pc == op_SUB_IMM8 && pc[1] == op_SUB_IMMX_X+0xc0+r_SP )
- pc += 3;
- }
- if ( *pc == op_PUSH_REG+r_SI )
- { pc++;
- if ( *pc == op_PUSH_REG+r_DI ) pc++;
- }
- if ( *pc == op_CMP_MR && pc[1] == (r_SP<<3)+6 && *(char _ds **)(pc + 2) == (char _ds *)&_stklen )
- pc += 4;
- len = pc - pcode;
- if ( len < sizeof(trace_entry_code) )
- { /* Not enough room for entry code */
- free((char *)rec);
- return -1;
- }
- /* There is, unfortunately, no far version of memcpy. */
- movedata(FP_SEG(pcode), FP_OFF(pcode), FP_SEG(pt), FP_OFF(pt), len);
- pt += len;
- *pt++ = op_JMP_FAR;
- *(char far **)pt = pc;
- rec->next = trace_list;
- rec->name = name;
- rec->arg_format = arg_format;
- rec->retsize = retsize;
- rec->entry_code.rec_seg = FP_SEG(rec);
- rec->entry_code.rec_off = FP_OFF(rec);
- rec->entry_code.print_args = (char far *)trace_print_arguments;
- rec->exit_code.rec_seg = FP_SEG(rec);
- rec->exit_code.rec_off = FP_OFF(rec);
- rec->exit_code.print_result = (char far *)trace_print_result;
- rec->count = 0;
- trace_list = rec;
- /* Patch the procedure entry */
- *ptrace = trace_entry_template;
- ptrace->entry = (char far *)&rec->entry_code;
- return 0;
- }
-
- /* Acquire the file to be used for tracing output. */
- static FILE *
- outfile()
- { return (trace_output_file == NULL ? stdout : trace_output_file);
- }
-
- /* Flush the output file if appropriate. */
- static void
- flushout()
- { if ( trace_output_file == NULL || trace_output_file == stdout )
- fflush(stdout);
- }
-
- /* The following routine gets called at the entry */
- /* to the traced procedure. It prints the arguments and returns. */
- bp_ret *
- trace_print_arguments(rec, sp)
- trace_rec far *rec;
- bp_ret _ss *sp;
- { bp_ret saved_bp_ret = *sp;
- char far *retcode = saved_bp_ret.ret;
- char far *morecode = &rec->exit_code.PUSH_BP;
- char _ss *args = (char _ss *)(sp + 1);
- int argsize = 0;
- bp_ret _ss *above;
- FILE *f = outfile();
- rec->count++;
- /* Get the size of the arguments by looking at */
- /* the instructions after the return */
- if ( retcode[0] == op_ADD_IMM8 && retcode[1] == op_ADD_IMMX_X+0xc0+r_SP )
- argsize = retcode[2];
- else if ( retcode[0] == op_ADD_IMM && retcode[1] == op_ADD_IMMX_X+0xc0+r_SP )
- argsize = *(int far *)(retcode + 2);
- else if ( retcode[0] == op_INC_REG+r_SP && retcode[1] == op_INC_REG+r_SP )
- argsize = 2;
- else if ( retcode[0] == op_POP_REG+r_CX )
- argsize = (retcode[1] == op_POP_REG+r_CX ? 4 : 2);
- else if ( retcode[0] == op_MOV_MR && retcode[1] == 0xc0+(r_SP<<3)+r_BP )
- argsize = (char _ss *)saved_bp_ret.bp - args;
- else
- printf("Unknown calling sequence at %x:%x, abort\n",
- FP_SEG(retcode), FP_OFF(retcode)),
- exit(1);
- fprintf(f, "%s called from %x:%x with ",
- rec->name, FP_SEG(retcode), FP_OFF(retcode));
- if ( rec->arg_format == NULL )
- fprint_block((int *)args, argsize >> 1, f);
- else
- vfprintf(f, rec->arg_format, (va_list)args);
- fputc('\n', f);
- flushout();
- /* Push the arguments down, saving the return and BP above them. */
- /* Note that this smashes the arguments of trace_print_arguments. */
- memcpy((char *)(args - sizeof(bp_ret)), (char *)args, argsize);
- args -= sizeof(bp_ret);
- above = (bp_ret _ss *)(args + argsize);
- *above = saved_bp_ret;
- ((char far **)args)[-1] = morecode;
- return above;
- }
-
- /* Print and return the result */
- long
- trace_print_result(rec, result, sp, bp)
- trace_rec far *rec;
- long result;
- int _ss *sp;
- bp_ret _ss *bp;
- { bp_ret temp_bp_ret;
- FILE *f = outfile();
- fprintf(f, "%s returns", rec->name);
- switch ( rec->retsize )
- {
- case 0: break;
- case 1: fprintf(f, " %x", (unsigned char)result); break;
- case 2: fprintf(f, " %x", (unsigned short)result); break;
- case 3: /* ??? */
- case 4: fprintf(f, " %lx", result); break;
- default:
- { /* We are returning a structure */
- fputs(" (struct)", f);
- fprint_block((int *)result, rec->retsize >> 1, f);
- }
- }
- fputc('\n', f);
- flushout();
- /* Move the saved BP and return back down below the args. */
- /* We can smash the args at this point without concern. */
- /* However, since the source and destination may overlap, */
- /* and Turbo C isn't smart enough to handle this case, */
- /* we have to use an intermediate variable. */
- temp_bp_ret = *bp;
- *(bp_ret *)(sp + 1) = temp_bp_ret;
- return result;
- }
-
- /* Procedure to print a block of words */
- static void
- fprint_block(ptr, count, f)
- int *ptr;
- int count;
- FILE *f;
- { int i;
- for ( i = 0; i < count; i++ )
- fprintf(f, "%5x", ptr[i]);
- }
-