depthcharge

The “top-level” of the Depthcharge’s target interaction API is implemented by the Depthcharge class. This encapsulates all underlying state and exposes wrappers to functionality implemented by the various submodules. An instance of Depthcharge effectively represents a “context handle”. One will perform most, if not all, actions on a target using this handle and should prefer its use over direct instantiation and interaction with the lower-level classes. (There will be exceptions; this is just best practice guidance offered to encourage portability and compatibility over any API updates.)

In the simplest case, creating a handle consists of two steps:

  1. Attaching to the target console by creating a Console instance.

  2. Creating a Depthcharge context, passing the Console instance as an argument.

The creation of the Depthcharge context results in an initial inspection of the target device so that Depthcharge can determine what functionality is available. If any inspection operations require deployment of executable payloads (and any necessary memory write operations are detected) these payload will be deployed to memory at this time. Otherwise, payloads will be deployed on an as-needed basis when Depthcharge methods are invoked.

A boilerplate example is shown below:

#!/usr/bin/env python3
import traceback
from depthcharge import Console, Depthcharge, log

ctx = None

try:
    console = Console('/dev/ttyUSB0', baudrate=115200)
    ctx = Depthcharge(console, arch='arm')

    # Comment out the above ctx creation and uncomment the following one in
    # order to possibly make more operations available to Depthcharge by allowing
    # it to deploy executable payloads to RAM and reboot/crash the platform.
    #ctx = Depthcharge(console, allow_deploy=True, allow_reboot=True)

    # Perform actions here via API calls on ctx handle

except Exception as error:
    log.error(str(error))

    # Shown if DEPTHCHARGE_LOG_LEVEL=debug in environment
    log.debug(traceback.format_exc())

finally:
    # Save gathered information to a device configuration file
    if ctx:
        ctx.save('my_device.cfg')

Observe that the Depthcharge.save() method is used to save information collected in the context object to a “device configuration file”. This is a JSON-formatted file that can be used to later create a Depthcharge object using the static Depthcharge.load() method. This results in a much quicker object creation, given that device inspection is not needed to initialize the context state. Below is an example usage of Depthcharge.load().

console = Console('/dev/ttyUSB0', baudrate=115200)
ctx = Depthcharge.load('my_device.cfg', console)

With a context object in hand, one can being interacting with a target device using the API methods documented here. Refer to both the scripts present in Depthcharge’s python/examples directory, as well as the implementation of its various utility scripts for some additional examples.

Context creation involving the use of a Companion device or a console monitor (via depthcharge.monitor) is discussed in their respective modules and classes.

As a final note, the Depthcharge API makes a fairly heavy usage of keyword arguments (**kwargs). These are propagated downward these from top-level calls to lower level implementations. While this isn’t a great design practice for most software projects, this decision was made to better facilitate the use of Depthcharge as “a tool for quickly building your own tools.” The intent is to allow API users to pass parameters (whether programmatically or as obtained from command-line arguments) to underlying operations in order to tweak behavior without much effort.

This is exemplified by the depthcharge-stratagem script providing the -X, --extra command-line parameter, which allows the user to tweak the values of the revlut_maxlen parameter of the ReverseCRC32Hunter constructor and the max_iterations parameter of ReverseCRC32Hunter.build_stratagem(). Thus, familiarity with other parts of the Depthcharge API documentation is still very helpful, even if you’re only using provided scripts.

Console

class depthcharge.Console(device='/dev/ttyUSB0:115200', prompt=None, monitor=None, **kwargs)

This class encapsulates a serial console interface and provides higher level functionality for interacting with a U-Boot console.

The device argument are required to configure the serial console connection. Default, but not necessarily correct, values will be used if these are not provided. Note that the baudrate can either be specified as part of the device string or as an additional baudrate=<value> keyword argument.

The prompt parameter specifies the U-Boot prompt string that should be used to detect when the interactive console is ready to accept input. If left as None, Depthcharge will attempt to later determine this using the discover_prompt() method.

If you wish to view or capture data sent and received using this console, provide a Monitor instance as the monitor parameter.

By default, the underlying serial console is initialized with a 150 ms timeout. Lowering this will speed up some operations (e.g. dumping memory via console operations), but setting it too low might cause Depthcharge to time out before a device has had a chance to finish responding with output from an operation.

The timeout be changed by providing a float value (in seconds) as a timeout keyword argument or by setting a DEPTHCHARGE_CONSOLE_TIMEOUT environment variable. The latter takes precedence.

On some systems, you may find that sending too much data at a U-Boot console will cause the target device’s UART FIFO to fill, dropping characters. You can find an example of this here: <https://twitter.com/sz_jynik/status/1414989128245067780>

If you wish to introduce intra-character delay to console input, provide an intrachar keyword argument or DEPTHCHARGE_CONSOLE_INTRACHAR environment variable. (The latter takes presence.) This floating point value, in seconds, is the minimum amount of time that shall be inserted between successive bytes. Realistically, the value will be larger because this mode of operation will send each byte with a single write() + flush(), incurring non-negligible overhead. You may set a value of 0 to incur only this implicit overhead, with no additional sleep()-based delay.

