How to use the memory guard ("Catch mem accesses") debugging feature
Rockbox is mainly written in C, with some assembler for time critical parts. Both languages lack one feature that would come in very handy for debugging - there is no range checking for array indices and pointers. Therefore it happens easily that you write some code that inadvertently accesses memory areas it shouldn't. One case are uninitialized pointers (pointing to NULL which is essentially address 0), another would be writing to write protected areas (const data). The latter case will usually be detected by the compiler, unless you do explicit casting.
While accessing those areas even for writing will usually not crash rockbox because there is nothing writable at those addresses (for writing to const data this is only true for RomBox
), it is still an error one would like to find.
How? The SH1 CPU used in our boxes does not feature an MMU, which would allow fine-grained memory access control. However, it features the "user break controller" (UBC), which can be mis-used as a "poor man's MMU". While this is somewhat limited, it is still better than nothing.
How to use
This feature is of course only available on the real target. It does not work in the simulator.
You will find a new menu item in the debug menu: "Catch mem accesses". The default setting is "None". If you want to check some code for illegal memory accesses, set this to one of the following modes:
|| This is the default. No check for illegal accesses is performed.
| Flash ROM writes
|| Select this if you want to check for write accesses to const data. This does only work when running RomBox, and for the main rockbox code only. It is mainly intended for debugging RomBox. This catches all write accesses to address 0x02?????? (area 02 for short)
| Zero area (all)
|| Select this if you want to check for any access to the zero area due to uninitialized pointers. This catches all accesses (read or write) to addresses 0x00?????? and 0x01?????? (areas 00 and 01 for short)
As this setting is not stored in a variable, but directly written to/ read from the UBC, it will survive RoLo
, this enables testing the rockbox init as well. However, the setting will not survive poweroff, as it is not saved within the config block.
There are some (rare) places where the check will be temporarily disabled, due to deliberate access of the protected areas. These places are:
| Debug->Dump ROM contents
|| Accesses area 00 (boot ROM) for reading
| Debug->View HW info
|| Accesses area 00 (boot ROM) for reading (CRC check and comparing with flash ROM to detect ROM-less boxes) and area 02 (flash ROM) for writing (flash ID check)
|| Accesses area 00 (boot ROM) for reading (CRC check and comparing with flash ROM to detect ROM-less boxes) and area 02 for writing (flashing)
|| Accesses area 02 for writing (flashing)
Performing your tests
Simply use the functions/ features of rockbox that use the code you want to test. It will work as usual as long as there is no illegal access detected. However, if an illegal access happens, you'll see a message like the following one on the screen:
The hexadecimal number following the
will vary, as this is the address of the PC at the moment the illegal access took place. (Yes, this is the output of the default handler for unexpected interrupts. I didn't bother to write my own handler, since this would not enable retrieving additional information, but only increase code size. Hey, this is a low-level debug feature anyway!)
In order to take full advantage of that feature, you'll need some understanding of SH1 assembler and architecture. I suggest reading both the SH1 programming manual and the SH1 hardware manual. The rockbox memory map is found on this page.
How do you interpret an actual UserBrk exception? You'll need the following additional info
- The address of the UserBrk exception. This tells you the value of the program counter at the point the illegal access was detected. It is not possible to tell the address that was access, nor whether it was a read or write access (unless you catch write accesses only)
- The linker map file (rockbox.map when debugging rockbox itself, [pluginname].map when debugging [plugin].rock. See later)
- Possibly a disassembly of the binary
For the following table I assume your build directory is one level down from the rockbox source tree.
|| Typical UserBrk address
|| How to create the disassembly
| Debug ordinary rockbox
|| ../tools/sh2d -sh1 -o 0x09000000 rockbox.bin >rockbox_dis.s
| Debug RomBox (recorder V1)
|| ../tools/sh2d -sh1 -o 0x02011010 rockbox.bin >rockbox_dis.s
| Debug RomBox (recorder V2)
|| ../tools/sh2d -sh1 -o 0x02012010 rockbox.bin >rockbox_dis.s
| Debug [plugin] (2 MB boxes)
|| ../tools/sh2d -sh1 -o 0x091F8000 [plugin].rock >[plugin]_dis.s
| Debug rockbox (all)
|| Exceptions in this area are tricky to analyze with disassemblies, because code for this area (the SH1 internal RAM) gets moved by the rockbox init
- open the .map file in an editor
- search for the code fragment (marked with .text) that has the highest start address below the exception address
- within that fragment, search for the function symbol with the highest start address below the exception address and that with the lowest start address above the exception address. Beware, the symbols are not sorted by address!
- look into the source file of that fragment: if there are no static functions between the two functions you determined in the last step, you are lucky: you now know the function that caused the exception
- if there are static functions in between, or you want to find the exact spot of the exception, you'll have to use the disassembly.
Rockbox memory map
|| normal access
|| internal (boot) ROM
|| read (1)
|| flash ROM
|| read (2)
|| MAS PIO (3)
|| on-chip modules
|| 0x06100100..0x06100107, 0x06104100, 0x06200206, 0x06200306
|| internal RAM
- debug functions only
- write for flash ID reading and flashing
- recorders only
Copyright © by the contributing authors.