While CloudpilotEmu provides support for debugging, it does not provide its own user interface for debugging. Instead, CloudpilotEmu implements the remote debugging protocol of the GNU debugger (GDB). This part of the emulator is called the GDB stub.
If the stub is enabled, CloudpilotEmu opens a port and listens for incoming debugger connections. After connecting, GDB can be used to interrupt and step the virtual CPU, inspect registers and memory, set breakpoints and watchpoints, and more.
IMPORTANT: Only the native CLI build of CloudpilotEmu supports debugging, the web version does not contain the GDB stub! Precompiled binaries for the CLI build are not provided, you'll have to build it from scratch.
With GDB connected to CloudpilotEmu, debugging at the level of m68k assembly works out of the box. However, GDB can also be used for symbolic debugging of PalmOS applications that have been produced with a modern toolchain.
In order to provide symbolic debugging at the source level, GDB requires access
to the debug symbols for the compiled app. Debug symbols are emitted by the
compiler during compilation with -g and provide the information required for
associating memory locations and instructions with actual variables and lines of
source code.
Debug symbols usually reference a fixed address space layout. However, on PalmOS applications are installed into RAM and run in place, so the relevant addresses depend on the exact location on the storage heap where the application is installed. This location differs between individual devices and may even change after installation if PalmOS decides to compact the heap by moving databases around.
In order to render the symbols useful to GDB, CloudpilotEmu can track down an applications code resource in memory and automatically communicate the corresponding relocation offset to GDB when it connects. This enables GDB to directly use the ELF file produced by GCC for debugging an application at the source level.
The GDB stub is only available in the native version of CloudpilotEmu (and not in the web version). Please follow the instructions in the README in order to build it from source.
A version of GDB built for the target m68k-none-elf is required for debugging
with CloudpilotEmu and needs to be built from source. Any halfway recent version
of GDB will do (don't use the version included in PRC tools, it will not
work). Please check out the GDB documentation for more details.
Launch CloudpilotEmu with -p xxxx in order to enable the stub and listen on
port xxxx . If you want CloudpilotEmu to pause and wait for the debugger on
startup, you can do so by adding --wait-for-attach to the command line.
Start GDB and connect to CloudpilotEmu by doing
$ target remote localhost:xxxx
in the debugger shell. On successful connection, CloudpilotEmu will pause execution and wait for further commands. Please check the GDB documentation for more details on debugging from there
Most parts of the CloudpilotEmu CLI remain functional even when the debugger is
attached and the emulator is paused. In particular, CLI commands can be used to
save savestates, dump memory, generate m68k traces (using macbug-style function
names that are embedded into most PalmOS apps and into PalmOS itself) and break
on system traps. Check help on the CLI for more information.
In order to debug an application, the app must be compiled with debug symbols
enabled (-g -ggdb). Optimization should be set to -O1 --- -O0 may produce
code that is too large to fit into a single code resource, and higher levels
will make debugging harder.
On startup, GDB needs to be pointed to ELF file generated by GCC during
compilation. Usually, during build, the relvant sections are copied from the ELF
with objcopy and turned into resources in a .prc with `pilrc``. Make sure that
your build process preserves the ELF file and pass it to GDB as a command line
argument.
In order to setup up the necessary relocation information, pass the ELF file to
CloudpilotEmu with the --debug-app command line option or with the
debug-set-app CLI command. CloudpilotEmu will search for the .text section in
PalmOS memory and communicate the corresponding relocation offset to GDB on
connection.
Please not that this will only work if the app has been previously installed on the device. It is a good idea to prepare a savestate file with the app preinstalled in order to simplify debugging.
Without an app set up for debugging, CloudpilotEmu will stop on all instructions, independent of whether they are located in RAM or ROM. However, when stepping over a system call during source-level debugging, this will throw off gdb and make it break on the vector, even if it was instructed to step over the call.
In order to avoid this issue, CloudpilotEmu can be told to stop only on
instructions that are located in RAM (debug-set-break-mode ram-only on the
CLI) or instructions located in the .text section of the ELF that is being
debugged (debug-set-break-mode app-only on the CLI). The second mode is the
default when an ELF has been loaded in CloudpilotEmu.
GDB insists on stepping each individual instruction when stepping over a line, which can make stepping over large functions a bit tedious. You can use breakpoints to step over function calls quickly in such scenarios.
VSCode can be used as a frontend for GDB, which makes debugging with CloudpilotEmu a bit more convenient. In order to do so, install the C/C++ extension for VSCode it will provide the necessary integration with GDB.
In order to build the app to be debugged, VSCode needs to be told how
to invoke the build system. This is done by adding a tasks.json file to the
.vscode directory in the project root. Additonally, the build task must
launch CloudpilotEmu with the correct command line options.
You will need to adjust the following example to match your environment and
needs, but it should be a good starting point:
{
"version": "2.0.0",
"tasks": [
{
"label": "Elf",
"command": "make code0001.68k.elf",
"type": "shell",
"options": {
"cwd": "${workspaceFolder}"
},
"hide": true
},
{
"label": "Prc",
"command": "make",
"type": "shell",
"options": {
"cwd": "${workspaceFolder}"
},
"hide": true
},
{
"label": "CP",
"command": "/path/to/cloudpilotnative -l 6667 path-to-session.bin -s cloudpilotStartDebug.cp",
"type": "shell",
"options": {
"cwd": "${workspaceFolder}"
},
"hide": true
},
{
"label": "Run",
"dependsOn": ["Elf", "Prc", "CP"],
"dependsOrder": "sequence",
"icon": {
"dark": "icon/Run.svg",
"light": "icon/Run.svg"
},
"problemMatcher": {
"owner": "cpp",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": {
"regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
"file": 1,
"line": 2,
"column": 3,
"severity": 4,
"message": 5
}
}
}
]
}The example above is composed of several tasks that are chained together. The
first two tasks are used to build the app. The CP task is used to launch
CloudpilotEmu with the correct command line options, and the Run task
chains all the previous tasks together and run them in sequence.
The Elf task is optional in the way that it can be removed if the app
preserves the ELF file during build.
As mentioned above, you will need to adjust the paths and command line options to your needs. In particular, you will need to adjust the path to the CloudpilotEmu Native executable, the path to the session image, and if it's the case, the ELF file name as well.
The session image, which is the file CloudpilotEmu loads on startup, should contain a ready-to-work PalmOS state. You can create this session image by following these steps:
- Launch a new session on CloudpilotEmu Web.
- Complete the PalmOS tutorial.
- Save the image from the Sessions tab by selecting the session, right-clicking on it, and choosing Save. This will download a file with the .bin extension, which will serve as the session image.
Once you have created this session image, you can reuse it for all subsequent debugging sessions. The latest version of the app will be automatically installed into it upon startup.
After creating the session image, you will need to create a CLI script that CloudpilotEmu will execute on startup. This script will be used to automatically install the latest version of the app to be debugged and to set up the relocation information for GDB. The script also launches the app, so that CloudpilotEmu will be in a state where it can be debugged immediately.
The following example script assumes that the prc to be debugged is called
myapp.prc, that the ELF file is called code0001.68k.elf, and that those
files are located in the root of your project. If the location differs, please
adjust the script accordingly to match the location of the PRC and ELF files.
It is also assumed that this file is located in the root of your project. If
the location differs, you will also need to adjust the script location in the
CP task in tasks.json above.
Create a file called cloudpilotStartDebug.cp in the root of your project
with the following content:
# Install the app to be debugged
install myapp.prc
# Set up the relocation information for GDB
debug-set-app code0001.68k.elf
# Launch the app
launch MyApp
So far, we have set up VSCode to build the app and launch CloudpilotEmu Native
with the correct command line options. All that is left is to tell VSCode
how to attach to GDB and which ELF file to load. This is done by setting up a
launch configuration. Launch configurations are stored in launch.json in the
.vscode directory in the project root. Add the following to launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "gdb",
"request": "attach",
"name": "Attach to CloudpilotEmu",
"executable": "./code0001.68k.elf",
"target": ":6667",
"remote": true,
"cwd": "${workspaceRoot}",
"gdbpath": "m68k-none-elf-gdb",
"valuesFormatting": "prettyPrinters"
}
]
}The example above assumes that the ELF file is called code0001.68k.elf and
that it is located in the root of your project. If the location of this
file differs, please adjust the executable field accordingly to match the
location of the ELF file. You will also need to adjust the path to the GDB
executable in the gdbpath field to match your environment.
You should now be able to debug your app by, in this order:
- Running the
Runtask, which will build the app and launch CloudpilotEmu Native with the correct command line options. - After CloudpilotEmu Native has started, attaching to GDB by
selecting the
Attach to CloudpilotEmulaunch configuration and pressing theStart Debuggingbutton in VSCode. You should now be able to debug your app as usual, setting breakpoints in VSCode, step through the code, and etc.
Remember to always close CloudpilotEmu and re-run the Run task if you make any
changes to your app's code, this will ensure that the latest version of the app
is installed into the session image.