property device

Device or interface used to communicate with console on target device.

property baudrate

Serial console’s baud rate configuration.

send_command(cmd: str, read_response=True) str

Send the provided command (cmd) to the attached U-Boot console.

If read_response is True, the response is returned. Otherwise, None is returned and no attempt to read the response data is made.

If one does not plan to use the response, keep read_response set to True and simply ignore the return value; this will ensure response data is removed from underlying buffers.

interrupt(interrupt_str='\x03', timeout=30.0)

Attempt to interrupt U-Boot and retrieve a console prompt.

By default, the character associated traditionally with Ctrl-C is sent to interrupt U-Boot.

If trying to do this at boot-time (within the autoboot grace period), note that a specific “Stop String” may be required.

Refer to U-Boot’s doc/README.autoboot regarding the CONFIG_AUTOBOOT_KEYED and CONFIG_AUTOBOOT_STOP_STR configuration options for more information.

discover_prompt(interrupt_str='\x03', timeout=30.0, count=10)

Attempt to deduce the U-Boot prompt string by repeatedly attempting to interrupt its execution by sending interrupt_str until count consecutive prompt strings are observed.

readline(update_monitor=True) str

Read and return one line from the serial console.

If update_monitor is True, this data is recorded by any attached Monitor.

read(readlen=64, update_monitor=True) str

Read the specified number of characters (readlen) from the serial console.

If update_monitor is True, this data is recorded by any attached Monitor.

read_raw(readlen=64, update_monitor=True) bytes

Read and return readlen bytes of raw data from the serial console.

If update_monitor is True, this data is recorded by any attached Monitor.

write(data: str, update_monitor=True)

Write the provided string (data) to the serial console.

If update_monitor is True, this data is recorded by any attached Monitor.

write_raw(data: bytes, update_monitor=True)

Write the provided raw data bytes to the serial console.

If update_monitor is True, this data is recorded by any attached Monitor.

close(close_monitor=True)

Close the serial console connection.

After this method is called, no further operations on this object should be perform, with the exception of reopen().

If close_monitor is True and a monitor is attached, it will be closed as well.

reopen()

Re-open a closed console connection with the same settings it was originally created with. After this function returns successfully, the object may be used again.

static strip_echoed_input(input_str: str, output: str) str

Remove echoed input (input_str) from data read from a serial console (output) and return the stripped string.

Depthcharge (Context)

class depthcharge.Depthcharge(console, companion=None, **kwargs)

This class represents a context handle for the top-level target interaction API.

The console argument is required by the Depthcharge constructor. It will be used by the context handle to interact with the target in future operations. It may either be an initialized Console instance or a string that can be passed directly to the Console constructor to create a new instance.

If a companion device is necessary to perform desired actions, an initialized Companion device handle can be passed via companion.

Keyword Arguments:

Retrieve detailed help text

When inspecting a device, the Depthcharge constructor will not read detailed help text. This can be achieved by passing a detailed_help=True keyword argument to the constructor or by making a later call to commands() with detailed=True.

Allow payload deployment & execution

If allow_deploy=True, Depthcharge will attempt to deploy and execute payloads in memory, when neccessary. Specifying allow_deploy=True will always force payloads to be deployed; this overrides (and therefore ignores) whatever is set for the following skip_deploy parameter.

Skip payload deployment, but allow execution

When performing the same operations on a device repeatedly, the deployment of executable payloads may be redundant and waste time. This can be disabled by providing a skip_deploy=True keyword argument.

Important, the target will crash if an attempt is made to execute a payload that has not been previously deployed.

