For the lastest information on how COVID-19 is impacting our business, please see our updates page.

Tracking Traps on the MODM7AE70 – Part 1

Magnifying glass and detective gear.
Share on facebook
Share on reddit
Share on twitter
Share on linkedin
Share on email
Share on print

Have you ever found yourself writing an application that, at runtime, reboots seemingly at random? Maybe your application prints trap output to a serial port. It happens to the best of us.

In this article, we will explain what to do with the trap output, and the workflow commonly used by the engineers here at NetBurner when tracking down the source of a trap.

The Secret's Out. Try Our ARM® Embedded Dev Kit Today.

Netburner ARM Cortex M7 embedded Development Kit for IoT product development and industrial automation.

Or, learn more about NetBurner IoT.

What Is A Trap

A trap is an exception in the application that is caused by the execution of an illegal instruction. Some examples of an illegal instruction could be an attempt to read/write to an invalid memory address, or performing an arithmetic calculation that divides by zero. If an application attempts to execute an illegal instruction, it will reboot.

Luckily, NetBurner supports a feature called Smart Traps that prints trap information to the default serial port when a trap occurs. It prints useful information like the call stacks of all your tasks, including the task that was running while the trap occurred. In most cases, this information will point you directly to the line of code that caused the trap.

Some Good News

Luckily, NetBurner supports a feature called Smart Traps that prints trap information to the default serial port when a trap occurs. This information provides details on the call stacks of all your tasks, including the one that was running while the trap occurred. In most cases, this information will point you directly to the line of code that caused the trap. Smart Traps are provided for most NetBurner platforms, but in this article, we will be dealing specifically with the MODM7AE70.

There is no performance hit in including this feature in your application. You may want to avoid using this feature if you’re using your serial ports to interface with peripherals. Otherwise, we recommend using this feature, particularly during development.

No performance hit!
Smart Traps won’t slow you down!

Smart Traps In Action

Let’s take a look at how to use Smart Traps and dive into an example. Smart Traps can be enabled by including the SmartTraps.h header file and by calling the function EnableSmartTraps() at the beginning of UserMain(). The following example demonstrates how to enable Smart Traps. It also shows a divide-by-zero trap.

/* Can cause a divide-by-zero trap if the divisor is equal to zero */
uint32_t doDivide(uint32_t divisor)
{
    return (TimeTick/divisor);  // TimeTick represents the Ticks since the system has booted
}

/* This function is safe from a divide-by-zero trap since it checks for a zero value before dividing */
uint32_t doSafeDivide(uint32_t divisor)
{
    if (divisor == 0)
    {
        return 0;
    }
    else
    {
        return (TimeTick/divisor);  // TimeTick represents the Ticks since the system has booted
    }
}

void UserMain(void *pd)
{
    init();                     // Initialize network stack
    WaitForActiveNetwork();
    EnableSmartTraps();         // Allows the system to print trap info to serial

    uint32_t myDivisor = 0;

    iprintf("Application: %s\r\n", AppName);

    while (1)
    {
        iprintf("Press any key to proceed.\r\n");
        getchar();
        iprintf("doSafeDivide() returned: %d \r\n", doSafeDivide(myDivisor) );

        iprintf("Press any key to proceed.\r\n");
        getchar();
        iprintf("doDivide() returned    : %d \r\n", doDivide(myDivisor) );  // Trap occurs here
    }
}

Notice that the example has two functions that perform divide calculations. Those functions are doDivide() and doSafeDivide(). The doSafeDivide() function does not trap since it checks for a divisor equal to zero before performing the division. In contrast, the doDivide() function does not perform the check. When the application calls doDivide(), it will result in the following trap output.

It’s not pretty… but it can be helpful.

There are three pieces of information that should be looked at – that is:

  • The faulted PC address
  • The current task running
  • The call stack for that task

These are outlined in red in the screenshot above. The task running while the trap occurred is Main#32 (that is a task priority of 0x32 in hex or 50 in decimal). Note that the Faulted PC value is also in the Main#32 call stack. Using this information, we can usually find which line of code caused the trap. This is done by using the addr2line tool.

The addr2line Tool

The addr2line tool converts the address of an instruction from your application’s .elf file into a line of code in your application’s source files. The .elf file is automatically generated during the build process, along with the application binary. It is VERY important that you use the .elf file that corresponds to the application that is running on your module. If you make even a small incremental change, it can change your .elf file drastically. Realizing the.elf file your drilling into doesn’t match what’s running on your module is only funny the first dozen times or so.

addr2line UI
The addr2line tool in all its glory.

There are two ways of using the addr2line tool. If you’re developing on Windows, you can use the desktop application that can be found in /nburn/pcbin/WinAddr2Line. The image previous shows an example of usage, and the output if we use the Faulted PC value from our trap. It outputs the function and line number where the trap occurred.

If you’re developing on OS X or Linux, you can use the addr2line command-line tool. The following snippet shows example usage by inputting the Faulted PC address from the trap above. The addr2line flags used in this example are some that we like using, but you can find more flags with their descriptions by using the command, arm-eabi-addr2line -help(note that for ColdFire based platforms, this command is m68k-elf-addr2line).

> arm-eabi-addr2line -Cife obj/release/SmartTrap.elf 70019D92

doDivide(unsigned long)
/SmartTrap/src/main.cpp:13
UserMain(void*)
/SmartTrap/src/main.cpp:47

Something to note from the trap output above is that the Faulted PC address is also in the Main#32 call stack. It is not uncommon for the call stack to have several addresses. In which case, it might be helpful to input all of those addresses in the addr2line tool. This allows you to get the full picture of what is going on, and can provide more insight as to what went wrong between function calls.

Wrapping Up

This divide-by-zero trap example was a simple one to help explain the tools and techniques, but Smart Traps can be helpful in solving complex bugs. Some bugs we’ve solved with Smart Traps include race conditions in variables that are shared across tasks/interrupts, deadlocks in critical sections, and much more.

Now that you know the trap hunting workflow used by the engineers here at NetBurner, I challenge you to fearlessly develop complex applications!

In part two of this article, we will explain a bit more about the trap output, such as register information. We will also explore an example of a faulted PC address that will have us searching through disassembly. Stay tuned!

As always, we love feedback. If you have any comments or thoughts, please feel free to drop them in the comments section below. Alternatively, you can mail us directly at sales@netburner.com

Share this post

Share on facebook
Share on twitter
Share on linkedin
Share on reddit
Share on email

Subscribe to our Newsletter

Get monthly updates from our Learn Blog with the latest in IoT and Embedded technology news, trends, tutorial and best practices. Or just opt in for product change notifications.

Leave a Reply

Your email address will not be published. Required fields are marked *

Click to access the login or register cheese