Stack Trace

The stack trace exception handler is probably the most useful tool in the firmware developers bag of tricks. The stack trace allows the developer to view the function nesting whenever the program is stopped at a breakpoint. Because the stack trace gives an instant view of the calling history, a stack trace can save gobs of debug and analysis time.

Stack trace capability is usually considered something that is only offered by a high-level debug environment. This doesnt have to be the case. Having implemented stack trace for a few different CPUs, I can tell you that implementing stack trace is a pain in the neck; once it works, however, you just cant live without it.

The monitor-based stack trace I discuss in this chapter extracts symbol information from the symbol table file, as have other commands. The stack trace, though, requires that the symbols be sorted in ascending address order. I also limit the monitor-based stack trace to provide function nesting only.

I limit the stack trace to function nesting because deciphering variables in the stack is a bit more complicated. Think about it for a minute: return addresses must be easy to find because the hardware must use a return address every time it returns from a function call. On the other hand, finding a variable within the stack isnt a hardware function its part of the virtual machine fabricated by the compiler. In fact, different compilers for the same CPU can use different conventions for placing variables on the stack. Thus, the ability to display variables within a particular functions stack frame is not a natural thing for the CPU nor for the monitor. Any stack trace that displays variables requires information from the compiler regarding how and where the variables are stored in the frame.

The majority of the code for a stack trace implementation is compiler and CPU specific. Some of the parsing of the symbol file is generic and can be reused on multiple implementations . As I mentioned above, I found the stack trace feature to be a challenge, but it has been well worth it. Listing 12.16 is the code for the MicroMonitor strace command (for a PowerPC).

Listing 12.16: strace Command.
image from book
 int Strace(int argc,char *argv[]) {     char    *symfile, fname[64];     TFILE   *tfp;     ulong   *framepointer, pc, fp, offset;     int     tfd, opt, maxdepth;     tfd = fp = 0;     maxdepth = 20;     pc = ExceptionAddr;     while ((opt=getopt(argc,argv,"d:F:P:rs:")) != -1) {         switch(opt) {         case 'd':             maxdepth = atoi(optarg);             break;         case 'F':             fp = strtoul(optarg,0,0);             break;         case 'P':             pc = strtoul(optarg,0,0);             break;         case 'r':             showregs();             break;             default:             return(0);         }     }          if (!fp) {         getreg("R1", &framepointer);     }     else {         framepointer = (ulong *)fp;     }     /* Start by detecting the presence of a symbol table file... */     symfile = getenv("SYMFILE");     if (!symfile) {         symfile = SYMFILE;     }     tfp = tfsstat(symfile);     if (tfp)  {         tfd = tfsopen(symfile,TFS_RDONLY,0);         if (tfd < 0) {             tfp = (TFILE *)0;         }     }     /* Show current position: */     printf("   0x%08lx",pc);     if (tfp) {         AddrToSym(tfd,pc,fname,&offset);         printf(": %s()",fname);         if (offset) {             printf(" + 0x%lx",offset);         }     }     putchar('\n');     /* Now step through the stack frame... */     while(maxdepth) {         framepointer = (ulong *)*framepointer;         if ((!framepointer)  (!*framepointer)  (!*(framepointer+1))) {             break;         }         printf("   0x%08lx",*(framepointer+1));         if (tfp) {             int match;             match = AddrToSym(tfd,*(framepointer+1),fname,&offset);             printf(": %s()",fname);             if (offset) {                 printf(" + 0x%lx",offset);             }             if (!match) {                 putchar('\n');                 break;             }         }         putchar('\n');         maxdepth--;     }     if (!maxdepth) {         printf("Max depth termination\n");     }          if (tfp) {         tfsclose(tfd,0);     }     return(0); } 
image from book
 

As is the case with most MicroMonitor commands, strace (see Listing 12.16) starts with default initialization and processing of command line options and arguments. Because strace always runs after some exception has occurred, the current address is taken from the global variable ExceptionAddr (which was loaded by the exception handler.) The command accepts several options but usually the defaults are appropriate.