Specifying skip_deploy=True` implies that one wishes to execute payloads. If this is not true – you want no payload deployment and no execution – then *allow_deploy=False is what you’re looking for.

Payload location

By default, payloads are staged 32 MiB beyond the target’s $loadaddr location. To override this location, either alternative absolute address can be provided in a payload_base keyword argument. Alternatively, the offset from the payload base address can be provided via payload_offset.

Crash/Reboot behavior

Some operations, such as DataAbortRegisterReader subclasses, need to crash platform (assuming it will automatically reboot) in order to perform their duty. If crashing or rebooting the platform is undesirable, specify allow_reboot=False to exclude these from the “available operations” lists. Alternatively, a user-provided callback function can be specified via the post_reboot_cb keyword argument. This function will be invoked after the reboot occurs. The callback function is responsible for calling interrupt() in order to catch the U-Boot prompt. (This is intended to allow any other prerequisites, such as sending an autoboot “stop string”, to occur within the callback.) The callback function takes a single argument, which can be provided via the post_reboot_cb_data keyword argument. If you would like to pass the Depthcharge context being created by this constructor, specify the string 'self'.

create_progress_indicator(owner, total_operations: int, desc: str, **kwargs)

Create a progress indicator and register it with the Depthcharge context.

By using this method, as opposed to invoking Progress.create() manually, it ensures that only this progress indicator will be displayed. Any progress indicators associated with underlying operations will be hidden.

The owner argument must be an object instance or a unique identifier. This is used to track progress indicator registration. As an API user, you may simply use a string such as 'user' or 'script' – these will not collide with values used by internal code.

The total_operations count indicates how many operations are expected to be tracked by this indicator. A 100% completion is displayed when the sum of values provided to Progress.update() reaches this value.

The desc string should briefly describe the ongoing operation, in just a few words. This will be shown to the user on the displayed progress indicator.

The return value is a Progress handle, initialized according to the provided keyword arguments.

from time import sleep

progress = ctx.create_progress_indicator('script', 64, 'Example Operation')
for i in range(0, 64):
    progress.update()  # Passes default count value of 1 when not specified
    sleep(0.25)

ctx.close_progress_indicator(progress)
close_progress_indicator(progress)

Close and relinquish control over a progress indicator. This will allow other progress indicators to be displayed again.

All create_progress_indicator() invocations must have a corresponding call to this method.

static load(filename: str, console, **kwargs)

Create and return a Depthcharge object from the JSON data included in the specified file, previously generated by save().

classmethod from_json(json_str: str, console, **kwargs)

Create and return a Context object from the provided JSON data, previously created by to_json().

to_json(timestamp=True, comment=None, **kwargs) str

Serialize a depthcharge Depthcharge object so that it can be later recreated via the from_json() method.

save(filename, timestamp=True, comment=None)

Serialize the current configuration of the current Depthcharge context to a JSON object and write it to the provided filename.

property prompt: str

Target’s (expected) U-Boot console prompt string, as provided by the underlying depthcharge.Console object.

send_command(*args, **kwargs) str

Send a command to the target console.

If a check=True keyword argument is provided, this method will check the response data for strings indicative of a failure and raise an OperationFailed exception on error. Otherwise, it is the caller’s responsibility to inspect the returned response data for content indicative of success or failure.

If an expected=<str> keyword argument is provided, this method will raise a ValueError if the response does not match the expected value. If a string is provided, a string comparison is performed in a case-insensitive manner, with leading and trailing whitespace stripped.

If expected=<re.Pattern> is provided with a compiled regular expression, then the re.match() will be applied to the response. A ValueError will be raised if the response does not match the provided pattern.

Example: Retrieving a Device Tree from NAND

# Determine command success based upon re.match() of expected response
success_pattern = re.compile(r'NAND read:.*: OK.*', re.MULTILINE|re.DOTALL)
ctx.send_command('nand read $dtb_addr $dtb_offset $dtb_size', expected=success_pattern)

# Check for success using Depthcharge's limited builtin failure response checks
ctx.send_command('fdt addr $dtb_addr', check=True)

# Alternatively, we could confirm success by verifying that the response is
# an empty string.
# ctx.send_command('fdt addr $dtb_addr', expected='')

# Just take the response data without checks
dts = ctx.send_command('fdt print')
print(dts)
interrupt(interrupt_str='\x03', timeout=30.0)

This is a convenience wrapper around depthcharge.Console.interrupt().

commands(cached=True, detailed=False) dict

Return a dictionary containing information about commands supported by the target’s U-Boot console environment.

If cached=True, any previously recorded information is returned. Otherwise, it will be actively obtained from the device.

The command names are stored as dictionary keys. Each command entry is its own dictionary containing a summary value, which is a brief description of the command. If detailed=True, additional help text is stored in a details value.

environment(cached=True) dict

Return the target’s environment variables as a dictionary.

By default, any previously cached results are returned. Specify cached=False in order to force their retrieval from the target device.

Note that all values in the returned dictionary are strings. The caller is responsible for performing any necessary type conversions.

Upon failure, an error will be logged and an empty dictionary will be returned.

env_var(name: str, expand=True, cached=True, convert_int=True, **kwargs)

Retrieve the value of an environment variable, specified by name.

If expand=True, the environment variable definition will be “expanded” such that any other variables in its definitions will be fully resolved.

If cached=True (default), the result is returned from cached environment data. Otherwise, the device will be queried for this information.

If convert_int=True, this method will attempt to return environment variables containing only an integer as an int type. Otherwise, the value will be returned as a str.

set_env_var(name: str, value, invalidate_cache=True)

Set the environment variable (whose name is specifeid by name) to the provided value.

If value is an integer, it will be convered to a string, formatted as a hexadecimal value prefixed with 0x.

Otherwise, the value is assumed to be a string and is set as-is.

The caller is responsible for escaping the variable contents appropriately.

The invalidate_cache argument forces readback of the entire environment into Depthcharge’s local cache when set to True. When setting a handful of variables in succession, you may set it to False for all but the final set_env_var() call.

version(cached=True, allow_reset=True) list

Return the target’s version information as a list.

By default, this is cached information. Set cached=False to force it to be actively retrieved from a device.

If the version command is not available and allow_reset=True (the default), the target will be reset in an attempt to retrieve this information.

property register_readers

An iterable collection of all available RegisterReader implementations.

default_register_reader(**kwargs)

Return the RegisterReader that will be used if no implementation is specified via the impl= keyword argument when read_register() is invoked.

Raises OperationNotSupported if no register readers are available.

read_register(register, **kwargs) int

Read a register and return its value.

property memory_readers

This property provides an iterable collection of all available MemoryReader implementations.

default_memory_reader(**kwargs)

Return the MemoryReader that will be used if no implementation is specified via the impl= keyword argument when read_memory() is invoked.

This most suitable default may change with respect to the amount of data being read. If the amount of data to read is known, pass this integer value via a data_len keyword argument.

Raises OperationNotSupported if no memory readers are available.

read_memory(address: int, size: int, **kwargs) bytes

Read size bytes at the specified memory address and return the data.

An optional impl keyword argument may specified to override the default memory read operation and use a specific MemoryReader implementation (by name) to perform this operation. Either a single name or a list of names, can be provided.

# Read 16 KiB from 0x8780_0000 using any available memory read implementation
data = ctx.read_memory(0x8780_0000, 16384)

# Read 16 KiB 0x8780_0000 using the GoMemoryReader implementation
# Observe that this is case-insensitive and the suffix can be omitted.
data = ctx.read_memory(0x8780_0000, 16384, impl='GoMemoryReader')
data = ctx.read_memory(0x8780_0000, 16384, impl='go')

# Use one of GoMemoryReader, MdMemoryReader, or I2CMemoryReader.
data = ctx.read_memory(0x8780_0000, 16384, impl=['go', 'md', 'i2c'])

If impl is not specified, or if a specified implementation is not available, one will be selected using default_memory_reader() and the requested read size.

read_memory_to_file(address: int, size: int, filename: str, **kwargs)

Read size bytes at the specified memory address and write the data to a file named filename.

Refer to read_memory() regarding the use of the optional impl keyword argument.

property memory_writers

This property provides an iterable collection of all available MemoryWriter implementations.

default_memory_writer(**kwargs)

Return the MemoryWriter that will be used if no implementation is specified via the impl= keyword argument when write_memory is invoked.

This most suitable default may change with respect to the amount of data being written. If the amount of data to write is known, pass this integer value via a data_len keyword argument.

Raises OperationNotSupported if no memory writers are available.

write_memory(address: int, data: bytes, **kwargs)

Write the provided data to the specified memory address.

Similar to read_memory(), this method supports an optional impl keyword argument.

If a memory write operation requires the use of a depthcharge.Stratagem, provide this via a stratagem= keyword argument and set data=None.

write_memory_from_file(address: int, filename: str, **kwargs)

Write the contents of the binary file accessed via filename to the specified memory address.

If the file contains a Stratagem (as opposed to raw data to write), set the keyword argument stratagem=True to indicate this.

Similar to read_memory(), this method supports an optional impl keyword argument.

patch_memory(patch_list, dry_run=False, **kwargs)

Patch a series of memory locations, as described in the provided patch_list.

This argument may be one of:

This method will first iterate over the list of memory locations to patch and verify that each contains the expected value (if the corresponding MemoryPatch contains an expected value). If a target memory location does not match an expected value, a :py::ValueError is raised. This behavior is intended to help avoid applying patches in cases where a target is executing a new or unexpected firmware revision. If this is not desired, checks can be disabled by providing a skip_checks=True keyword argument.

Next, each patch’s data will be written to their corresponding memory locations. If dry_run=True, this write step is skipped.

Similar to the read_memory() and write_memory() methods, an impl keyword argument can be used to specify, by name, which specific memory access implementations should be used (if available). Otherwise, Depthcharge will attempt to use the most reasonable defaults.

Below is an example invocation that specifies that CRC32MemoryReader and NmMemoryWriter.

ctx.patch_memory(patch_list, impl=['CRC32MemoryReader', 'NmMemoryWriter'])

Technically, the “MemoryReader” and “MemoryWriter” suffixes can be omitted in the above example. However, it is recommended that they be used when specifying both readers and writers, as suffixless names may be ambiguous (as in the case of NmMemoryReader and NmMemoryWriter).

register_payload(name: str, payload: bytes, required_by=None)

Register an executable payload that can later be deployed and executed. The payload’s name must be unique.

If you wish to manually deploy and execute the payload, refer to deploy_payload() and execute_payload().

Otherwise, the payload can be automatically deployed and executed as-needed by specifying one or more Operation classes that require this payload to function. These can be specified by class name (str) or an instance of the class. Either a single item or a list can be provided.

deploy_payload(name: str, **kwargs)

Write the builtin payload identified by name to its corresponding address (determined during execution of the Depthcharge constructor).

If the payload is already deployed, this method performs no action.

The keyword argument force=True can be used to force (re)deployment.

execute_payload(name: str, *args, **kwargs)

Execute the builtin payload identified by name.

This method will invoke deploy_payload() as-needed to ensure the payload is deployed before attempting to execute it.

Positional and keyword arguments are passed to the underlying Executor implementation, which will determine which of these (if any) are passed to the payload (and how). This of course, also depends upon the payload code itself.

Returns a tuple: (return code: int, response data: bytes)

The keyword argument read_response=False can be passed to suppress reading of response data, should the caller want to do so manually through raw console accesses. In this case, this method returns None.

execute_at(address: int, *args, **kwargs)

Instruct the target to execute instructions at the specified address.

Any additional positional and keyword arguments are passed to the underlying Executor implementation.

Important: This method does not perform any pre-requisite validation before attempting to begin execution. Favor the use of execute_payload().

uboot_global_data(cached=True, **kwargs) dict

Inspect U-Boot’s global data structure and return a dictionary describing elements relevant to Depthcharge’s usage of it.

The Depthcharge constructor includes this inspection if sufficient information and operations are available. This method returns the cached results are returned if cached=True. Otherwise, executing this method with cached=False will force Depthcharge to explore the target to obtain this information.

Below is a summary of how this method operates and how optional keyword arguments can be used to modify its behavior.

First, information is gathered from the console bdinfo command if it is available.

Next, an attempt is made to retrieve the global data structure (gd_t) address and read memory at this location. These memory contents are used to infer the locations of functions exported in its jump table.

An OperationNotSupported exception is raised if requisite underlying operations are not available to retrieve any (partial) information.

If platform- or version-specific differences resulting in failures, the following two keyword arguments can be used to skip portions of this implementation, as a workaround:

  • skip_bdinfo=True - Skip bdinfo invocation and parsing of output

  • skip_gd_jt=True - Skip access of global data pointer and jump table inspection

Upon success, returned information will be also recorded within the Depthcharge object and made available to successive calls. This information is included in the device configuration file exported via save().

Refer to U-Boot’s include/asm-generic/global_data.h header and its README.standalone document for more information about the data structures and tables discussed above.

Operation

class depthcharge.Operation(ctx, **_kwargs)

This class provides a common base for different types of target device interactions.

Generally, the direct subclasses of Operation will themselves be abstract base classes providing some common functionality for a particular type of operation. Examples of this include MemoryReader and MemoryWriter.

classmethod register(*args)

Register one or more available Operation implementations, by class.

Once registered, Operations are accessible via implementations() and get_implementation(). These methods allow for more programmatic discovery and are intended to support command-line scripts that allow a user to choose an implementation. (See the ``–op`` argument used in the Depthcharge scripts.)

This registration mechanism is used by Depthcharge modules themselves to present their implementation to core code. Most API users need not to concern themselves with this method unless they are writing their own depthcharge.Operation implementations that they wish to load at runtime.

By convention, the name of the class you register should contain a suffix associated with its parent class. For example, if your foo class implements MemoryWriter, it should be named FooMemoryWriter. As with the built-in operations, it will still be accessible from the command-line as foo and FooMemoryWriter.

classmethod implementations()

Return an iterable collection registered implementations, for a given abstract Operation class (e.g. MemoryWriter).

Calling this on Operation results in all available operations being returned.

classmethod get_implementation(name: str)

Retrieve an Operation implementation by case-insensitive name.

property name: str

Operation name (string)

classmethod class_name() str

Returns the operation name (string). Unlike the name() property, which requires a class instance, this can be invoked on an Operation subclass itself.

property required: dict

Return a dictionary that describes the requirements and dependencies of a given Operation subclass.

classmethod get_stratagem_spec() dict

Return this class’s Stratagem specification, which is a dictionary defining the keys and their respective value types that are required in Stratagem entries.

None is returned if the class does not use Stratagem objects.

This baseline specification generally consists of the following the keys, all of which map to integers.

  • src_addr - Absolute address of source data.

  • src_size - Size of data at source location, in bytes.

  • dst_off - Offset into destination location.

Other keys may be present, however. For example, if an output size is not implicit, a dst_size parameter may be included.

classmethod stratagem_hunter(*args, **kwargs)

Returns a Hunter instance that can be used to create a Stratagem for a given Operation. The *args, and **kwargs parameters are passed directly to the corresponding Hunter constructor.

None is returned if the Operation does not require the use of a Hunter-generated Stratagem.

classmethod check_requirements(ctx)

Inspect the provided Depthcharge context object (ctx) to determine if the pre-requisites required to use a given Operation are satisfied.

If they are not, a depthcharge.OperationNotSupported exception is raised.

classmethod rank(**kwargs) int

Rank an Operation, considering any criteria specified in kwargs.

This ranking is represented by an integer value within the range of 0 to 100. A higher value implies that a particular Operation is better suited for performing a given task than alternative options with lower rankings.

The ranking system is fairly subjective and subject to change, but roughly is defined as follows:

Range (Inclusive)

Description

75 - 100

A great choice. Performs operation quickly and cleanly, with no major side effects.

50 - 74

A pretty good choice. It gets the job done, but might not be the fastest.

25 - 49

An okay choice. It might be quite slow and dirty the runtime state of the device.

0 - 24

Suitable as a last-ditch effort. May be very slow, have some undesirable side-effects, or require a Companion device that’s not needed by other Operations that could achieve the same result.

Currently, one keyword argument is supported.

The data_len keyword argument can be used to denote how many bytes of data are being handled. Some operation implementations may be better options for larger payloads do to some setup overhead, while not being a great choice for operating on just a couple bytes. This parameter is used to inform the ranking in this situation.

The reason this class method supports arbitrary keyword arguments in order to accommodate the introduction of different Operations types in the furture.

class depthcharge.OperationSet(suffix=None)

This class represents a set of Operation objects, allowing different types of operation instances to be grouped into respective collections.

add(op)

Add an Operation instance to the set.

find(op, try_suffix=True)

Search the set for the specified operation. The requested Operation instance will be returned if present in the set. Otherwise, a ValueError is raised.

The op argument can be specified as a class name of the desired Operation (i.e. a string). The search is performed in a case-insensitive manner. When try_suffix=True, the provided name does not need to contain the suffix associated with the particular Operation type. For example, the “MemoryWriter” can be omitted from the operation name when searching a set containing MemoryWriter. For example, the following are equivalent:

op = ctx.memory_writers.find('crc32')
op = ctx.memory_writers.find('CRC32')
op = ctx.memory_writers.find('CRC32MemoryWriter')

The set can also be searched using a class as the op parameter:

from depthcharge.memory import CRC32MemoryWriter

op = ctx.memory_writers.find(CRC32MemoryWriter)

Finally, the op argument can be provided as a list. The search of the set will be performed over each entry in the list until a matching Operation is found and returned.

from depthcharge.memory import CRC32MemoryWriter

op = ctx.memory_writers.find(['crc32', 'mw', 'nm'])
default(**kwargs)

Return the best available default Operation instance from the set. The “best” option is chosen based upon Operation.rank() and the following optional keyword arguments, specified here with their default values.

Any keyword arguments beside those listed below are passed to Operation.rank(). (e.g. the data_len=<n> keyword argument)

  • The exclude_reqts keyword is a list or tuple that denotes that an Operation should be excluded if it requires any of the items named in its list. For example, exclude_reqts=('stratagem', 'payloads', 'companion') implies that any options that require the use of a Stratagem, depend upon already-deployed payloads, or need to use a Companion device should be excluded. The default value is exclude_reqts=('stratage').

  • An exclude_op keyword can be used to exclude specific instances of Operation from being returned. If an Operation uses another to bootstrap itself, this can be used to exclude itself from the available options. A single object, list, or set may be provided. The default value is None.

exception depthcharge.OperationFailed

Raised when an operation did not complete successfully.

The exception message should further explain the nature and circumstances of the failure.

exception depthcharge.OperationNotSupported(cls, msg, *args)

Raised to denote that a requested operation is not supported in the current device state or configuration.

exception depthcharge.OperationAlignmentError(alignment: int, cls=None)

Raised when an operation is attempted, but would (unintentionally) violate architecture-specific alignment data or memory address alignment requirements.

When raising this exception, the alignment parameter must indicate the alignment size in bytes. If a class is passed via the cls parameter, its name will be prefixed to the exception message.

Stratagem

class depthcharge.Stratagem(op_class, capacity: int = -1, **kwargs)

Some Operation implementations, such as CRC32MemoryWriter, cannot perform their objective directly (e.g. writing an arbitrary value to a selected memory address). Instead, they must perform a roundabout sequence of operation to achieve this goal.

Within the Depthcharge project, this type of indirect operation is referred to as a Stratagem.

A Stratagem encapsulates a list of dictionaries, whose keys are defined and only relevant to the corresponding Hunter and Operation implementations that generate and use them, respectively.

Operation subclasses that require a Stratagem are expected to provide a “Stratagem specification” via their : Operation.stratagem_spec() method.

API users usually do not need to instantiate a Stratagem directly, but rather receive them from Hunter.build_stratagem() implementations. Nonetheless, the constructor arguments are as follows:

  • The op_class argument shall be an Operation subclass (not an instance of that class, but the class itself) that provides a Stratagem specification in its stratagem_spec() method.

  • If the final size of the Stratagem is known in advance, it can be created with this initial capacity via the capacity keyword. Doing this allows the Stratagem entries to be set using the index ([]) notation implemented by __setitem__. Otherwise, append() must be used to add entries to the Stratagem.

entries()

Returns (a generator for) entries in the Stratagem.

The returned elements are copies; the caller is free to modify them and this will not affect the state of the Stratagem.

property operation_name: str

Name of the depthcharge.Operation intended for use with this Stratagem.

append(entry: Optional[dict] = None, **kwargs)

Append an entry to the Stratagem.

The key-value pairs may be provided in the entry dictionary or as keyword arguments to this method.

The key names and the value types will be validated according to the Stratagem’s specification (obtained from the associated Operation).

property total_operations

Total number of operations performed when executing this Stratagem.

For Stratagem whose entries contain an iterations key denoting that multiple iterations of an operation are required, this will reflect the total number of operations performed; the result will be larger than the value returned by len(), which instead denotes how many entries are in the Stratagem.

classmethod from_json(json_str: str)

Create a Stratagem object from a provided JSON string.

classmethod from_json_file(filename: str)

Create a Stratagem object from the contents of the specified JSON file.

to_json(**kwargs) str

Convert the Stratagem to a JSON string.

to_json_file(filename, **kwargs)

Conver the Stratagem to a JSON string and write it to the specified file.

exception depthcharge.StratagemRequired(name)

The StratagemRequired exception is a specific kind of TypeError that is raised when “raw data” (e.g. bytes) was passed to an Operation, but that Operation can only accept a Stratagem as input.

exception depthcharge.StratagemNotRequired

This exception is raised when an attempt to create a Stratagem is made for an Operation that does not necessitate it.

exception depthcharge.StratagemCreationFailed

It may be the case that a Stratagem cannot be created, given the inherent constraints of the input and those of the creation parameters. This Exception may be raised to signal that a result was not possible to create.

Companion

class depthcharge.Companion(device='/dev/ttyACM0', baudrate=115200, **kwargs)

A Companion object represents a handle to a device running the Depthcharge Companion Firmware. This firmware implments a proxy by which the Depthcharge host library can perform desired operations (e.g. target memory access).

A connection to the Companion device is established when this class’s constructor is invoked. The USB-Serial connection to the device is configured using the device and baudrate parameters.

Additional keyword parameters, listed below, can be used to configure the Companion device firmware.

Currently, the Companion suport is limited to I2C peripheral functionality. With some simple additions here and in the firmware, it should be capable of acting as a peripheral device on other simple buses (e.g. SPI) as well. This API will update along with official additions to the firmware.

The get_capabilities() and get_version() methods can be used to determine what functionality is supported by the companion device.

Constructor Keyword Arguments

  • i2c_bus - I2C bus index within U-Boot environment. Corresponds to i2c dev [n] console commands. Defaults to i2c_bus=0.

  • i2c_addr - I2C address that device should respond to. May be set later via set_i2c_addr(). Default: i2c_bus=0x78 (A reserved address)

  • i2c_speed - I2C bus speed, in Hz. May be set later via set_i2c_speed(). Default: i2c_speed=100000

firmware_verison(cached=True) str

Retrieve firmware version from device.

If cached=True, a previously read value will be returned. Otherwise it will be read from the Companion device.

firmware_capabilities(cached=True) dict

Query the companion firmware capabilities and return a dict indicating which features are present.

If cached=True, previously obtained capabilities will be returned. Otherwise they will be read from the Companion device.

Keys represent capabilities and the corresponding boolean value denotes whether or not that capability is present on the Companion device.

i2c_bus() int

Zero-indexed I2C bus number the companion device is associated with.

i2c_addr(cached=True) int

Retrieve the I2C device address that the Companion responds to.

If cached=True, the value stored host-side will be returned. Otherwise it will be read from the device.

set_i2c_addr(addr: int)

Set the I2C device address that the Depthcharge Companion firmware responds to. Valid range: 0x00 - 0x7f

i2c_speed(cached=True) int

Retrieve the I2C bus clock rate the device is configured for, in Hz.

If cached=True, the value stored host-side will be returned. Otherwise it will be read from the device.

set_i2c_speed(speed: int) int

Configure the device for the specified I2C bus clock rate.

Refer to reference manual of the device you’re running the Depthcharge Companion firmware on for supported bus clock speeds.

i2c_write_buffer() bytes

Retrieve the contents of the I2C data write buffer

This represents data written by a target SoC (bus controller) to our peripheral device. Within the context U-Boot shenanigans, this is data retrieved from the memory space of the target SoC.

Different firmware implementations are free to use either separate read and write buffers, as well as a single, shared buffer. Do not assume that this call will return the same data that was written by a preceding call to set_i2c_read_buffer().

set_i2c_read_buffer(data: bytes)

Set up the contents of the I2C data read buffer.

This is the data that the target SoC (bus controller) will read from our peripheral device into its memory space.

Different firmware implementations are free to use either separate read and write buffers, as well as a single, shared buffer. Do not assume that i2c_write_buffer() will return the value written by this call.

send_cmd(cmd_str: str, data: bytes, expected_resp_size: int = -1, expected_resp=None) bytes

Send a raw command to the Companion device and return its response. This can be used when adding new features and custom functionality to the Companion firmware.

If non-default values for expected_resp and expected_resp_size, an:py:exc:IOError will be raised if the device’s resonse contents or size (respectively) do not match the provided expected values.

ValueError and TypeError exceptions are raised when invalid arguments are provided.

close()

Close the connection to the Companion device. No further instance methods should be invoked following this call.

Architecture

class depthcharge.Architecture

Provides access to architecture-specific properties and conversion methods.

classmethod get(arch_name)

Retrieve an architecture definition by name.

Example:

from depthcharge import Architecture

arm = Architecture.get('arm')
print(arm.description)
for reg_name in arm.registers():
    print('  ' + reg_name)
classmethod supported()

Returns a generator for all supported architectures.

Example:

from depthcharge import Architecture

for arch in Architecture.supported():
    print(arch.name)
classmethod is_word_aligned(address)

Returns True if address is word-aligned, and False otherwise.

classmethod is_allowed_access(address, size)

Returns True if accessing address is permitted with respect to alignment requirements.

The base implementation performs an address mask check for a minimum alignment requirement. Subclasses should perform a size check, if needed.

classmethod ptr_value(data: bytes) int

Read a pointer from the provided data and return this address as an integer in the host endianness.

classmethod ptr_value_adv(data: bytes) int

Returns a tuple (value, data_slice) such that the first element is the result of ptr_value(), and data_slice is data advanced by the size of the pointer.

classmethod int_to_bytes(intval: int) bytes

Convert an integer value to bytes, according to the architecture’s endianness and word size.

classmethod to_int(data: bytes)

Returns the value in the provided data converted to a signed two’s complement integer in the host endianness.

classmethod to_int_adv(data: bytes)

Returns a tuple (value, data_slice) such that the first element is the result of to_int(), and data_slice is data advanced by the size of the target’s integer.

classmethod to_uint(data: bytes)

Returns the value in the provided data converted to an unsigned integer in the host endianness.

classmethod to_uint_adv(data: bytes)

Returns a tuple (value, data_slice) such that the first element is the result of to_uint(), and data_slice is data advanced by the size of the target’s (unsigned) integer.

classmethod hexint_to_bytes(hex_str: str, num_bytes: int) int

Convert a hexadecimal string representing an integer value (big endian) to bytes taking the architecture’s endianness into account.

classmethod word_sizes() dict

This property provides a dictionary of word sizes supported by the architecture (int), mapped the command suffix used by U-Boot’s memory commands (i.e., ‘b’, ‘w’, ‘l’, ‘q’).

The quad-word suffix is only included if the architecture supports 64-bit data, per supports_64bit_data().

classmethod multiple_of_word_size(value: int) bool

Returns True if the provided value is a multiple of the architecture’s word size, and False otherwise.

classmethod registers() dict

Dictionary containing registers names as keys and additional information (if any) in corresponding values.

Special keys (i.e. not included with every entry) include:

  • alias - A string containing an alternate name for the register (e.g. ‘sp’).

  • gd=True if the register store’s U-Boot global data structure.

  • ident - An integer identifier used internally (e.g. in payloads).

classmethod register(name: str) tuple

Look up a single register by case insensitve name or alias.

The returned value is a tuple containing its corresponding key and value in the dictionary returned by :py:meth:registers()`.

