Debugger support for Greengrass

NOTE: Currently this content has only been tested on Greengrass V1 but will be updated for Greengrass V2 soon

Attaching a debugger to a process running on a Greengrass Core is something I dreamed of doing since the first release of Greengrass V1 years ago. I've managed to pull it off in a few different ways but here I'll share the most recent methods I've used to do it for Java, NodeJS, and Python 3.

Java

TODO

NodeJS

TODO

Python

Python debugging is implemented with pydevd-pycharm.

For Python 3 I replaced /usr/bin/python3.7 with the script below. Here's the high level overview of what it does:

  • Adds in debug shim code into a Python function
  • Opens a server on localhost that waits for a connection from a client
  • When the client connects to the server (telnet, netcat, etc) the server asks it for a port number
  • The client enters the port number that its debug server is running on
  • The server tries to connect Python to the client's debug server on that port on 127.0.0.1. This requires the client to be running on the host or to port forward the host's local port to its debug port.

The debug code was designed like this since the Python debugger I used runs as a server as opposed to a client. Instead of attaching to a process like gdb or Java Debug Wire Protocol JDWP the process reaches out to the debugger.

In detail it does this:

  • Removes /usr/bin from the process's PATH environment variable. This is so it avoids recursively invoking itself.
  • Checks to see if the DEBUG_PORT variable is set. If it isn't set it sets it to a random number between 2000 and 9999 so it avoids privileged ports.
  • Attempts to find the real Python executable. If it cannot find Python it reports an error and exits with error code 1.
  • Checks to see if the DEBUG variable is set. If the variable is not set then it simply runs Python normally.
  • If the DEBUG variable is set it does the following:
    • Pipe the debug shim code into a Python interpreter
    • Pass the Python interpreter the command-line options passed from Greengrass (usually just the script name)
    • Start the interpreter
  • The debug shim code does the following:
    • Print that the debug code has been loaded along with the port number that it is listening on
    • Binds to the port number on 127.0.0.1
      • NOTE: Remote debugging is achieved by using an SSH tunnel and port forwarding the debugger to the client. However, this can be skipped in a closed environment by binding to a public IP address. This must not be done in production!
    • Waits for a client to connect and provide a port number to call back to the debugger
    • Attempts to connect to the provided debug port on 127.0.0.1 with pydevd_pycharm.settrace

Code: