/* * Mach Operating System * Copyright (c) 1991,1990 Carnegie Mellon University * All Rights Reserved. * * Permission to use, copy, modify and distribute this software and its * documentation is hereby granted, provided that both the copyright * notice and this permission notice appear in all copies of the * software, derivative works or modified versions, and any portions * thereof, and that both notices appear in supporting documentation. * * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS" * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. * * Carnegie Mellon requests users of this software to return to * * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU * School of Computer Science * Carnegie Mellon University * Pittsburgh PA 15213-3890 * * any improvements or extensions that they make and grant Carnegie Mellon * the rights to redistribute these changes. */ /* * Author: David B. Golub, Carnegie Mellon University * Date: 7/90 */ #if MACH_KDB /* * Breakpoints. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define NBREAKPOINTS 100 #define NTHREAD_LIST (NBREAKPOINTS*3) struct db_breakpoint db_break_table[NBREAKPOINTS]; db_breakpoint_t db_next_free_breakpoint = &db_break_table[0]; db_breakpoint_t db_free_breakpoints = 0; db_breakpoint_t db_breakpoint_list = 0; static struct db_thread_breakpoint db_thread_break_list[NTHREAD_LIST]; static db_thread_breakpoint_t db_free_thread_break_list = 0; static boolean_t db_thread_break_init = FALSE; static int db_breakpoint_number = 0; db_breakpoint_t db_breakpoint_alloc() { db_breakpoint_t bkpt; if ((bkpt = db_free_breakpoints) != 0) { db_free_breakpoints = bkpt->link; return (bkpt); } if (db_next_free_breakpoint == &db_break_table[NBREAKPOINTS]) { db_printf("All breakpoints used.\n"); return (0); } bkpt = db_next_free_breakpoint; db_next_free_breakpoint++; return (bkpt); } void db_breakpoint_free(bkpt) db_breakpoint_t bkpt; { bkpt->link = db_free_breakpoints; db_free_breakpoints = bkpt; } static int db_add_thread_breakpoint(bkpt, task_thd, count, task_bpt) const db_breakpoint_t bkpt; vm_offset_t task_thd; int count; boolean_t task_bpt; { db_thread_breakpoint_t tp; if (db_thread_break_init == FALSE) { for (tp = db_thread_break_list; tp < &db_thread_break_list[NTHREAD_LIST-1]; tp++) tp->tb_next = tp+1; tp->tb_next = 0; db_free_thread_break_list = db_thread_break_list; db_thread_break_init = TRUE; } if (db_free_thread_break_list == 0) return (-1); tp = db_free_thread_break_list; db_free_thread_break_list = tp->tb_next; tp->tb_is_task = task_bpt; tp->tb_task_thd = task_thd; tp->tb_count = count; tp->tb_init_count = count; tp->tb_cond = 0; tp->tb_number = ++db_breakpoint_number; tp->tb_next = bkpt->threads; bkpt->threads = tp; return(0); } static int db_delete_thread_breakpoint( db_breakpoint_t bkpt, vm_offset_t task_thd) { db_thread_breakpoint_t tp; db_thread_breakpoint_t *tpp; if (task_thd == 0) { /* delete all the thread-breakpoints */ for (tpp = &bkpt->threads; (tp = *tpp) != 0; tpp = &tp->tb_next) db_cond_free(tp); *tpp = db_free_thread_break_list; db_free_thread_break_list = bkpt->threads; bkpt->threads = 0; return 0; } else { /* delete the specified thread-breakpoint */ for (tpp = &bkpt->threads; (tp = *tpp) != 0; tpp = &tp->tb_next) if (tp->tb_task_thd == task_thd) { db_cond_free(tp); *tpp = tp->tb_next; tp->tb_next = db_free_thread_break_list; db_free_thread_break_list = tp; return 0; } return -1; /* not found */ } } static db_thread_breakpoint_t __attribute__ ((pure)) db_find_thread_breakpoint(bkpt, thread) const db_breakpoint_t bkpt; const thread_t thread; { db_thread_breakpoint_t tp; task_t task = (thread == THREAD_NULL)? TASK_NULL: thread->task; for (tp = bkpt->threads; tp; tp = tp->tb_next) { if (tp->tb_is_task) { if (tp->tb_task_thd == (vm_offset_t)task) break; continue; } if (tp->tb_task_thd == (vm_offset_t)thread || tp->tb_task_thd == 0) break; } return(tp); } db_thread_breakpoint_t db_find_thread_breakpoint_here(task, addr) const task_t task; db_addr_t addr; { db_breakpoint_t bkpt; bkpt = db_find_breakpoint(task, addr); if (bkpt == 0) return(0); return(db_find_thread_breakpoint(bkpt, current_thread())); } db_thread_breakpoint_t db_find_breakpoint_number( int num, db_breakpoint_t *bkptp) { db_thread_breakpoint_t tp; db_breakpoint_t bkpt; for (bkpt = db_breakpoint_list; bkpt != 0; bkpt = bkpt->link) { for (tp = bkpt->threads; tp; tp = tp->tb_next) { if (tp->tb_number == num) { if (bkptp) *bkptp = bkpt; return(tp); } } } return(0); } static void db_force_delete_breakpoint( db_breakpoint_t bkpt, vm_offset_t task_thd, boolean_t is_task) { db_printf("deleted a stale breakpoint at "); if (bkpt->task == TASK_NULL || db_lookup_task(bkpt->task) >= 0) db_task_printsym(bkpt->address, DB_STGY_PROC, bkpt->task); else db_printf("%#X", bkpt->address); if (bkpt->task) db_printf(" in task %X", bkpt->task); if (task_thd) db_printf(" for %s %X", (is_task)? "task": "thread", task_thd); db_printf("\n"); db_delete_thread_breakpoint(bkpt, task_thd); } void db_check_breakpoint_valid(void) { db_thread_breakpoint_t tbp, tbp_next; db_breakpoint_t bkpt, *bkptp; bkptp = &db_breakpoint_list; for (bkpt = *bkptp; bkpt; bkpt = *bkptp) { if (bkpt->task != TASK_NULL) { if (db_lookup_task(bkpt->task) < 0) { db_force_delete_breakpoint(bkpt, 0, FALSE); *bkptp = bkpt->link; db_breakpoint_free(bkpt); continue; } } else { for (tbp = bkpt->threads; tbp; tbp = tbp_next) { tbp_next = tbp->tb_next; if (tbp->tb_task_thd == 0) continue; if ((tbp->tb_is_task && db_lookup_task((task_t)(tbp->tb_task_thd)) < 0) || (!tbp->tb_is_task && db_lookup_thread((thread_t)(tbp->tb_task_thd)) < 0)) { db_force_delete_breakpoint(bkpt, tbp->tb_task_thd, tbp->tb_is_task); } } if (bkpt->threads == 0) { db_put_task_value(bkpt->address, BKPT_SIZE, bkpt->bkpt_inst, bkpt->task); *bkptp = bkpt->link; db_breakpoint_free(bkpt); continue; } } bkptp = &bkpt->link; } } db_breakpoint_t db_set_breakpoint(task, addr, count, thread, task_bpt) const task_t task; db_addr_t addr; int count; const thread_t thread; boolean_t task_bpt; { db_breakpoint_t bkpt; db_breakpoint_t alloc_bkpt = 0; vm_offset_t task_thd; bkpt = db_find_breakpoint(task, addr); if (bkpt) { if (thread == THREAD_NULL || db_find_thread_breakpoint(bkpt, thread)) { db_printf("Already set.\n"); return NULL; } } else { if (!DB_CHECK_ACCESS(addr, BKPT_SIZE, task)) { db_printf("Cannot set break point at %X\n", addr); return NULL; } alloc_bkpt = bkpt = db_breakpoint_alloc(); if (bkpt == 0) { db_printf("Too many breakpoints.\n"); return NULL; } bkpt->task = task; bkpt->flags = (task && thread == THREAD_NULL)? (BKPT_USR_GLOBAL|BKPT_1ST_SET): 0; bkpt->address = addr; bkpt->threads = 0; } if (db_breakpoint_list == 0) db_breakpoint_number = 0; task_thd = (task_bpt)? (vm_offset_t)(thread->task): (vm_offset_t)thread; if (db_add_thread_breakpoint(bkpt, task_thd, count, task_bpt) < 0) { if (alloc_bkpt) db_breakpoint_free(alloc_bkpt); db_printf("Too many thread_breakpoints.\n"); return NULL; } else { db_printf("set breakpoint #%d\n", db_breakpoint_number); if (alloc_bkpt) { bkpt->link = db_breakpoint_list; db_breakpoint_list = bkpt; } return bkpt; } } void db_delete_breakpoint(task, addr, task_thd) const task_t task; db_addr_t addr; vm_offset_t task_thd; { db_breakpoint_t bkpt; db_breakpoint_t *prev; for (prev = &db_breakpoint_list; (bkpt = *prev) != 0; prev = &bkpt->link) { if ((bkpt->task == task || (task != TASK_NULL && (bkpt->flags & BKPT_USR_GLOBAL))) && bkpt->address == addr) break; } if (bkpt && (bkpt->flags & BKPT_SET_IN_MEM)) { db_printf("cannot delete it now.\n"); return; } if (bkpt == 0 || db_delete_thread_breakpoint(bkpt, task_thd) < 0) { db_printf("Not set.\n"); return; } if (bkpt->threads == 0) { *prev = bkpt->link; db_breakpoint_free(bkpt); } } db_breakpoint_t __attribute__ ((pure)) db_find_breakpoint(task, addr) const task_t task; db_addr_t addr; { db_breakpoint_t bkpt; for (bkpt = db_breakpoint_list; bkpt != 0; bkpt = bkpt->link) { if ((bkpt->task == task || (task != TASK_NULL && (bkpt->flags & BKPT_USR_GLOBAL))) && bkpt->address == addr) return (bkpt); } return (0); } boolean_t db_find_breakpoint_here(task, addr) const task_t task; db_addr_t addr; { db_breakpoint_t bkpt; for (bkpt = db_breakpoint_list; bkpt != 0; bkpt = bkpt->link) { if ((bkpt->task == task || (task != TASK_NULL && (bkpt->flags & BKPT_USR_GLOBAL))) && bkpt->address == addr) return(TRUE); if ((bkpt->flags & BKPT_USR_GLOBAL) == 0 && DB_PHYS_EQ(task, addr, bkpt->task, bkpt->address)) return (TRUE); } return(FALSE); } boolean_t db_breakpoints_inserted = TRUE; void db_set_breakpoints(void) { db_breakpoint_t bkpt; task_t task; db_expr_t inst; task_t cur_task; cur_task = (current_thread())? current_thread()->task: TASK_NULL; if (!db_breakpoints_inserted) { for (bkpt = db_breakpoint_list; bkpt != 0; bkpt = bkpt->link) { if (bkpt->flags & BKPT_SET_IN_MEM) continue; task = bkpt->task; if (bkpt->flags & BKPT_USR_GLOBAL) { if ((bkpt->flags & BKPT_1ST_SET) == 0) { if (cur_task == TASK_NULL) continue; task = cur_task; } else bkpt->flags &= ~BKPT_1ST_SET; } if (DB_CHECK_ACCESS(bkpt->address, BKPT_SIZE, task)) { inst = db_get_task_value(bkpt->address, BKPT_SIZE, FALSE, task); if (inst == BKPT_SET(inst)) continue; bkpt->bkpt_inst = inst; db_put_task_value(bkpt->address, BKPT_SIZE, BKPT_SET(bkpt->bkpt_inst), task); bkpt->flags |= BKPT_SET_IN_MEM; } else { db_printf("Warning: cannot set breakpoint at %X ", bkpt->address); if (task) db_printf("in task %X\n", task); else db_printf("in kernel space\n"); } } db_breakpoints_inserted = TRUE; } } void db_clear_breakpoints(void) { db_breakpoint_t bkpt, *bkptp; task_t task; task_t cur_task; db_expr_t inst; cur_task = (current_thread())? current_thread()->task: TASK_NULL; if (db_breakpoints_inserted) { bkptp = &db_breakpoint_list; for (bkpt = *bkptp; bkpt; bkpt = *bkptp) { task = bkpt->task; if (bkpt->flags & BKPT_USR_GLOBAL) { if (cur_task == TASK_NULL) { bkptp = &bkpt->link; continue; } task = cur_task; } if ((bkpt->flags & BKPT_SET_IN_MEM) && DB_CHECK_ACCESS(bkpt->address, BKPT_SIZE, task)) { inst = db_get_task_value(bkpt->address, BKPT_SIZE, FALSE, task); if (inst != BKPT_SET(inst)) { if (bkpt->flags & BKPT_USR_GLOBAL) { bkptp = &bkpt->link; continue; } db_force_delete_breakpoint(bkpt, 0, FALSE); *bkptp = bkpt->link; db_breakpoint_free(bkpt); continue; } db_put_task_value(bkpt->address, BKPT_SIZE, bkpt->bkpt_inst, task); bkpt->flags &= ~BKPT_SET_IN_MEM; } bkptp = &bkpt->link; } db_breakpoints_inserted = FALSE; } } /* * Set a temporary breakpoint. * The instruction is changed immediately, * so the breakpoint does not have to be on the breakpoint list. */ db_breakpoint_t db_set_temp_breakpoint( task_t task, db_addr_t addr) { db_breakpoint_t bkpt; bkpt = db_breakpoint_alloc(); if (bkpt == 0) { db_printf("Too many breakpoints.\n"); return 0; } bkpt->task = task; bkpt->address = addr; bkpt->flags = BKPT_TEMP; bkpt->threads = 0; if (db_add_thread_breakpoint(bkpt, 0, 1, FALSE) < 0) { if (bkpt) db_breakpoint_free(bkpt); db_printf("Too many thread_breakpoints.\n"); return 0; } bkpt->bkpt_inst = db_get_task_value(bkpt->address, BKPT_SIZE, FALSE, task); db_put_task_value(bkpt->address, BKPT_SIZE, BKPT_SET(bkpt->bkpt_inst), task); return bkpt; } void db_delete_temp_breakpoint( task_t task, db_breakpoint_t bkpt) { db_put_task_value(bkpt->address, BKPT_SIZE, bkpt->bkpt_inst, task); db_delete_thread_breakpoint(bkpt, 0); db_breakpoint_free(bkpt); } /* * List breakpoints. */ void db_list_breakpoints(void) { db_breakpoint_t bkpt; if (db_breakpoint_list == 0) { db_printf("No breakpoints set\n"); return; } db_printf(" No Space Thread Cnt Address(Cond)\n"); for (bkpt = db_breakpoint_list; bkpt != 0; bkpt = bkpt->link) { db_thread_breakpoint_t tp; int task_id; int thread_id; if (bkpt->threads) { for (tp = bkpt->threads; tp; tp = tp->tb_next) { db_printf("%3d ", tp->tb_number); if (bkpt->flags & BKPT_USR_GLOBAL) db_printf("user "); else if (bkpt->task == TASK_NULL) db_printf("kernel "); else if ((task_id = db_lookup_task(bkpt->task)) < 0) db_printf("%0*X ", 2*sizeof(vm_offset_t), bkpt->task); else db_printf("task%-3d ", task_id); if (tp->tb_task_thd == 0) { db_printf("all "); } else { if (tp->tb_is_task) { task_id = db_lookup_task((task_t)(tp->tb_task_thd)); if (task_id < 0) db_printf("%0*X ", 2*sizeof(vm_offset_t), tp->tb_task_thd); else db_printf("task%03d ", task_id); } else { thread_t thd = (thread_t)(tp->tb_task_thd); task_id = db_lookup_task(thd->task); thread_id = db_lookup_task_thread(thd->task, thd); if (task_id < 0 || thread_id < 0) db_printf("%0*X ", 2*sizeof(vm_offset_t), tp->tb_task_thd); else db_printf("task%03d.%-3d ", task_id, thread_id); } } db_printf("%3d ", tp->tb_init_count); db_task_printsym(bkpt->address, DB_STGY_PROC, bkpt->task); if (tp->tb_cond > 0) { db_printf("("); db_cond_print(tp); db_printf(")"); } db_printf("\n"); } } else { if (bkpt->task == TASK_NULL) db_printf(" ? kernel "); else db_printf("%*X ", 2*sizeof(vm_offset_t), bkpt->task); db_printf("(?) "); db_task_printsym(bkpt->address, DB_STGY_PROC, bkpt->task); db_printf("\n"); } } } /* Delete breakpoint */ /*ARGSUSED*/ void db_delete_cmd(void) { int n; thread_t thread; vm_offset_t task_thd; boolean_t user_global = FALSE; boolean_t task_bpt = FALSE; boolean_t user_space = FALSE; boolean_t thd_bpt = FALSE; db_expr_t addr; int t; t = db_read_token(); if (t == tSLASH) { t = db_read_token(); if (t != tIDENT) { db_printf("Bad modifier \"%s\"\n", db_tok_string); db_error(0); } user_global = db_option(db_tok_string, 'U'); user_space = (user_global)? TRUE: db_option(db_tok_string, 'u'); task_bpt = db_option(db_tok_string, 'T'); thd_bpt = db_option(db_tok_string, 't'); if (task_bpt && user_global) db_error("Cannot specify both 'T' and 'U' option\n"); t = db_read_token(); } if (t == tHASH) { db_thread_breakpoint_t tbp; db_breakpoint_t bkpt; if (db_read_token() != tNUMBER) { db_printf("Bad break point number #%s\n", db_tok_string); db_error(0); } if ((tbp = db_find_breakpoint_number(db_tok_number, &bkpt)) == 0) { db_printf("No such break point #%d\n", db_tok_number); db_error(0); } db_delete_breakpoint(bkpt->task, bkpt->address, tbp->tb_task_thd); return; } db_unread_token(t); if (!db_expression(&addr)) { /* * We attempt to pick up the user_space indication from db_dot, * so that a plain "d" always works. */ addr = (db_expr_t)db_dot; if (!user_space && !DB_VALID_ADDRESS((vm_offset_t)addr, FALSE)) user_space = TRUE; } if (!DB_VALID_ADDRESS((vm_offset_t) addr, user_space)) { db_printf("Address %#X is not in %s space\n", addr, (user_space)? "user": "kernel"); db_error(0); } if (thd_bpt || task_bpt) { for (n = 0; db_get_next_thread(&thread, n); n++) { if (thread == THREAD_NULL) db_error("No active thread\n"); if (task_bpt) { if (thread->task == TASK_NULL) db_error("No task\n"); task_thd = (vm_offset_t) (thread->task); } else task_thd = (user_global)? 0: (vm_offset_t) thread; db_delete_breakpoint(db_target_space(thread, user_space), (db_addr_t)addr, task_thd); } } else { db_delete_breakpoint(db_target_space(THREAD_NULL, user_space), (db_addr_t)addr, 0); } } /* Set breakpoint with skip count */ /*ARGSUSED*/ void db_breakpoint_cmd(addr, have_addr, count, modif) db_expr_t addr; int have_addr; db_expr_t count; const char * modif; { int n; thread_t thread; boolean_t user_global = db_option(modif, 'U'); boolean_t task_bpt = db_option(modif, 'T'); boolean_t user_space; if (count == -1) count = 1; if (!task_bpt && db_option(modif,'t')) task_bpt = TRUE; if (task_bpt && user_global) db_error("Cannot specify both 'T' and 'U'\n"); user_space = (user_global)? TRUE: db_option(modif, 'u'); if (user_space && db_access_level < DB_ACCESS_CURRENT) db_error("User space break point is not supported\n"); if (!task_bpt && !DB_VALID_ADDRESS((vm_offset_t)addr, user_space)) { /* if the user has explicitly specified user space, do not insert a breakpoint into the kernel */ if (user_space) db_error("Invalid user space address\n"); user_space = TRUE; db_printf("%#X is in user space\n", addr); } if (db_option(modif, 't') || task_bpt) { for (n = 0; db_get_next_thread(&thread, n); n++) { if (thread == THREAD_NULL) db_error("No active thread\n"); if (task_bpt && thread->task == TASK_NULL) db_error("No task\n"); if (db_access_level <= DB_ACCESS_CURRENT && user_space && thread->task != db_current_task()) db_error("Cannot set break point in inactive user space\n"); db_set_breakpoint(db_target_space(thread, user_space), (db_addr_t)addr, count, (user_global)? THREAD_NULL: thread, task_bpt); } } else { db_set_breakpoint(db_target_space(THREAD_NULL, user_space), (db_addr_t)addr, count, THREAD_NULL, FALSE); } } /* list breakpoints */ void db_listbreak_cmd(void) { db_list_breakpoints(); } #endif /* MACH_KDB */