2 * mdb.c - MINIX program debugger
4 * Written by Bruce D. Szablak
6 * This free software is provided for non-commerical use. No warrantee
7 * of fitness for any use is implied. You get what you pay for. Anyone
8 * may make modifications and distribute them, but please keep this header
13 * Originally ported to MINIX-PC and MINIX-386 by Bruce Evans.
14 * NB: the original sym.c and mdbdis86.c come from his 'db'
16 * Added by Philip Murton:
18 * 2.0 'Core' file functions
19 * 2.1 Support for GNU exec
20 * 2.2 Changes for Minix 1.6.x Beta
21 * 2.3 Changes for Minix 1.7.0 and trace syscalls
22 * 2.4 Changes for Minix 1.7.2 and clean up
23 * 2.5.1 Add better help
24 * 2.5.2 Added io.c for logging options
25 * 2.5.3 Minor changes and tested with Minix 1.7.4
26 * 2.5.4 Command arguments processing improved (Thanks to Will Rose)
27 * 2.6.0 Final Version for MINIX CD (Sept/96)
33 #include <minix/type.h>
43 #define ptrace mdbtrace
44 #include <sys/ptrace.h>
48 #include <machine/archtypes.h>
49 #include <kernel/const.h>
50 #include <kernel/type.h>
51 #include <kernel/proc.h>
53 /* buffer for proc and pointer to proc */
54 extern struct proc
*prc
;
59 static unsigned long lastexp
= 0L; /* last expression and segment */
60 static int lastseg
= NOSEG
;
61 static char *prog
; /* prog name */
62 static char sbuf
[MAXLINE
];
63 static char cbuf
[MAXLINE
];
64 static char *cmd
; /* current command */
65 static char *cmdstart
; /* start of command */
66 static jmp_buf mainlp
;
70 struct b_pnt
*nxt
, *prv
;
76 int main(int argc
, char *argv
[]);
78 static void cleanup(void);
79 static void freepnt(struct b_pnt
*pnt
);
80 static void findbpnt(int verbose
);
81 static int exebpnt(int restart
);
82 static void catch(int sig
);
83 static int run(char *name
, char *argstr
, int tflg
);
84 static int dowait(void);
85 static void backtrace(int all
);
86 static void modify(long addr
, int cnt
, int verbose
, int size
);
87 static void display(long addr
, int req
);
88 static void fill(long addr
, int req
);
89 static void dorun(char *cmd
);
90 static void not_for_core(void);
91 static void command(void);
98 while (b_head
) freepnt(b_head
);
101 static void findbpnt(verbose
)
104 for (curpnt
= b_head
; curpnt
; curpnt
= curpnt
->nxt
) {
105 if (curpnt
->addr
== PC_MEMBER(prc
) - BREAKPOINT_ADVANCE
) {
106 ptrace(T_SETINS
, curpid
, curpnt
->addr
, curpnt
->oldval
);
107 ptrace(T_SETUSER
, curpid
, PC_OFF
, curpnt
->addr
);
110 do_syscall(curpnt
->addr
);
111 else if (curpnt
->cmd
[0] != '\n')
113 if (curpnt
->cmd
[0] != '\n')
115 cmd
= strcpy(cbuf
, curpnt
->cmd
);
117 Printf("Breakpoint hit.\n");
121 if (verbose
) Printf("Unknown breakpoint hit.\n");
124 static int exebpnt(restart
)
127 ptrace(T_STEP
, curpid
, 0L, (long) restart
);
128 if (dowait() == 0) return TRUE
;
129 ptrace(T_SETINS
, curpid
, curpnt
->addr
, BREAK(curpnt
->oldval
));
135 static void freepnt(pnt
)
139 pnt
->prv
->nxt
= pnt
->nxt
;
142 if (pnt
->nxt
) pnt
->nxt
->prv
= pnt
->prv
;
143 if (curpid
> 0) ptrace(T_SETINS
, curpid
, pnt
->addr
, pnt
->oldval
);
145 if (pnt
== curpnt
) curpnt
= NULL
;
149 long breakpt(addr
, cmd
)
156 Printf("No active process.\n");
159 for (new = b_head
; new; new = new->nxt
)
160 if (new->addr
== addr
) {
161 Printf("Breakpoint already exists here.\n");
164 new = (struct b_pnt
*) malloc(sizeof(struct b_pnt
) + strlen(cmd
));
166 Printf("No room for new breakpoint.\n");
171 if (b_head
) b_head
->prv
= new;
174 strcpy(new->cmd
, cmd
);
175 new->oldval
= ptrace(T_GETINS
, curpid
, addr
, 0L);
176 ptrace(T_SETINS
, curpid
, addr
, BREAK(new->oldval
));
177 if (ptrace(T_GETINS
, curpid
, addr
, 0L) != BREAK(new->oldval
)) {
178 do_error("Can't set breakpoint");
185 static void catch(sig
)
189 if (sig
== SIGINT
|| sig
== SIGQUIT
) return;
190 tstart(T_EXIT
, 0, sig
, 0);
199 if (corepid
> 0) return cursig
= 0;
200 while (wait(&stat
) != curpid
) {};
201 if ( WIFEXITED(stat
) ) {
202 if (WEXITSTATUS(stat
) != 127)
203 Printf("child exited with status %d\n", WEXITSTATUS(stat
));
207 if ( WIFSIGNALED(stat
) ) {
208 Printf("child terminated by signal %d\n", WTERMSIG(stat
) );
209 if (_LOW(stat
) & 0x80) Printf("(core dumped)\n");
213 return cursig
= WSTOPSIG(stat
);
218 void tstart(req
, verbose
, val
, cnt
)
219 int req
, verbose
, val
, cnt
;
222 if (verbose
) Printf("No active process.\n");
226 ptrace(T_EXIT
, curpid
, 0L, (long) val
);
230 if (cnt
== 0) cnt
= 1;
233 if (exebpnt(val
)) return;
234 if (req
== T_RESUME
) cnt
++;
237 ptrace(req
, curpid
, 0L, (long) val
);
238 if (dowait() == 0) return;
241 case SIGEMT
: /* breakpoint */
245 case SIGTRAP
: /* trace trap? */
246 if (req
== T_STEP
) break;
247 default: /* signal */
255 if ( verbose
) dasm((long) PC_MEMBER(prc
), 1, 1);
258 static int run(name
, argstr
, tflg
)
263 char *argv
[MAXARG
], *inf
= NULL
, *outf
= NULL
;
266 if ((procid
= fork()) == 0) {
268 if (tflg
) ptrace(T_OK
, 0, 0L, 0L);
271 argstr
= skip(argstr
);
272 if (*argstr
== '\n' || *argstr
== ';') {
274 if (inf
) freopen(inf
, "r", stdin
);
275 if (outf
) freopen(outf
, "w", stdout
);
287 else if (*argstr
== '>')
289 else if (argc
== MAXARG
) {
290 Printf("Too many arguments.\n");
293 argv
[argc
++] = argstr
;
294 while (!isspace(*argstr
)) argstr
++;
295 if (*argstr
== '\n') argstr
[1] = '\n', argstr
[2] = 0;
299 if (procid
< 0) do_error("Fork failed.\n");
304 static void dorun(cmd
)
307 if (curpid
= run(prog
, cmd
, 1)) {
309 ptrace(T_SETUSER
, curpid
, BP_OFF
, 0L);
311 Printf("Process stopped.\n");
317 * backtrace - inspect the stack
319 static void backtrace(all
)
322 unsigned long pc
, bp
, off
, val
, obp
;
325 Printf("No process.\n");
328 pc
= get_reg(curpid
,PC_OFF
);
329 bp
= get_reg(curpid
,BP_OFF
);
331 Printf("No active frame.\n");
337 pc
= (ptrace(T_GETDATA
, curpid
, bp
+ ADDRSIZE
, 0L)
338 >> SHIFT(ADDRSIZE
)) & MASK(ADDRSIZE
);
339 off
= ptrace(T_GETINS
, curpid
, pc
, 0L);
342 Printf("Return address %lx Value %lx\n",pc
,off
);
347 /* Check for various instruction used to restore the stack.
348 * Should gives us the number of arguments.
349 * This is obvious dependent on interal features of the
352 if (ADDQ(off
)) off
= ADDQ_CNT(off
) + bp
;
355 off
= LEA_DISP(off
) + bp
;
358 off
= ADDA_CNT(ptrace(T_GETINS
, curpid
, pc
+ 2, 0L)) + bp
;
359 #if defined(__i386__)
360 else if (INCSP2(off
))
361 off
= bp
+ 2*INTSIZE
;
362 else if (POPBX2(off
))
363 off
= bp
+ 2*INTSIZE
;
364 else if (POPCX2(off
))
365 off
= bp
+ 2*INTSIZE
;
376 Printf("Number of arguments: %d\n",(off
-bp
)/INTSIZE
);
381 val
= (ptrace(T_GETDATA
, curpid
, bp
, 0L)
382 >> SHIFT(INTSIZE
)) & MASK(INTSIZE
);
383 Printf("0x%0*lx", 2 * INTSIZE
, val
);
385 if (bp
>= off
) break;
391 bp
= (long) ( (reg_t
) ptrace(T_GETDATA
, curpid
, obp
, 0L) );
394 Printf("Old BP %lx New %lx\n",obp
,bp
);
397 while (all
&& (reg_t
) bp
);
400 static void modify(addr
, cnt
, verbose
, size
)
402 int cnt
, verbose
, size
;
407 Printf("No active process.\n");
410 curval
= ptrace(T_GETDATA
, curpid
, addr
, 0L) & MASK(size
);
412 if (cursig
== SIGTRAP
) cursig
= 0;
414 off
= get_reg(curpid
, PC_OFF
);
417 if (curpnt
&& exebpnt(cursig
))
420 ptrace(T_STEP
, curpid
, addr
, 0L);
430 if (curval
!= ptrace(T_GETDATA
, curpid
, addr
, 0L) & MASK(size
)) {
431 Printf("Modification detected\n");
437 dasm((long) PC_MEMBER(prc
), 1, 1);
441 static void display(addr
, req
)
445 int count
, size
, out
, shift
;
450 Printf("No active process\n");
453 if (req
== T_GETDATA
&& seg
== T
) req
= T_GETINS
;
454 count
= strtol(cmd
, &cmd
, 0);
455 if (count
== 0) count
= 1;
457 if (*cmd
== 'i' || *cmd
== 'I') {
458 dasm(addr
, count
, *cmd
== 'i');
462 symbolic(addr
, '\n');
466 case 'b': size
= sizeof(char); break;
467 case 'h': size
= sizeof(short); break;
468 case 'l': size
= sizeof(long); break;
474 switch (fmt
= *cmd
) {
480 addr
= ptrace(req
, curpid
, addr
, 0L);
492 val
= (ptrace(req
, curpid
, addr
, 0L) >> shift
) & msk
;
493 if (out
== 0) Printf("\n0x%0*lx: ", 2 * ADDRSIZE
,
494 (addr
>> SHIFT(ADDRSIZE
)) & MASK(ADDRSIZE
));
497 Printf(isprint((int) (UCHAR(val
))) ? " %c " : "\\%03o ",
499 if (++out
== 8) out
= 0;
502 Printf("%12lu ", val
);
503 if (++out
== 4) out
= 0;
507 Printf("%*lx ", 2 * size
, val
);
508 if (++out
== (size
== 4 ? 4 : 8)) out
= 0;
511 Printf("%*lo ", 3 * size
, val
);
512 if (++out
== (size
== 4 ? 4 : 8)) out
= 0;
520 if (++out
== 64) out
= 0;
525 Printf("%12ld ", val
);
526 if (++out
== 4) out
= 0;
531 while (--count
> 0 || fmt
== 's' || fmt
== 'a');
536 static void fill(addr
, req
)
540 int count
, size
, shift
;
544 Printf("No active process\n");
548 if (req
== T_GETDATA
&& seg
== T
) {
550 Printf("mdb: warning - modifying text\n");
552 count
= strtol(cmd
, &cmd
, 0);
553 if ( count
== 0 ) count
= 1;
555 case 'b': size
= sizeof(char); break;
556 case 'h': size
= sizeof(short); break;
557 case 'l': size
= sizeof(long); break;
565 cmd
= getexp(cmd
, &nval
, &seg
);
569 Printf("Filling for Count=%d Size=%d val=%lx\n",count
,size
,nval
);
574 val
= ptrace(req
, curpid
, addr
, 0L) | (nval
& msk
);
575 val
&= (nval
| ~msk
);
576 ptrace(req
+ 3, curpid
, addr
, val
);
582 static void not_for_core()
585 mdb_error("Illegal command for 'core' file\n");
588 static void command()
597 seg
= NOSEG
; /* don't restrict segment expressions are in */
598 cmdstart
= cmd
= skip(cmd
);
599 cmd
= getexp(cmd
, &exp
, &seg
);
601 if (cmd
== cmdstart
) {
602 /* Not an expression */
603 if (corepid
< 0) { /* default to pc for running processs */
605 exp
= PC_MEMBER(prc
);
611 /* Is it a help command */
622 if (seg
== NOSEG
) seg
= T
; /* Absolute becomes Text */
623 lastexp
= exp
; /* save last expression */
627 Printf("Current address 0x%0*lx and segment %d\n", 2 * ADDRSIZE
, exp
, seg
);
632 switch (c
= *cmd
++) {
633 case 'r': /* illegal for 'core' files */
638 case 'D': not_for_core();
641 case 'b': /* illegal for 'core' files */
642 case 'c': /* Otherwise run process first */
650 case 'I': not_for_core();
651 if (curpid
<= 0) dorun("\n");
654 case 's': if (curpid
<= 0) dorun("\n");
661 case '!': /* escape to shell */
662 if (cmd
== cmdstart
+ 1) {
664 if (*cmd
== '\n' || *cmd
== ';') {
665 i
= run("/bin/sh", "\n", 0);
667 for (p
= cmd
+ 1; *p
&& !isspace(*p
); p
++) {
670 i
= run(cmd
, *p
? p
: "\n", 0);
672 if (i
> 0) while (wait(&stat
) != i
) {};
675 if (corepid
> 0) longjmp(mainlp
, 0);
677 case 'T': /* top line of backtrace */
680 case 't': /* back trace */
683 case '/': /* print variable value */
684 display(exp
, T_GETDATA
);
686 case 'x': /* print registers and instruction */
687 if (disp_regs()) break;
689 case 'X': /* print instruction - X n [, n] */
690 lj
= strtol(cmd
, &cmd
, 0);
693 lk
= strtol(++cmd
, &cmd
, 0);
695 dasm(exp
+ lk
, lj
? lj
: 1, 1);
697 Printf("No active process.\n");
699 case 'R': /* run program with no args */
700 case 'r': /* run program with args (possibly defaulted) */
701 tstart(T_EXIT
, 0, 0, 0);
704 if (*cmd
== '\n' || *cmd
== ';')
713 case 'c': /* continue program - ignore signal */
715 case 'C': /* continue program - handle signal */
717 if (seg
== T
&& curpnt
== 0 && cmd
!= cmdstart
+ 1) {
720 ptrace(T_SETINS
, curpid
, curpnt
->addr
, curpnt
->oldval
);
723 tstart(T_RESUME
, 1, cursig
, (int) strtol(cmd
, &cmd
, 0));
724 /* remove temporary bp */
725 if (i
) freepnt(b_head
);
726 if (cursig
== SIGEMT
) return;
727 if (curpid
) Printf("Process stopped by signal %d\n", cursig
);
729 case 'i': /* single step - ignore signal */
730 tstart(T_STEP
, 1, 0, (int) strtol(cmd
, &cmd
, 0));
732 case 'I': /* single step - handle signal */
733 tstart(T_STEP
, 1, cursig
, (int) strtol(cmd
, &cmd
, 0));
735 case 'm': /* single step until location modified */
736 case 'M': /* single step until location modified - verbose */
739 case 'b': size
= sizeof(char); break;
740 case 'h': size
= sizeof(short); break;
741 case 'l': size
= sizeof(long); break;
747 modify(exp
, (int) strtol(cmd
, &cmd
, 0), c
== 'M', size
);
749 case 'k': /* kill current program */
750 tstart(T_EXIT
, 1, 0, 0);
752 case 'b': /* set a breakpoint at the given line */
754 if (seg
!= T
|| exp
> end_addr
) {
756 if (seg
!= T
|| exp
< st_addr
|| exp
> et_addr
) {
758 Printf("Address not in text space.\n");
761 breakpt(exp
, skip(cmd
));
764 case 'B': /* print list of currently active breakpoints */
765 for (i
= 1, bp
= b_head
; bp
; bp
= bp
->nxt
, i
++) {
767 symbolic((long) bp
->addr
, '\t');
768 Printf("(0x%lx)\t- %s", bp
->addr
, bp
->cmd
);
771 case 'd': /* delete breakpoint */
773 for (bp
= b_head
; bp
&& bp
->addr
!= exp
; bp
= bp
->nxt
);
779 Printf("No such breakpoint.\n");
781 case 'D': /* delete all breakpoints */
782 while (b_head
) freepnt(b_head
);
785 dump_stack( strtol(cmd
, &cmd
, 0) );
789 if (paging
) Printf("Paging is ON\n");
793 logging(c
,skip(cmd
));
797 start_syscall( strtol(cmd
, &cmd
, 0) );
799 Printf("Break point set - use the 'c n' command\n");
803 tstart(T_EXIT
, 0, 0, 0);
810 if (isdigit(*cmdstart
))
813 Printf("0x%0*lx\n", 2 * ADDRSIZE
, exp
);
817 case 'v': /* toggle debug */
819 if (debug
) Printf("Debug flag ON\n");
822 case 'e': /* list symbols */
825 case 'y': /* print mapping */
828 case '?': /* print help */
831 case 'V': /* print version info */
834 case '@': /* command file */
839 case '#': /* set register or variable */
844 set_reg(curpid
, i
, strtol(cmd
+2, &cmd
, 0) );
848 cmd
= getexp(cmd
, &exp
, &seg
);
849 fill(exp
, T_GETDATA
);
855 while (*cmd
!= '\n' && *cmd
!= ';') ++cmd
;
856 if (*cmd
== ';') cmd
= skip(cmd
+ 1);
872 int opt_c
= FALSE
; /* load core file */
873 int opt_f
= FALSE
; /* load object file */
874 int opt_l
= FALSE
; /* log to file */
875 int opt_L
= FALSE
; /* log to file and screen */
878 prc
= (struct proc
*) lbuf
;
880 corepid
= -1; /* set to indicate none */
881 prog
= p
= q
= r
= NULL
;
889 /* Possible combinations of arguments:
890 * A single file name:
891 * If the name is 'core', the coreonly flag is set.
892 * The -c flag: examine a core file.
893 * One filename is required with this flag.
894 * The -f flag: examine an object file.
895 * One file name is required with this flag.
896 * The -L or -l flag: write to a log file.
897 * One file name is required with these flags.
898 * The -x flag: turn on debugging.
899 * Used for debugging, and followed by an integer
900 * argument which is the debugging level.
902 * If any files remain on the argument list, the first
903 * file is an executable, and the second a core file.
904 * If any filename starts with '@' it is assumed to
905 * to be a command file. Only one command file is
909 /* check for default file name and fake out getopt */
910 if (strcmp(argv
[1], "core") == 0) {
911 for (i
= argc
; i
> 1 ; i
--)
912 argv
[i
] = argv
[i
- 1];
919 while ((i
= getopt(argc
, argv
, "c:f:L:l:x:")) != EOF
) {
921 case 'c': /* examine a core file */
922 if (opt_c
== TRUE
|| opt_f
== TRUE
) {
929 case 'f': /* examine an object file */
930 if (opt_c
== TRUE
|| opt_f
== TRUE
) {
937 case 'l': /* start logging */
938 if (opt_l
== TRUE
|| opt_L
== TRUE
) {
945 case 'L': /* start logging */
946 if (opt_l
== TRUE
|| opt_L
== TRUE
) {
954 case 'x': /* set debug level */
955 debug
= atoi(optarg
);
958 case '?': /* default arguments arrive here */
965 /* can't cope without filenames */
966 if (!opt_c
&& !opt_f
&& optind
>= argc
) {
971 /* any remaining arguments are (optional) file names */
972 for (i
= optind
; i
< argc
; i
++) {
973 if (*argv
[i
] == '@') { /* command file */
974 if (r
== NULL
) r
= argv
[i
] + 1;
976 /* you can't combine a -c or -f object file and a core file */
977 else if (!opt_c
&& !opt_f
&& p
== NULL
) p
= argv
[i
];
978 else if (q
== NULL
) q
= argv
[i
]; /* core file */
981 /* initialise stuff - fairly tricky logic */
984 /* when examining files, prog == NULL */
985 if (!opt_c
&& !opt_f
) {
990 /* file_init is called for non-core files.
991 * It is very similar to core_init. It opens the file and set
992 * various pointers so that we can read it using the same routines
994 * NB: Currently there is no special provision to handle object files.
997 /* A comment from Will Rose:
998 * It would be nice to have
999 * symbol tables available when reading a core
1000 * or a.out, either as part of the executable or
1001 * as a separate file.
1002 * At least three separate types of file structure
1003 * may be used by mdb - core files, a.out files, and
1004 * object files (which may have several flavours).
1005 * A set of routines is needed for each type, with
1006 * a function switch table initialised when mdb is
1010 if (opt_c
) lastexp
= core_init(p
);
1011 if (opt_f
) lastexp
= file_init(p
);
1012 if (q
!= NULL
) lastexp
= core_init(q
);
1013 if (r
!= NULL
) openin(r
);
1014 for (i
= 1; i
< _NSIG
; i
++) signal(i
, catch);
1018 while (get_cmd( cbuf
, MAXLINE
) != NULL
) {
1019 if (strlen(cbuf
) == sizeof(cbuf
) - 1) {
1020 Printf("Command line too long.\n");
1025 while (*cmd
!= '\n') command();
1027 tstart(T_EXIT
, 0, 0, 0);