990 lines
27 KiB
C
990 lines
27 KiB
C
/*************************************************************************\
|
||
* 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 <stddef.h>
|
||
#include <stdlib.h>
|
||
#include <stdio.h>
|
||
#include <stdarg.h>
|
||
#include <string.h>
|
||
|
||
#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
|
||
|