Monitor-based debugging

Infineon / Mitsubishi / Fuji / Semikron / Eupec / IXYS

Monitor-based debugging

Posted Date: 2024-01-25

When you think about debugging an embedded system, what comes to mind? I guess it depends on your background and budget allocated to the tool. You might consider a JTAG/BDM-based debug port, an emulator or logic analyzer, printf(), or one of the many sophisticated source-level debuggers available today. Each solution has its own advantages and disadvantages. Some are very powerful, but costly. Some are related to specific compiler toolsets, while others are only useful on certain CPU families. JTAG/BDM based debug ports are probably the most common nowadays because they provide a good balance between cost and functionality. These devices still cost several thousand dollars and are only useful when connected to a system, often requiring some bulky pod hung fairly close to the target.

The subject of this article is something of a dying art: monitor-based debugging. What is monitor-based debugging? First, it assumes that your application is running on a system launched with some kind of monitor. The launch monitor is the base platform on which the application resides, and if the application crashes, the monitor will take over.

When in ROM

Monitor-based debugging relies on functionality built into the processor, and in most cases (not always) this requires being able to write to the instruction space. Typically, you set breakpoints in the monitor's command line interface (CLI) and then transfer control to the application. If the instruction that sets the breakpoint is executed, a branch is taken from the application and control is returned to the monitor's CLI. Monitors now have the ability to display memory, possibly step through, or even return to the application where the breakpoint was.

Sounds good? Well, it certainly can, but there are a lot of complications that can arise, such as:

How to display memory? Typically, the monitor can display raw memory as blocks of 1, 2, or 4 byte units, but you must specify the address in hexadecimal. Since you are running on the target's CLI, probably all you can do is refer to the output map file generated by the linker to determine where the symbols are in the data/bss address space. You have to associate the symbol with some hexadecimal address. If the build changes, the memory map changes, so the next time you want to look at the same chunk of data, you have to do the address-to-symbol association again. Additionally, the monitor doesn't know how to display in the format you want, such as short, long, and string. Forget structure display.

How to set breakpoints? Similar to the previous question, first find the address of the function and then issue some command like "b 0x123456" where 0x123456 is the address of the function where you want to set the breakpoint.

How does the monitor communicate with the serial port once a breakpoint occurs? The monitor takes over due to the breakpoint; but the application now owns the serial port. If the monitor reinitializes the serial port for use, the interface between the application and the port may become confused. This means that returning control to the application will be very difficult.

Monitor How to temporarily close an application? This can get tricky when the application is running on an RTOS, enabling interrupts and configuring various peripherals.

Single-stepping is now at the assembler level rather than the C language level. This is not very useful for programmers.

Perhaps this explains why monitor-based debugging is no longer popular. Before we give up, though, I'd like to revisit the topic and see if there's still some breath left in this old beast.

Debugging concept

Let’s start by setting some boundaries. We have to accept the fact that monitor-based debugging environments have limitations. After we establish some guidelines and rethink how some of these things are done, I think you'll agree there's still quite a bit of capability. So let's build a debug model that launches the monitor. What do we gain and what do we sacrifice?

For memory display, we will be able to display "C-like" data: strings, characters, shorts, integers, longs, and even data structures. Data structures can be displayed individually, as a table, or as a linked list. We will be able to reference data symbolically. This means that a global variable named "SysTick" can be referenced as "SysTick" without knowing its location in memory. We'll do a runtime analysis that considers breakpoints as one of its features, rather than thinking in the context of typical breakpoints. There are standard breakpoints that terminate application execution and "auto-return" breakpoints for runtime analysis. After the monitor catches a breakpoint or exception, a symbolic stack trace is available.

We will not have access to the source code line number information; however, if the compiler supports the ability to dump mixed source and assembly code, then we can solve this problem. We won't consider single-stepping because it's assembly level; however, if we implement everything else, assembly-level single-stepping is a handy giveaway. Since we can only access global variables, the stack trace will not show the parameters passed, and local data cannot be retrieved from the stack. One and important limitation of this debugger is that control cannot be returned to the running application after a hard breakpoint.

If these considerations are acceptable, the end result is a debugging environment that exists alongside the application. It can be shipped with the product and used in the field and is somewhat independent of the compiler toolset, the RTOS used, and to some extent CPU and hardware independent.

#Monitorbased #debugging