Capturing ETB Trace Data With ETBLib
From Texas Instruments Embedded Processors Wiki
Contents |
Introduction
This page will walk through a demo on how to capture ETB Trace data and moving it off chip with assistance from ETBLib, an ETB programming library.
Tools Needed
- Code Composer Studio v4 with Target Emulator
- TCI6488 Target Card
Software Used
- ETBLib - Embedded Trace Buffer Programming Library
- DSPTraceLib - DSP Trace Programming Library
- AETLib - Advanced Event Triggering Library
Demo Application
Overview
The demo application is called etblib_demo. It is a DSP/Bios based sample application. There are three tasks that get called in a round robin fashion, each with a priority level of 2. Each of these tasks will pend on a semaphore. When all three tasks have pended, a lower level task will get executed, which will post the semaphores to re-enable the higher level tasks. In addition, there is one more task with a priority level of three. Upon execution, it immediately pends on an additional semaphore.
In order to simulate an application crash, which would cause the application to end up in the SYS_ABORT routine, we create a PRD function that will get called after 15 seconds of execution. This function will post the semaphore that allows the high priority task to execute. This high priority task then calls SYS_ABORT to simulate a crash.
GOAL
The Goal of this exercise is to demonstrate the ability to look at the resulting trace data and trace back to the point of the crash. In this case, it will involve simply tracing back to the call to SYS_ABORT. In a more realistic scenario, there would be more detailed analysis needed to find the result of the crash. But the objective of this exercise is more in the capturing of the data, rather than in the analysis of it.
Application Source Code Description
Note: Many of these variables are declared global so that they are visible within any of the application functions. Depending on the situation, sometimes internal memory buffer that will hold the ETB contents can be declared dynamically. However, in this case, we are declaring a static buffer. The reason for this is because we won't be able to dynamically allocate once SYS_abort() has been called. So we allocate statically beforehand, and then copy the values into that buffer after SYS_abort() has been called. We have created our own function myExit, which replaces UTL_halt, which is called from the abort routine. This function will copy the ETB into internal memory, set a status bitfield recording progress, and then turn off hardware interrupts and spin in an infinite loop. This same routine could be used on an ordinary application to dump the ETB values to memory before shutting down.
Downloading the Example Prroject
You can download the example project from the following locations. Note that in order to build the projects, you will need to have the AETLib, ETBLib, and TraceLib libraries available. They can be downloaded from here
The two projects mane a few assumptions on the locations of include files and libraries. You will need to either tweak the projects to accommodate your configuration or update the macro variables accordingly. The assumptions are listed below.
- Macro AetLib points to a directory with subdirectories of \include and \lib. The AetLib header files are located in the \include subdirectory and the .lib files are located in the \lib subdirectory.
- Macro EtbLib points to a directory with subdirectories of \include and \lib. The EtbLib header files are located in the \include subdirectory and the .lib files are located in the \lib subdirectory.
- Macro TraceLib points to a directory with subdirectories of \include and \lib. The TraceLib header files are located in the \include subdirectory and the .lib files are located in the \lib subdirectory.
| Project File Description | Link |
|---|---|
| Code Composer Studio 3.3 Project | Link |
| Code Composer Studio v4 Project | Link |
main()
The following code, implemented in main, merely sets up ths DSP?Bios objects that are needed to execute the demo. We create each of the tasks, set the appropriate arguments, and also create the PRD one-shot function. If any of this fails, we notify the user through the Bios message log.
/* Initialize the TSK_Attrs structure for all tasks */ TSK_Attrs tsk0_attrs = {0}; TSK_Attrs tsk1_attrs = {0}; TSK_Attrs tsk2_attrs = {0}; TSK_Attrs lltask_attrs = {0}; TSK_Attrs cleanup_attrs = {0}; LOG_printf(&trace, "Faraday Test example started.\n"); /* Start the PRD Clock */ PRD_start(&PRD0); /* Create Task0 */ tsk0_attrs.name = "Task0"; tsk0_attrs.priority = 2; tsk0_attrs.stacksize = 512; tsk0_attrs.stack = NULL; tsk0_attrs.initstackflag = 1; hTask0 = TSK_create((Fxn)task0, &tsk0_attrs); if (!hTask0) { SYS_abort("Can't create task 0"); } /* Create Task1 */ tsk1_attrs.name = "Task1"; tsk1_attrs.priority = 2; tsk1_attrs.stacksize = 512; tsk1_attrs.stack = NULL; tsk1_attrs.initstackflag = 1; hTask1 = TSK_create((Fxn)task1, &tsk1_attrs); if (!hTask1) { SYS_abort("Can't create task 1"); } /* Create Task2 */ tsk2_attrs.name = "Task2"; tsk2_attrs.priority = 2; tsk2_attrs.stacksize = 512; tsk2_attrs.stack = NULL; tsk2_attrs.initstackflag = 1; hTask2 = TSK_create((Fxn)task2, &tsk2_attrs); if (!hTask2) { SYS_abort("Can't create task 2"); } /* Create Low Level Task */ lltask_attrs.name = "LowLevel Task"; lltask_attrs.priority = 1; lltask_attrs.stacksize = 512; lltask_attrs.stack = NULL; lltask_attrs.initstackflag = 1; hllTask = TSK_create((Fxn)lltask, &lltask_attrs); if (!hllTask) { SYS_abort("Can't create low level task"); } /* Create Clean Up Task */ cleanup_attrs.name = "Cleanup Task"; cleanup_attrs.priority = 3; cleanup_attrs.stacksize = 512; cleanup_attrs.stack = NULL; cleanup_attrs.initstackflag = 1; cleanupTask = TSK_create((Fxn)cleanup, &cleanup_attrs); if (!hllTask) { SYS_abort("Can't create CleanUp task"); }
We also want to initialize the ETB and the DSP trace hardware. We also need to program AET to turn trace on and off at the appropriate points. The code comments will give you an idea of what's going on. We are configuring AET to trigger PC and Timing trace to start when we execute task0, and to end when we execute our exit function myExit. ETB will capture in a looping fashion so that when trace has ended, we will have a group of the last instructions executed.
/* Set Up the ETB Receiver */ etbError = ETB_open(pETBErrCallBack, eETB_Circular, coreID, &pETBHandle, &etbSize); if(etbError != eETB_Success) { SYS_abort("Error opening ETB"); } else { LOG_printf(&trace, "ETB Opened"); } /* Enable ETB receiver */ etbError = ETB_enable(pETBHandle, 0); if(etbError != eETB_Success) { SYS_abort("Error enabling ETB"); } else { LOG_printf(&trace, "ETB Enabled"); } /* AETLIB Programming should be done here */ /* Configure AET to start at the task0 function and to end at the custom myExit function */ startTraceParams = AET_JOBPARAMS; endTraceParams = AET_JOBPARAMS; startTraceParams.programAddress = (Uint32)&task0; startTraceParams.traceTriggers = AET_TRACE_TIMING | AET_TRACE_PA; startTraceParams.traceActive = AET_TRACE_ACTIVE; endTraceParams.programAddress = (Uint32) &myExit; endTraceParams.traceTriggers = AET_TRACE_TIMING | AET_TRACE_PA; endTraceParams.traceActive = AET_TRACE_INACTIVE; AET_init(); if (AET_claim()){ SYS_abort("AET not claimed"); }else{ LOG_printf(&trace, "AET Claimed"); } /* Set Up the Trace Start Job */ if (AET_setupJob(AET_JOB_START_STOP_TRACE_ON_PC, &startTraceParams)){ SYS_abort("Start job program failure"); }else{ LOG_printf(&trace, "Start Job Programmed Success"); } startJob = startTraceParams.jobIndex; LOG_printf(&trace, "Start Job #%d", startJob); /* Set Up the Trace End Job */ if (AET_setupJob(AET_JOB_START_STOP_TRACE_ON_PC, &endTraceParams)){ SYS_abort("End job program failure"); }else{ LOG_printf(&trace, "End Job Programmed Success"); } endJob = endTraceParams.jobIndex; LOG_printf(&trace, "End Job #%d", endJob); /* Enable AET */ if (AET_enable()){ SYS_abort("Error Enabling AET"); }else{ LOG_printf(&trace, "AET Enabled"); } /*** Setup Trace Export ***/ /* Open DSP Trace export module */ dspTraceError = DSPTrace_open( pDSPErrorCallBack, &pDSPHandle); if(dspTraceError != eDSPTrace_Success) { SYS_abort("Error opening DSP Trace Export block"); }else{ LOG_printf(&trace, "Trace Export Opened"); } /* Setup trace export clock to FCLK/2 */ dspTraceError= DSPTrace_setClock(pDSPHandle, 2); if(dspTraceError != eDSPTrace_Success) { SYS_abort("Error setting up DSP trace export clock"); }else{ LOG_printf(&trace, "Trace Clock Initialized"); } /* Enable DSP Trace */ dspTraceError= DSPTrace_enable(pDSPHandle, 0, testPattern); if(dspTraceError != eDSPTrace_Success) { SYS_abort("Error enabling DSP trace export"); } else { LOG_printf(&trace, "DSP Trace Enabled"); } /* Start the PRD Clock */ PRD_start(&PRD0);
dumpEtb()
Once the application has executed for ~15 seconds, the PRD function will be executed, posting the semaphore that allows the high priority task to call SYS_abort() to simulate the application crash. The custom exit function calls dumpEtb(), which reclaims the resources used by AET, moves the ETB Data from the ETB to processor memory, disables and closes the DSP Trace interface, and disables and closes the ETB interface. Through each of these steps, we monitor sucess by updating the bits in the global variable statusCode. When we end up in the infinite loop after SYS_abort(), the value signaling total success in the transport is one of either 0x1F7 (ETB has not wrapped) or 0x1EF (ETB Wrapped). In a typical case, rather than using these bit fields, we'd just use a printf to tell the user where the error occurred. We can't do that here because we can't call printf after we've called SYS_abort.
void dumpEtb(){ // Release all of the AETLIB Resources AET_releaseJob(startJob); AET_releaseJob(endJob); AET_release(); // Disable Trace export dspTraceError = DSPTrace_disable(pDSPHandle); if (dspTraceError == eDSPTrace_Success) { statusCode |= STS_TRACE_EXPORT_DISABLED; } /* Disable Trace Capture - ETB Receiver */ etbError = ETB_disable(pETBHandle); if (etbError == eETB_Success){ statusCode |= STS_ETB_DISABLED; } /* Check the ETB status */ etbError = ETB_status(pETBHandle, &etbStatus); if (etbError == eETB_Success){ statusCode |= STS_ETB_STATUS; } /* Move the ETB data into memory */ if (etbStatus.canRead == 1) { etbWordsAvailable = etbStatus.availableWords; if (etbStatus.isWrapped == 1){ statusCode |= STS_ETB_WRAPPED; }else{ statusCode |= STS_ETB_NOT_WRAPPED; } /* * Allocate the buffer for copying the ETB to * Set the memPtr to the array that we've statically allocated */ memPtr = (uint32_t*)&etb_buf; if (memPtr) { etbError = ETB_read(pETBHandle, memPtr, etbStatus.availableWords, 0, etbStatus.availableWords, &retSize); if (etbError == eETB_Success) { statusCode |= STS_ETB_READ; } statusCode |= STS_BUF_ALLOCATE; } } /* Close the handle to the ETB */ etbError = ETB_close(pETBHandle); if (etbError == eETB_Success) { statusCode |= STS_ETB_CLOSED; } /* Close the handle to DSPTrace */ dspTraceError = DSPTrace_close(pDSPHandle); if (dspTraceError == eDSPTrace_Success) { statusCode |= STS_TRACE_EXPORT_CLOSED; } }
Exporting the Data
Typically, I set a breakpoint on the asm(" nop") instruction in the myExit() function. The breakpoint will get hit as soon as the application has _crashed_. You can also potentially just wait for the application to execute a sufficient amount of time, and then manually halt it. In both cases, the PC should be in the same location.
When we have halted, the ETB contents will have been copied to CPU memory at the location of the array etb_buf. You should now put the following three values in the CCS watch window
- etb_buf - Location of the ETB buffer values
- etbWordsAvilable - Number of words in the buffer
- statusCode - Error Status code.
If the error status code is either 0x1F7 or 0x1EF, it is safe to proceed. You then want to open the CCS Scripting Console window and enter a command to export the data to a bin file. In this case, we will use the name "etb_test.bin". You can use whatever name you choose. We will assume that the .out file is stored in c:\temp\, and will use the same path for the bin file.
In the command window, enter the following command, replacing the values as necessary. Replace startAddr with the value in etb_buf in the watch window Replace length with the value in etbWordsAvailable in the watch window.saveRaw(startAddr, PAGE_PROGRAM, "C:\\temp\\etb_test.bin", length , 32, true)
Converting the Data
To this point, the data that we have captured is simply raw etb data in binary form. We need to convert this data to a trace data format (.tdf) in order to be able to view it. There is a tool supplied with Code Composer Studio v4 that will accomplish this task. It's called bin2tdf.exe and is located in the <%CCS_INSTALL_DIR%>\CCSv4\emulation]analysis\bin directory.
The first step to using bin2tdf is to create a bin2tdf configuration file that will need to be passed to the utility. You can create one for this demo by copying the text in the box below into a text file and saving it with a name like bin2tdf.cfg
Trace Mode = 0 Encoding Mode = 0 Number of AEG = 0 Memory Mode = 0 CEMU Status = 0
There are a number of parameters that need to be specified on the command line.
- -bin <binary etb file>
- -coffname <.out file>
- -cpuid <type of CPU: For the TCI6488, this is 64x+>
- -rcvrname <trace receiver name: In this case, it's ETB>
- -dcmfile <bin2tdf configuration file name
Assuming that all of the necessary files are in the current directory and the location of the .exe is in the path, the following command line can be used for this example
bin2tdf.exe -bin etb_test.bin -coffname etblib_demo.out -cpuid 64x+ -rcvrname ETB -dcmfile bin2tdf.cfg
The .tdf file will be created with the same name as the .bin file, so in this case, it would be etb_test.tdf. It will be created in the current directory.
Viewing the Data
There are actually two different viewer applications shipped with Code Composer Studio 4 that will allow you to display the captured .tdf file. It's your choice as to which you use.
Trace Analyzer Display
Code Composer Studio v4 comes with a built in trace viewer called trace analyzer. The Trace Analyzer window can be opened from within CCS by choosing Tools->Trace Analyzer. In order to open the .tdf file, perform the following steps.
- Select Tools->Trace Analyzer->Open Trace file in New View
- Navigate to the location where the .tdf file is located and choose it.
- The next window will ask for the program file. Navigate to the location of the .out file and choose it
- The next window will ask to browse for folder. Navigate to the folder that contains the source code and choose it (optional)
Stand Alone Trace Display
Code Composer Studio v4 comes with a stand alone trace display viewer that can be used to view .tdf files. The application is called TraceDisplay.exe and can be found in the <%CCS_INSTALL_DIRECTORY%>/ccsv4/emulation/analysis/bin directory. When opening TraceDisplay.exe, you will get a dialog box that mentions that it is unable to connect to the trace data server and will operate in Standalone mode. You can just acknowledge the dialog, as we want to be operatign in Standalone mode.
Once the trace display application has been opened, you can choose File->Open and navigate to and select the .tdf file that you wish to view through dialog. In some cases, once you specicify the .tdf, the trace display will ask you to specify the associated .out file. If this occurs, just point the dialog to the .out file that was used when the trace data was captured. (If you are saving these files to be viewed at a later time, it is important that you also save the associated .out file. If the .out file is not precisely the same file that was used when the trace data was captured, the trace decoder will not be able to retrieve the data.)
In the image to the right, we can see what the trace data looks like after it's been decoded. The top frame shows the raw assembly level execution of the application. We see the program address for each instruction, a timing cycle count associated with each instruction, and the disassembly of each instruction. In the lower pane, we see the C source code. As we scroll through the top pane, the lower pane will stay in sync when we are in code that is contained in the directories that we have specified as source directoriesSpecifying source directories
To indicate to the Trace Display which folders contain the source code, right click in the top pane of the display above, and select Source->Set Source Directory. To add a directory to the list, select New Folder and navigate to the desired directory and select "OK." Repeat as necessary. You can choose to automatically include sub directories, but if there are too many of them, it will significantly slow down the search.
FAQ
Q: Do I need an XDS560 Trace emulator to use this?
Related
- CToolsLib
- XDS560 Trace
- Embedded Trace Buffer or ETB
- Advanced Event Triggering
- Unified Breakpoint Manager
CN Capturing ETB Trace Data With ETBLib
Leave a Comment