Skip to content

Latest commit

 

History

History
311 lines (249 loc) · 12.4 KB

File metadata and controls

311 lines (249 loc) · 12.4 KB

Debugging PalmOS and PalmOS apps with CloudpilotEmu

A bit of theory

The GDB stub

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.

Symbolic debugging of PalmOS applications

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.

Debugging in practice

Building CloudpilotEmu native

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.

Building GDB

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.

Launching CloudpilotEmu

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.

Connecting GDB

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

Using the CLI while GDB is attached

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.

Compiling apps for symbolic debugging

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.

Loading the ELF file in GDB

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.

Setting up relocation information

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.

Stepping over system calls

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.

Performance

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.

Debugging with VSCode

Installing the C/C++ extension

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.

Setting up the build task

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:

  1. Launch a new session on CloudpilotEmu Web.
  2. Complete the PalmOS tutorial.
  3. 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.

Setting up CloudpilotEmu Native CLI Script

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

Setting up the launch configuration

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.

Debugging

You should now be able to debug your app by, in this order:

  1. Running the Run task, which will build the app and launch CloudpilotEmu Native with the correct command line options.
  2. After CloudpilotEmu Native has started, attaching to GDB by selecting the Attach to CloudpilotEmu launch configuration and pressing the Start Debugging button 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.