Debugging a remote core from the Linux terminal#

This section is a continuation of the information in Debug the LED Blink Program from the Linux terminal.

CCS gives the developer many debug tools that Linux does not have access to: pausing code, stepping through instructions, viewing internal registers, and more. If using CCS to debug is an option, we suggest using CCS instead of trying to do the entire debug through Linux. For more information about CCS debug while Linux is running, reference section Debug the remote core program from CCS while Linux is running.

If CCS is not a debugging option, there are several basic debugging tools that are still available from the Linux command line.

Use devmem2 to read and write memory#

the devmem2 tool allows Linux to implement several debug strategies by reading and writing data to and from the remote core. RPMsg can also be used to pass data back and forth between Linux and the remote core, but this section will assume that RPMsg is not used in the design.

How to find what memory to read and write#

The memory that Linux and the remote core is using to read and write can be anywhere in the processor system. However, one common usecase is to identify a specific region of DDR memory that is used to pass data back and forth.

Let’s take a Linux RPMsg Echo example. First, build the example. Then, read the .map file that is generated: examples/drivers/ipc/ipc_rpmsg_echo_linux/<board>/<core>/ti-arm-clang/ipc_rpmsg_echo_linux.release.map

In this particular example, the first part of the .map file looks like this:

******************************************************************************
        TI ARM Clang Linker Unix v2.1.3
******************************************************************************
>> Linked Sun Sep 17 16:43:46 2023

OUTPUT FILE NAME:   <ipc_rpmsg_echo_linux.release.out>
ENTRY POINT SYMBOL: "_vectors"  address: 00000000


MEMORY CONFIGURATION

         name            origin    length      used     unused   attr    fill
----------------------  --------  ---------  --------  --------  ----  --------
  R5F_VECS              00000000   00000040  00000040  00000000  RWIX
  R5F_TCMA              00000040   00007fc0  000010f8  00006ec8  RWIX
  R5F_TCMB0             41010000   00008000  00000000  00008000  RWIX
  FLASH                 60100000   00080000  00000000  00080000  RWIX
  MSRAM                 70080000   00040000  00000000  00040000  RWIX
  DDR_0                 a0100000   00100000  00001000  000ff000  RWIX
  DDR_1                 a0200000   00e00000  00023cc0  00ddc340  RWIX
  RTOS_NORTOS_IPC_SHM_M a5000000   00800000  00006680  007f9980  RWIX
  USER_SHM_MEM          a5800000   00000080  00000000  00000080  RWIX
  LOG_SHM_MEM           a5800080   00003f80  00000000  00003f80  RWIX

DDR_1 is the DDR memory set aside by Linux for the R5F core to use. We see that 0xE0_0000 of memory is reserved, from 0xA020_0000 to 0xA0FF_FFFF. However, only 0x2_3CC0 of that memory region is currently used. That means we can use the rest of that memory region for passing data between Linux and the remote core, and no other code in the system will corrupt the data.

For example, we could use 0xA050_0000 as the starting address for the memory addresses that Linux and the remote core firmware used to pass data back and forth.

Usecases for reading from memory#

Usecases here are similar to the usecases when the developer would want to add DebugP_log print statements.

Usecases for writing to memory#

Writing to memory is useful in multiple situations, like being able to easily pass different values to the remote core program to see how it responds. However, one of the most powerful usecases for writing to memory that the remote core is reading is implementing software breakpoints.

For example, if the developer wants to inspect memory values at a certain point during the remote core execution, they could implement a breakpoint in their remote core firmware where the remote core waits for a value to be written to a known memory address before it continues executing code. That allows the developer to inspect memory values once the breakpoint has been reached, and then tell the remote core to continue running by writing a value to a known memory address.

The logic to implement a software breakpoint on the remote core side could look like this:

set the value of address 0xA050_0004 to 0
while the value at 0xA050_0004 = 0, loop
// once the user is ready for the program to continue running,
// use devmem2 w to write a value to the memory address and break the loop