pyocd / pyOCD

Open source Python library for programming and debugging Arm Cortex-M microcontrollers
https://pyocd.io
Apache License 2.0
1.09k stars 475 forks source link

Reads via gdb are not marked as cacheable #1323

Open j4cbo opened 2 years ago

j4cbo commented 2 years ago

It appears that when I perform a memory read [edit: on a Cortex-M7, STM32H750] through gdb and pyocd gdb, what I get is the actual contents of the backing SRAM, not the view as seen by the CPU - which means that if the D-cache is enabled and I'm reading something in cacheable memory that hasn't been cleaned from the cache, I won't get the right results. This leads to some confusing behavior:

# Random data in AXI SRAM after boot
(gdb) x/16w 0x24020000
0x24020000: -1760592895 1630186032  89348259    1028313498
0x24020010: -1061064142 99499594    1160210010  -1502964292
0x24020020: 89785445    -1251708343 -1768125164 -1424349768
0x24020030: 1224770264  -897671144  1670031863  1483403987
# Use the CPU to memset it
(gdb) call memset((void*)0x24020000, 0, 32)
$1 = (void *) 0x24020000
# Result of memset is not visible
(gdb) x/16w 0x24020000
0x24020000: -1760592895 1630186032  89348259    1028313498
0x24020010: -1061064142 99499594    1160210010  -1502964292
0x24020020: 89785445    -1251708343 -1768125164 -1424349768
0x24020030: 1224770264  -897671144  1670031863  1483403987
# But if memset is immediately followed by a cache clean...
(gdb) call memset((void*)0x24020000, 0, 32)
$2 = (void *) 0x24020000
(gdb) set SCB->DCCMVAC = 0x24020000
(gdb) x/16w 0x24020000
0x24020000: 0   0   0   0
0x24020010: 0   0   0   0
0x24020020: 89785445    -1251708343 -1768125164 -1424349768
0x24020030: 1224770264  -897671144  1670031863  1483403987

Once again I'm not sure what the right fix here is. :( It looks like the AHB-AP allows setting e.g. HPROT_CACHEABLE, but it seems like what we actually want for gdb is to use whichever cacheability settings the CPU would apply, as configured through the MPU if applicable, right?

flit commented 2 years ago

Hi @j4cbo, yeah, that's a tricky one without a completely right answer. I think it depends on what you're debugging—ultimately it's something you need to configure. In almost all cases in my experience, the debugger by default reads and writes the actual memory contents, ignoring any cache. Also, the various MEM-APs all reset to HPROT == 0x3 (data access, privileged, nothing else enabled).

In pyocd you can control the HPROT value for memory transactions using the set hprot x command, and show hprot to view the current setting. These modify/view the currently selected MEM-AP (set/show mem-ap), which is independent of the gdbserver's core (not something to worry about if you are only debugging core #0).

To enable cacheable transfers from gdb, you'd run monitor set hprot 0xb. This can be placed in a connect script if you want cacheable transfers all the time.

I've not heard of a debugger that reads MPU settings and applies them to its own transfers (but I could certainly have missed such a feature!); that's not something the hardware supports automatically. That would be a pretty neat feature, though. But I'm not even sure it would be something that should be enabled by default.

tyalie commented 1 year ago

How would such a connect script look like? I have quite a few issues finding the right APIs I'd need to call in pyOCD.

flit commented 1 year ago

If you're using gdb, you don't need to directly use the pyOCD APIs. Just use the monitor set hprot 0xb gdb command immediately after gdb connects to pyocd. I'd set this up in config for whatever debugger you're using. For instance, using cortex-debug in VSCode, add it to postAttachCommands. You can use monitor show hprot to verify the settings.

If you want to avoid doing this through gdb commands for whatever reason, you can create a user script with containing this snippet:

def did_connect():
    target.aps[0].hprot = 0xb

And if using pyOCD via a Python script, mirror the statement inside did_connect() above using your target object.

Please note that although I've tested that the above commands work and correctly set HPROT, I don't have an easy way to really verify that the D$ contents are being read rather than raw memory contents. No reason to believe it shouldn't work though, at least on Cortex-M7 based devices using the core's built-in D$.

For reference, here are the HPROT bits for Cortex-M devices (show hprot will also display something like this). Not all bits are implemented on all core/MEM-AP variants; each MEM-AP version implements slightly different sets of these options. For instance, the standard CM3 and CM4 MEM-AP only implements HPROT[1] (privilege, user).

  HPROT[0] = 1 data access, 0 instr fetch
  HPROT[1] = 1 privilege, 0 user
  HPROT[2] = 1 bufferable, 0 non bufferable
  HPROT[3] = 1 cacheable/modifable, 0 non cacheable
  HPROT[4] = 1 lookup in cache, 0 no cache
  HPROT[5] = 1 allocate in cache, 0 no allocate in cache
  HPROT[6] = 1 shareable, 0 non shareable

In the above commands, 0xb == data (hprot[0]) | privileged (hprot[1]) | cacheable (hprot[3]).