Raises ValueError for an invalid register name/alias.

Progress Indicator

class depthcharge.Progress(total_operations: int, desc: str, **kwargs)

API users working with a Depthcharge context object should instead use Depthcharge.create_progress_indicator().

This base class implementation is a no-op that can substituted in when API usage indicates that no progress should be presented. Otherwise, the ProgressBar subclass can be used to display the progress status of an ongoing operation.

Basic statistics are recorded internally, however, just to support debugging efforts.

static create(total_operations: int, desc: str, **kwargs)

Create either a ProgressBar or a Progress instance, depending upon the the log level. The following levels will result in a progress bar, while other levels will not.

  • depthcharge.log.NOTE

  • depthcharge.log.INFO

  • depthcharge.log.WARNING

Note that the DEBUG level is not included due to emitted information being likely to interfere with drawing a progress bar.

The total_operations count indicates how many operations are expected to be tracked by this indicator. A 100% completion is displayed when the sum of values provided to Progress.update() reaches this value.

The desc string should briefly describe the ongoing operation, in just a few words. This will be shown to the user on the displayed progress indicator.

update(count=1)

Record an updated count of operations that have completed since the the previous invocation of update().

i.e. This is relative, not the total since the creation.

reset(total_operations=None)

Reset the progress indicator in order to start again.

If total_operations is not provided, it will be reset with the original value passed to the constructor.

close()

Close and cleanup progress status.

property owner

Value of owner= keyword argument provided to Progress constructor, or None. This is used to track ownership of the top-level Progress handle in use by a depthcharge.Context object.

class depthcharge.ProgressBar(total_operations, desc=None, unit='op', **kwargs)

This is currently just a simple wrapper around tqdm, intended to maintain a consistent UX across usages throughout the Depthcharge codebase.

In the future, this may migrate to use a different library for progress bar graphics.

Unless you have a very good reason, do not instantiate this class directly. Either use Depthcharge.create_progress_indicator() or Progress.create().