Files
apache--tvm/python/tvm/exec/microtvm_debug_shell.py
T
Andrew Reusch f956c38cd7 [µTVM] Add serial transport, parameterize µTVM Zephyr test, run on physical HW (#6789)
* [BUGFIX] Respect infinite-timed session start timeouts.

 * When debugging, the intended behavior is to set the session start
   timeout to infinite to allow the user to configure the debugger.
 * At present, if a session start retry timeout is defined, the
   current logic will bail after the retry timeout expires.
 * This change makes the session start logic retry forever, once per
   retry timeout.

* Document RPCEndpoint::Create.

* Add stm32f746xx to tvm.target.micro() call; fix parameter name.

 * This API is expected to just be used with positional args, not
   kwargs, so this change isn't expected to cause any breakage.
 * model is more inline with the rest of the file, given TVM Target
   Specification RFC.

* [BUGFIX] If session start fails, exit transport context manager.

 * If an error occurred during session setup, then complex transports
   e.g. DebugWrapperTransport would not de-initialize.

* Align transport writes/reads in TransportLogger

* fix syntax errors which were not exercised in previous PR

* Remove microTVM logic from standard RPC server, add debug shell.

 * microTVM uses the host RPC server as a way to launch a debugger in
   a dedicated, separate terminal window. microTVM needs to be able to
   launch the debugger itself, because its model of the device
   flash/debug flow separates these two things into distinct
   operations implemented by shell commands (for maximum portability
   across frameworks).
 * microTVM can be configured to launch the debugger (e.g. GDB) in the
   same terminal as is used for flashing, but this is sub-optimal
   because then it hides any logs emitted by the device.
 * Using the standard RPC server was hard because GDB expects the user
   to issue SIGINT to interrupt program flow, but due to the RPC
   server's necessary use of multiprocessing, multiple signal handlers
   needed to be SIG_IGN'd, and further, because libtvm.so is
   intentionally frontend-agnostic, it's difficult to include signal
   handling directly in that binary (Python expects you to call
   PyErr_CheckSignals, but we don't require and don't want to require
   python-dev to compile libtvm.so, and this is the only such case
   where libtvm.so is expected to block the main thread for a long
   period of time).
 * Here we implement a separate microTVM debug shell python script
   using the non-blocking server implementation.

* Add serial transport, parameterize test_zephyr to work on real hardware

* add pytest test fixture, missed from previous change.

 * this test fixture helps to parameterize the test case

* address leandron@ comment from #6703
2020-10-31 09:24:01 -04:00

153 lines
5.3 KiB
Python

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# pylint: disable=redefined-outer-name, invalid-name
"""Start an RPC server intended for use as a microTVM debugger.
microTVM aims to be runtime-agnostic, and to that end, frameworks often define command-line tools
used to launch a debug flow. These tools often manage the process of connecting to an attached
device using a hardware debugger, exposing a GDB server, and launching GDB connected to that
server with a source file attached. It's also true that this debugger can typically not be executed
concurrently with any flash tool, so this integration point is provided to allow TVM to launch and
terminate any debuggers integrated with the larger microTVM compilation/autotuning flow.
To use this tool, first launch this script in a separate terminal window. Then, provide the hostport
to your compiler's Flasher instance.
"""
import argparse
import logging
import socket
import struct
import tvm.micro.debugger as _ # NOTE: imported to expose global PackedFuncs over RPC.
from .._ffi.base import py_str
from ..rpc import base
from ..rpc import _ffi_api
_LOG = logging.getLogger(__name__)
def parse_args():
"""Parse command line arguments to this script."""
parser = argparse.ArgumentParser(description="microTVM debug-tool runner")
parser.add_argument("--host", default="0.0.0.0", help="hostname to listen on")
parser.add_argument("--port", type=int, default=9090, help="hostname to listen on")
parser.add_argument(
"--impl",
help=(
"If given, name of a module underneath tvm.micro.contrib "
"which contains the Debugger implementation to use. For example, to enable a "
"debugger named BarDebugger in python/tvm/micro/contrib/foo.py, specify either "
"'tvm.micro.contrib.foo' or 'foo' here. To enable a debugger named BazDebugger in "
"a third-party module ext_package.debugger, specify 'ext_package.debugger' here. "
"NOTE: the module cannot be in a sub-package of tvm.micro.contrib."
),
)
return parser.parse_args()
class ConnectionClosedError(Exception):
"""Raised when the connection is closed."""
def handle_conn(conn, rpc_key):
"""Handle a single connection that has just been accept'd()."""
def send(data):
conn.sendall(data)
return len(data)
magic = struct.unpack("<i", base.recvall(conn, 4))[0]
if magic != base.RPC_MAGIC:
conn.close()
return
keylen = struct.unpack("<i", base.recvall(conn, 4))[0]
key = py_str(base.recvall(conn, keylen))
arr = key.split()
expect_header = "client:"
server_key = "server:" + rpc_key
if arr[0] != expect_header:
conn.sendall(struct.pack("<i", base.RPC_CODE_MISMATCH))
_LOG.warning("mismatch key from %s", addr)
return
conn.sendall(struct.pack("<i", base.RPC_CODE_SUCCESS))
conn.sendall(struct.pack("<i", len(server_key)))
conn.sendall(server_key.encode("utf-8"))
server = _ffi_api.CreateEventDrivenServer(send, "microtvm-rpc-debugger", key)
def _readall(n):
buf = bytearray()
while len(buf) < n:
x = conn.recv(n - len(buf))
if not x:
raise ConnectionClosedError()
buf = buf + x
return buf
while True:
packet_length_bytes = _readall(8)
packet_length = struct.unpack("<q", packet_length_bytes)[0]
if not packet_length:
break
status = server(packet_length_bytes, 3)
if status == 0:
break
packet_body = _readall(packet_length)
status = server(packet_body, 3)
def main():
"""Main entry point for microTVM debug shell."""
args = parse_args()
logging.basicConfig(level=logging.INFO)
if args.impl:
package = None
if "." not in args.impl:
package = f"tvm.micro.contrib.{args.impl}"
importlib.import_module(args.impl, package)
sock = socket.socket(base.get_addr_family([args.host, args.port]), socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((args.host, args.port))
sock.listen(1)
bind_addr, bind_port = sock.getsockname()
_LOG.info("listening for connections on %s:%d", bind_addr, bind_port)
while True:
conn, peer = sock.accept()
_LOG.info("accepted connection from %s", peer)
try:
handle_conn(conn, "")
except ConnectionClosedError:
pass
finally:
conn.close()
_LOG.info("closed connection from %s", peer)
if __name__ == "__main__":
main()