/*************************************************************************\ * Copyright (c) 2002 The University of Chicago, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * EPICS BASE Versions 3.13.7 * and higher are distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* dbBkpt.c */ /* base/src/db $Id$ */ /* * Author: Matthew Needes * Date: 8-30-93 */ /* * Database Breakpoint Manipulation and User Interface * * USER COMMANDS * dbb(record_name) Set a breakpoint in a record * dbd(record_name) Delete a record's breakpoint * dbc(record_name) Resume record processing * dbs(record_name) Step through record processing through * IO links, forward process links, etc. * dbstat() Display status of stopped records in lock sets. * dbap(record_name) Toggle automatic print after processing. * dbp(record_name) Print out fields from record currently stopped. * dbprc(record_name) Processes a record once without printing it. * (Unless autoprint is on) * * INTERNAL FUNCTIONS * dbBkpt() Process breakpoints, called by dbProcess(). * dbPrint() Prints record if autoprint enabled. * dbBkptCont() The task that continues and steps through * records that are stopped at a breakpoint. */ /* #define BKPT_DIAG */ #include #include #include #include #include #include "dbDefs.h" #include "epicsThread.h" #include "epicsMutex.h" #include "epicsEvent.h" #include "epicsTime.h" #include "ellLib.h" #include "errlog.h" #include "alarm.h" #include "dbBase.h" #include "dbFldTypes.h" #include "link.h" #include "dbCommon.h" #include "dbFldTypes.h" #include "db_field_log.h" #include "errMdef.h" #include "recSup.h" #include "special.h" #define epicsExportSharedSymbols #include "dbAddr.h" #include "dbAccessDefs.h" #include "dbScan.h" #include "dbLock.h" #include "recGbl.h" #include "dbTest.h" #include "dbBkpt.h" /* private routines */ static void dbBkptCont(dbCommon *precord); static long FIND_CONT_NODE( const char *record_name, struct LS_LIST **ppnode, struct dbCommon **pprecord); /* * Breakpoints are used as a debugging instrument to suspend the * processing of database records. Once suspended, record * processing may continue if either a continue (dbc()) or a * step (dbs()) command is then issued. The current record's * contents may be printed either with dbp(), or immediately * after processing (use dbap() to toggle the BKPT_PRINT bit). * * dbb() and dbd() add a breakpoint to a record or delete one * from a record. dbstat() prints out comprehensive breakpoint * status information. * * Breakpoints may be set on a per lockset basis. When a * breakpoint is set in a lockset, a new task is created. A * separate task gets created for _every_ lockset containing * a breakpoint. Thus multiple locksets may be debugged * simultaneously. The breakpoint handler then schedules future * processing in that lockset to this task. The separate task is * used so that locksets that do not have breakpoints are isolated * from locksets that do. This allows the processing of other * locksets to continue uninterupted, even if they exist on the same * scan list as a lockset containing a breakpoint. * * An entrypoint is the first record that gets processed in a lockset. * This type of record is the basis for subsequent recursive executions * of dbProcess(). The breakpoint handler monitors and schedules * these entrypoints to the breakpoint tasks. * * Two hooks have been inserted in dbProcess() to manage breakpoints, * dbBkpt() and dbPrint(). The former does two things: * * 1. Schedule entrypoints with the breakpoint task. * 2. Suspend record processing when a breakpoint is detected. * * 1 occurs only if dbProcess() is called outside of the breakpoint * task. Number 2 only occurs when dbProcess() is called from * _within_ the breakpoint task's context. Number 1 is used for * detection and scheduling, while 2 is used for suspending the task. * * The dbPrint() hook is used to print out a record's contents immediately * _after_ a record has been processed. * * The dbBkptCont, or breakpoint task, pends on a semaphore that gets * released whenever new entrypoints are scheduled for it. When * released, this task then runs down its entrypoint queue and * processes each entrypoint in turn. In this context, dbProcess * will execute the dbBkpt() hook in mode 2, allowing this task to * be suspended whenever a breakpoint is detected. * * NOTE: This is not a very "real-time" implementation (even for those * locksets not containing a breakpoint). I may fix this later. * * Final comment: The scary thing is, I don't think this can be done * more simply... * */ /* * Flag used by dbProcess() to determine if there are * any breakpoints. This is so that there is only * a single comparison in the critical path during * normal record execution, i.e. when there aren't * any breakpoints set. */ long lset_stack_not_empty = 0; /* * Stack--in which each entry represents a different * lock set with either breakpoints and/or stopped * execution. (Breakpoints may be disabled even * though execution is stopped). The order of the * list is maintained so that the entry on the top * of stack is used as a default for dbc() and dbs(). * The semaphore is used to prevent conflicts while * operating with this stack. */ static ELLLIST lset_stack; static epicsMutexId bkpt_stack_sem; /* * Stores the last lockset continued or stepped from. * dbs() and dbc() will print a message if the current * lockset to be continued from differs from this * variable. */ static unsigned long last_lset = 0; /* * FIND_LOCKSET() finds the stack entry * whose l_num field matches precord's * lset field. The node that is found * is returned in "pnode." */ #define FIND_LOCKSET(precord, pnode) \ pnode = (struct LS_LIST *) ellFirst(&lset_stack); \ while ((pnode) != NULL) { \ if (pnode->l_num == dbLockGetLockId(precord)) break; \ pnode = (struct LS_LIST *) ellNext((ELLNODE *)pnode); \ } \ /* * FIND_QUEUE_ENTRY() matches entries in an * entry point queue. pep_queue is the queue * being searched, pqe is the pointer to the * queue entry found, and precord is the record * being searched for in *pep_queue. */ #define FIND_QUEUE_ENTRY(pep_queue, pqe, precord) \ pqe = (struct EP_LIST *) ellFirst(pep_queue); \ while ((pqe) != NULL) { \ if ((pqe)->entrypoint == (precord)) break; \ pqe = (struct EP_LIST *) ellNext((ELLNODE *)pqe); \ } \ /* * Fills out pnode and precord structures for dbc() and dbs() * MUST LOCK OUT STACK BEFORE ENTRY */ static long FIND_CONT_NODE( const char *record_name, struct LS_LIST **ppnode, struct dbCommon **pprecord) { struct dbAddr addr; struct LS_LIST *pnode; struct dbCommon *precord = NULL; long status = 0; if (record_name == NULL) { /* * Search through stack, taking the first entry that * is currently stopped at a breakpoint. */ pnode = (struct LS_LIST *) ellFirst(&lset_stack); while (pnode != NULL) { if (pnode->precord != NULL) { precord = pnode->precord; break; } pnode = (struct LS_LIST *) ellNext((ELLNODE *)pnode); } if (pnode == NULL) { printf(" BKPT> No records are currently stopped\n"); return(S_db_notStopped); } } else { /* * Convert name to address */ status = dbNameToAddr(record_name, &addr); if (status == S_db_notFound) printf(" BKPT> Record %s not found\n", record_name); if (status != 0) return(status); precord = addr.precord; FIND_LOCKSET(precord, pnode); if (pnode == NULL || pnode->precord == NULL) { printf(" BKPT> Currently not stopped in this lockset\n"); return(S_db_notStopped); } } *pprecord = precord; *ppnode = pnode; return(0); } /* * Add breakpoint to a lock set * 1. Convert name to address and check breakpoint mask. * 2. Lock database. * 3. If empty, initialize lock set stack and its semaphore. * 4. Take that semaphore. * 5. Find lockset in the list. If it doesn't exist, create it. * 6. Turn on breakpoint field in record. * 7. Add breakpoint to list of breakpoints in structure. * 8. Spawn continuation task if it isn't already running. */ long epicsShareAPI dbb(const char *record_name) { struct dbAddr addr; struct LS_LIST *pnode; struct BP_LIST *pbl; struct dbCommon *precord; long status; /* * Convert name to address */ status = dbNameToAddr(record_name, &addr); if (status == S_db_notFound) printf(" BKPT> Record %s not found\n", record_name); if (status != 0) return(status); precord = addr.precord; if (precord->bkpt & BKPT_ON_MASK) { printf(" BKPT> Breakpoint already set in this record\n"); return(S_db_bkptSet); } dbScanLock(precord); /* * Add lock set to the stack of lock sets that * contain breakpoints and/or stopped records. */ if (! lset_stack_not_empty) { /* initialize list and semaphore */ bkpt_stack_sem = epicsMutexCreate(); if (bkpt_stack_sem == 0) { printf(" BKPT> epicsMutexCreate failed\n"); dbScanUnlock(precord); return(1); } ellInit(&lset_stack); lset_stack_not_empty = 1; } epicsMutexMustLock(bkpt_stack_sem); FIND_LOCKSET(precord, pnode); if (pnode == NULL) { /* lockset not found, create node, add to end of list */ pnode = (struct LS_LIST *) malloc(sizeof(struct LS_LIST)); if (pnode == NULL) { printf(" BKPT> Out of memory\n"); dbScanUnlock(precord); epicsMutexUnlock(bkpt_stack_sem); return(1); } pnode->precord = NULL; /* initialize breakpoint list */ ellInit(&pnode->bp_list); /* initialize entry point queue */ ellInit(&pnode->ep_queue); /* create execution semaphore */ pnode->ex_sem = epicsEventCreate(epicsEventEmpty); if (pnode->ex_sem == NULL) { printf(" BKPT> Out of memory\n"); dbScanUnlock(precord); epicsMutexUnlock(bkpt_stack_sem); return(1); } pnode->taskid = 0; pnode->step = 0; pnode->l_num = dbLockGetLockId(precord); ellAdd(&lset_stack, (ELLNODE *)pnode); } /* * Add record to breakpoint list */ pbl = (struct BP_LIST *) malloc(sizeof(struct BP_LIST)); if (pbl == NULL) { printf(" BKPT> Out of memory\n"); dbScanUnlock(precord); epicsMutexUnlock(bkpt_stack_sem); return(1); } pbl->precord = precord; ellAdd(&pnode->bp_list, (ELLNODE *)pbl); /* * Turn on breakpoint field in record */ precord->bkpt |= BKPT_ON_MASK; if (! pnode->taskid) { #ifdef BKPT_DIAG printf(" BKPT> Spawning task: %s\n", precord->name); #endif /* * Spawn continuation task */ pnode->taskid = epicsThreadCreate("bkptCont",epicsThreadPriorityScanLow-1, epicsThreadGetStackSize(epicsThreadStackBig), (EPICSTHREADFUNC)dbBkptCont,precord); if (pnode->taskid == 0) { printf(" BKPT> Cannot spawn task to process record\n"); pnode->taskid = 0; dbScanUnlock(precord); epicsMutexUnlock(bkpt_stack_sem); return(1); } } epicsMutexUnlock(bkpt_stack_sem); dbScanUnlock(precord); return(0); } /* * Remove breakpoint from a record * 1. Convert name to address and check breakpoint mask. * 2. Lock database and take stack semaphore. * 3. Find structure for record's lockset (in stack). * 4. Find and delete record from breakpoint list. * 5. Turn off break point field. * 6. Give up semaphore to "signal" bkptCont task to quit. */ long epicsShareAPI dbd(const char *record_name) { struct dbAddr addr; struct LS_LIST *pnode; struct BP_LIST *pbl; struct dbCommon *precord; long status; /* * Convert name to address */ status = dbNameToAddr(record_name, &addr); if (status == S_db_notFound) printf(" BKPT> Record %s not found\n", record_name); if (status != 0) return(status); precord = addr.precord; if (! precord->bkpt & BKPT_ON_MASK) { printf(" BKPT> No breakpoint set in this record\n"); return(S_db_bkptNotSet); } dbScanLock(precord); epicsMutexMustLock(bkpt_stack_sem); FIND_LOCKSET(precord, pnode); if (pnode == NULL) { /* not found, error ! */ printf(" BKPT> Logic Error in dbd()\n"); precord->bkpt &= BKPT_OFF_MASK; epicsMutexUnlock(bkpt_stack_sem); dbScanUnlock(precord); return(S_db_bkptLogic); } /* * Remove record from breakpoint list */ /* find record in list */ pbl = (struct BP_LIST *) ellFirst(&pnode->bp_list); while (pbl != NULL) { if (pbl->precord == precord) { ellDelete(&pnode->bp_list, (ELLNODE *)pbl); free(pbl); break; } pbl = (struct BP_LIST *) ellNext((ELLNODE *)pbl); } if (pbl == NULL) { printf(" BKPT> Logic Error in dbd()\n"); precord->bkpt &= BKPT_OFF_MASK; epicsMutexUnlock(bkpt_stack_sem); dbScanUnlock(precord); return(S_db_bkptLogic); } /* * Turn off breakpoint field in record */ precord->bkpt &= BKPT_OFF_MASK; /* * If there are no more breakpoints, give up semaphore * to cause the bkptCont task to quit. */ if (ellCount(&pnode->bp_list) == 0) epicsEventSignal(pnode->ex_sem); epicsMutexUnlock(bkpt_stack_sem); dbScanUnlock(precord); return(0); } /* * Continue processing in a lock set * 1. Find top node in the lockset stack. * 2. Turn off stepping mode. * 2. Resume dbBkptCont. */ long epicsShareAPI dbc(const char *record_name) { struct LS_LIST *pnode; struct dbCommon *precord = NULL; long status = 0; epicsMutexMustLock(bkpt_stack_sem); status = FIND_CONT_NODE(record_name, &pnode, &precord); if (status) { epicsMutexUnlock(bkpt_stack_sem); return(status); } if (record_name == NULL && last_lset != pnode->l_num) printf(" BKPT> Continuing: %s\n", pnode->precord->name); last_lset = pnode->l_num; /* * Turn off stepping mode */ pnode->step = 0; /* * Resume dbBkptCont() until dbProcess() is executed * for a record with a breakpoint. This occurs * because stepping mode has been switched off. */ epicsThreadResume(pnode->taskid); epicsMutexUnlock(bkpt_stack_sem); return(0); } /* * Step through record processing * 1. Find top node in lockset stack. * 2. Resume dbBkptCont. */ long epicsShareAPI dbs(const char *record_name) { struct LS_LIST *pnode; struct dbCommon *precord = NULL; long status = 0; epicsMutexMustLock(bkpt_stack_sem); status = FIND_CONT_NODE(record_name, &pnode, &precord); if (status) { epicsMutexUnlock(bkpt_stack_sem); return(status); } if (last_lset != pnode->l_num && record_name == NULL) printf(" BKPT> Stepping: %s\n", pnode->precord->name); last_lset = pnode->l_num; epicsThreadResume(pnode->taskid); epicsMutexUnlock(bkpt_stack_sem); return(0); } /* * Task for continuing record processing * 1. Find lockset in stack for precord. * DO 2-3 while breakpoints exist in the lockset. * 2. Wait on execution semaphore ... * 3. Run through every entrypoint in queue, processing * those that are scheduled. * 4. Free resources for lockset, and exit task. */ static void dbBkptCont(dbCommon *precord) { struct LS_LIST *pnode; struct EP_LIST *pqe = NULL; /* * Reset breakpoint, process record, and * reset bkpt field in record */ epicsMutexMustLock(bkpt_stack_sem); FIND_LOCKSET(precord, pnode); if (pnode == NULL) { printf(" BKPT> Logic error in dbBkptCont()\n"); return; } /* * For every entrypoint scheduled, process. Run process * until there are no more breakpoints remaining in a * lock set. */ do { /* Give up semaphore before waiting to run ... */ epicsMutexUnlock(bkpt_stack_sem); /* Wait to run */ epicsEventMustWait(pnode->ex_sem); /* Bkpt stack must still be stable ! */ epicsMutexMustLock(bkpt_stack_sem); pqe = (struct EP_LIST *) ellFirst(&pnode->ep_queue); /* Run through entrypoint queue */ while (pqe != NULL) { /* check if entrypoint is currently scheduled */ if (pqe->sched) { /* save current entrypoint */ pnode->current_ep = pqe->entrypoint; /* lock the lockset, process record, unlock */ dbScanLock(precord); dbProcess(pqe->entrypoint); dbScanUnlock(precord); /* reset schedule and stepping flag - Do this AFTER processing */ pqe->sched = 0; pnode->step = 0; } pqe = (struct EP_LIST *) ellNext((ELLNODE *)pqe); } /* Reset precord. (Since no records are at a breakpoint) */ pnode->precord = NULL; } while (ellCount(&pnode->bp_list) != 0); /* remove node from lockset stack */ ellDelete(&lset_stack, (ELLNODE *)pnode); { /* * free entrypoint queue * * avoid use of ellFree because problems on windows occur if the * free is in a different DLL than the malloc */ ELLNODE * nnode = pnode->ep_queue.node.next; while ( nnode ) { ELLNODE * pnode = nnode; nnode = nnode->next; free ( pnode ); } } /* remove execution semaphore */ epicsEventDestroy(pnode->ex_sem); printf("\n BKPT> End debug of lockset %lu\n-> ", pnode->l_num); /* free list node */ free(pnode); /* if last node on stack ... */ if (ellCount(&lset_stack) == 0) { /* Unset flag, delete stack semaphore */ lset_stack_not_empty = 0; epicsMutexDestroy(bkpt_stack_sem); } if (lset_stack_not_empty) epicsMutexUnlock(bkpt_stack_sem); } /* * Process breakpoint * Returns a zero if dbProcess() is to execute * record support, a one if dbProcess() is to * skip over record support. See dbProcess(). * * 1. See if there is at least a breakpoint set somewhere * in precord's lockset. If not, return immediately. * 2. Check the disable flag. * 3. Add entry points to the queue for future stepping and * schedule new entrypoints for the continuation task. * 4. Check the pact flag. * 5. Check to see if there is a breakpoint set in a record, and * if so, turn on stepping mode. * 6. If stepping mode is set, stop and report the breakpoint. */ int epicsShareAPI dbBkpt(dbCommon *precord) { struct LS_LIST *pnode; struct EP_LIST *pqe; /* * It is crucial that operations in dbBkpt() execute * in the correct order or certain features in the * breakpoint handler will not work as expected. */ /* * Take and give a semaphore to check for breakpoints * every time a record is processed. Slow. Thank * goodness breakpoint checking is turned off during * normal operation. */ epicsMutexMustLock(bkpt_stack_sem); FIND_LOCKSET(precord, pnode); epicsMutexUnlock(bkpt_stack_sem); if (pnode == NULL) { /* no breakpoints in precord's lockset */ return(0); } /* Check disable flag */ dbGetLink(&(precord->sdis),DBR_SHORT,&(precord->disa),0,0); if (precord->disa == precord->disv) { /* * Do not process breakpoints if the record is disabled, * but allow disable alarms. Alarms will be raised * in dbProcess() because returning 0 allows dbProcess() * to continue. However processing will be prevented * because disa and disv will be examined again in * dbProcess(). Note that checking for pact will occur * before checking for disa and disv in dbProcess(). */ return(0); } /* * Queue entry points for future stepping. The taskid comparison * is used to determine if the source of processing is the * continuation task or an external source. If it is an external * source, queue its execution, but dump out of dbProcess without * calling record support. */ if (pnode->taskid && (epicsThreadGetIdSelf() != pnode->taskid)) { /* CONTINUE TASK CANNOT ENTER HERE */ /* * Add an entry point to queue, if it does * not already exist. */ FIND_QUEUE_ENTRY(&pnode->ep_queue, pqe, precord); if (pqe == NULL) { pqe = (struct EP_LIST *) malloc(sizeof(struct EP_LIST)); if (pqe == NULL) return(1); pqe->entrypoint = precord; pqe->count = 1; epicsTimeGetCurrent(&pqe->time); pqe->sched = 0; #ifdef BKPT_DIAG printf(" BKPT> Adding entrypoint %s to queue\n", precord->name); #endif /* * Take semaphore, wait on continuation task */ epicsMutexMustLock(bkpt_stack_sem); /* Add entry to queue */ ellAdd(&pnode->ep_queue, (ELLNODE *)pqe); epicsMutexUnlock(bkpt_stack_sem); } else { if (pqe->count < MAX_EP_COUNT) pqe->count++; } /* check pact */ if (! precord->pact) { /* schedule if pact not set */ pqe->sched = 1; /* * Release the semaphore, letting the continuation * task begin execution of the new entrypoint. */ epicsEventSignal(pnode->ex_sem); } return(1); } /* * Don't mess with breakpoints if pact set! Skip * over rest of dbProcess() since we don't want * alarms going off. The pact flag is checked * AFTER entry point queuing so that the record * timing feature will work properly. */ if (precord->pact) return(1); /* Turn on stepping mode if a breakpoint is found */ if (precord->bkpt & BKPT_ON_MASK) { pnode->step = 1; #ifdef BKPT_DIAG printf(" BKPT> Bkpt detected: %s\n", precord->name); #endif } /* * If we are currently stepping through the lockset, * suspend task. */ if (pnode->step) { printf("\n BKPT> Stopped at: %s within Entrypoint: %s\n-> ", precord->name, pnode->current_ep->name); pnode->precord = precord; /* Move current lockset to top of stack */ ellDelete(&lset_stack, (ELLNODE *)pnode); ellInsert(&lset_stack, NULL, (ELLNODE *)pnode); /* * Unlock database while the task suspends itself. This * is done so that dbb() dbd() dbc() dbs() may be used * when the task is suspended. Scan tasks that also * use the scan lock feature will not be hung during * a breakpoint, so that records in other locksets will * continue to be processed. Cross your fingers, this * might actually work ! */ epicsMutexUnlock(bkpt_stack_sem); dbScanUnlock(precord); epicsThreadSuspendSelf(); dbScanLock(precord); epicsMutexMustLock(bkpt_stack_sem); } return(0); } /* print record after processing */ void epicsShareAPI dbPrint(dbCommon *precord ) { struct LS_LIST *pnode; if (! (precord->bkpt & BKPT_PRINT_MASK)) return; FIND_LOCKSET(precord, pnode); /* do not print if lockset does not currently contain breakpoints */ if (pnode == NULL) return; printf("\n"); dbpr(precord->name, 2); printf("-> "); } /* print stopped record */ long epicsShareAPI dbp(const char *record_name, int interest_level) { struct LS_LIST *pnode; struct dbCommon *precord; int status; epicsMutexMustLock(bkpt_stack_sem); /* find pnode and precord pointers */ status = FIND_CONT_NODE(record_name, &pnode, &precord); if (status) { epicsMutexUnlock(bkpt_stack_sem); return(status); } /* print out record's fields */ dbpr(precord->name, (interest_level == 0) ? 2 : interest_level); epicsMutexUnlock(bkpt_stack_sem); return(0); } /* toggle printing after processing a certain record */ long epicsShareAPI dbap(const char *record_name) { struct dbAddr addr; struct dbCommon *precord; long status; /* * Convert name to address */ status = dbNameToAddr(record_name, &addr); if (status == S_db_notFound) printf(" BKPT> Record %s not found\n", record_name); if (status != 0) return(status); precord = addr.precord; /* * Toggle print after process field in record */ if (precord->bkpt & BKPT_PRINT_MASK) { printf(" BKPT> Auto print off for record %s\n", precord->name); precord->bkpt &= BKPT_PRINT_OFF_MASK; } else { printf(" BKPT> Auto print on for record %s\n", precord->name); precord->bkpt |= BKPT_PRINT_MASK; } return(0); } /* print list of stopped records, and breakpoints set in locksets */ long epicsShareAPI dbstat(void) { struct LS_LIST *pnode; struct BP_LIST *pbl; struct EP_LIST *pqe; epicsTimeStamp time; epicsMutexMustLock(bkpt_stack_sem); epicsTimeGetCurrent(&time); /* * Traverse list, reporting stopped records */ pnode = (struct LS_LIST *) ellFirst(&lset_stack); while (pnode != NULL) { if (pnode->precord != NULL) { printf("LSet: %lu Stopped at: %-28.28s #B: %5.5d T: %p\n", pnode->l_num, pnode->precord->name, ellCount(&pnode->bp_list), pnode->taskid); /* for each entrypoint detected, print out entrypoint statistics */ pqe = (struct EP_LIST *) ellFirst(&pnode->ep_queue); while (pqe != NULL) { double diff = epicsTimeDiffInSeconds(&time,&pqe->time); if (diff) { printf(" Entrypoint: %-28.28s #C: %5.5lu C/S: %7.1f\n", pqe->entrypoint->name, pqe->count,diff); } pqe = (struct EP_LIST *) ellNext((ELLNODE *)pqe); } } else { printf("LSet: %lu #B: %5.5d T: %p\n", pnode->l_num, ellCount(&pnode->bp_list), pnode->taskid); } /* * Print out breakpoints set in the lock set */ pbl = (struct BP_LIST *) ellFirst(&pnode->bp_list); while (pbl != NULL) { printf(" Breakpoint: %-28.28s", pbl->precord->name); /* display auto print flag */ if (pbl->precord->bkpt & BKPT_PRINT_MASK) printf(" (ap)\n"); else printf("\n"); pbl = (struct BP_LIST *) ellNext((ELLNODE *)pbl); } pnode = (struct LS_LIST *) ellNext((ELLNODE *)pnode); } epicsMutexUnlock(bkpt_stack_sem); return(0); } /* * Process a record without printing it. */ long epicsShareAPI dbprc(char *record_name) { struct dbAddr addr; struct dbCommon *precord; long status; /* * Convert name to address */ status = dbNameToAddr(record_name, &addr); if (status == S_db_notFound) printf(" BKPT> Record %s not found\n", record_name); if (status != 0) return(status); precord = addr.precord; /* lock lockset, process record, unlock lockset */ dbScanLock(precord); status = dbProcess(precord); dbScanUnlock(precord); return(status); } #ifdef BKPT_DIAG /* Reset breakpoints */ int dbreset() { epicsMutexUnlock(bkpt_stack_sem); return(0); } #endif