After the options are processed , the code retrieves the stack pointer. The stack is basically organized as a linked list of stack frames . Later, the code uses this pointer to find the first frame.

Before processing the stack though, the code looks for the symtbl file and initializes the tfd descriptor. This descriptor is later passed to AddrToSym() each time the monitor wants to find the symbolic equivalent of an address. If the symtbl file is not present, only the addresses are displayed. The processing loop walks through the currently nested stack frames until some termination criteria is met, either a null frame pointer or a maximum frame depth.

Listing 12.17: AddrToSym()
image from book
 /* AddrToSym():  *  Assumes each line of symfile is formatted as...  *      synmame SP hex_address  *  and that the symbols are sorted from lowest to highest address.  *  Using the file specified by the incoming TFS file descriptor,   *  determine what symbol's address range covers the incoming address.  *  If found, store the name of the symbol as well as the offset between  *  the address of the symbol and the incoming address.  *  *  Return 1 if a match is found, else 0.  */ int AddrToSym(int tfd,ulong addr,char *name,ulong *offset) {     int     lno;     char    *space;     ulong   thisaddr, lastaddr;     char    thisline[84];     char    lastline[sizeof(thisline)];     lno = 1;     *offset = 0;     lastaddr = 0;     tfsseek(tfd,0,TFS_BEGIN);     while(tfsgetline(tfd,thisline,sizeof(thisline)-1)) {         space = strpbrk(thisline,"\t ");         if (!space) {             continue;         }         *space++ = 0;         while(isspace(*space)) {             space++;         }          thisaddr = strtoul(space,0,0);  /* Compute address from         */                                         /* entry in symfile.            */         if (thisaddr == addr) {     /* Exact match, use this entry      */             strcpy(name,thisline);  /* in symfile.                      */             return(1);         }         else if (thisaddr > addr) { /* Address in symfile is greater    */               if (lno == 1) {         /* than incoming address...         */                 break;              /* If first line of symfile         */             }                       /* then return error.               */             strcpy(name,lastline);              *offset = addr-lastaddr;/* Otherwise return the symfile     */             return(1);              /* entry previous to this one.      */         }         else {                      /* Address in symfile is less than  */             lastaddr = thisaddr;    /* incoming address, so just keep   */             strcpy(lastline,thisline);  /* a copy of this line and      */             lno++;                      /* go to the next.              */         }     }     strcpy(name,"???");     return(0); } 
image from book
 

The stack trace function uses AddrToSym() (see Listing 12.17) to convert an address to a function name. This conversion is done by stepping through each line of the symtbl file (using the TFS API function tfsgetline() and assuming that the symbols are sorted in ascending address order). When the address in the symbol table is greater than or equal to the incoming address, the address is considered a match, and the string and offset are returned. The offset is returned only if the address is not an exact match. When there is no exact match, the name of the nearest match is displayed with an offset, as in this example:

 uMON> strace  0x8018844a: errCheck() + 0x18  0x800c7f40: serialTest() + 0x40  0x8004006c: TaskAudit() + 0x88 

This example shows that function TaskAudit() called function serialTest() and that serialTest() called function errCheck() . The exception (or breakpoint) occurred within errCheck() .

Note 

What do you do if your customers report that your product is occasionally just resetting. If the problem occurs only occasionally, it can be very hard to reproduce, and sometimes is related to some event unique to the customer site. So how do you catch such a bug in the act? You probably cant leave an emulator (or an engineer) at the customers site.

With this stack trace capability and some of the other capabilities in MicroMonitor, you can configure the environment so that any exception automatically causes the monitor to dump a stack trace to a file in the file system and then will restart the application.You can later retrieve the file and analyze it in your lab.



Embedded Systems Firmware Demystified
Embedded Systems Firmware Demystified (With CD-ROM)
ISBN: 1578200997
EAN: 2147483647
Year: 2002
Pages: 118
Authors: Ed Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net