micropython-esp32-ulp contains a disassembler for disassembling code for the ESP32 ULP (Ultra Low-Power) Co-Processor.
The main purpose of this tool is to inspect what instructions our assembler created, what value each field is set to, and to compare this with the output created by the assembler from Espressif (part of their binutils-gdb fork), which we use as our reference implementation.
To disassemble a ULP binary, simply run:
micropython -m tools.disassemble path/to/binary.ulp
You can also specify additional options to disassemble.py
as follows:
Option | Description |
---|---|
-c or --mcpu |
Choose ULP variant: either esp32 or esp32s2 |
-h |
Show help text |
-m <bytes sequence> |
Disassemble a provided sequence of hex bytes
(in this case any filename specified is ignored)
|
-v |
Verbose mode (shows ULP header and fields of each instruction) |
The simplest and default mode of the disassembler is to disassemble the specified file.
Note that the ULP header is validates and files with unknown magic bytes will be
rejected. The correct 4 magic bytes at the start of a ULP binary are ulp\x00
.
Example disassembling an ESP32 ULP binary:
$ micropython -m tools.disassemble path/to/binary.ulp
.text
0000 040000d0 LD r0, r1, 0
0004 0e0000d0 LD r2, r3, 0
0008 04000068 ST r0, r1, 0
000c 0b000068 ST r3, r2, 0
.data
0010 00000000 <empty>
Example disassembling an ESP32-S2 ULP binary:
$ micropython -m tools.disassemble -c esp32s2 path/to/binary.ulp
.text
0000 040000d0 LD r0, r1, 0
0004 0e0000d0 LD r2, r3, 0
0008 84010068 ST r0, r1, 0
000c 8b010068 ST r3, r2, 0
.data
0010 00000000 <empty>
The -m
option allows disassembling a sequences hex letters representing
ULP instructions.
This option expects the actual instructions directly, without any ULP header.
The sequence must contain a number of hex letters exactly divisible by 8, i.e. 8, 16, 24, etc, because each 32-bit word is made up of 8 hex letters. Spaces can be included in the sequence and they are ignored.
The typical use case for this feature is to copy/paste some instructions from a hexdump (e.g. xxd output) for analysis.
Example:
# hexdump binary.ulp
$ xxd path/to/binary.ulp
00000000: 756c 7000 0c00 2400 0400 0000 9300 8074 ulp...$........t
00000010: 2a80 0488 2004 8074 1c00 0084 0000 0040 *... ..t.......@
(...)
# analyse the last 2 instructions
$ micropython -m tools.disassemble -m "1c00 0084 0000 0040"
0000 1c000084 JUMPS 0, 28, LT
0004 00000040 NOP
In verbose mode the following extra outputs are enabled:
- ULP header (except when using
-m
) - The fields of each instruction and their values
For example:
header ULP magic : b'ulp\x00' (0x00706c75) .text offset : 12 (0x0c) .text size : 36 (0x24) .data offset : 48 (0x30) .data size : 4 (0x04) .bss size : 0 (0x00) ---------------------------------------- .text 0000 93008072 MOVE r3, 9 dreg = 3 imm = 9 opcode = 7 sel = 4 (MOV) sreg = 0 sub_opcode = 1 unused = 0 (...detail truncated...) 0020 000000b0 HALT opcode = 11 (0x0b) unused = 0 ---------------------------------------- .data 0000 00000000 <empty>
The disassembler also works when used on an ESP32 device.
To use the disassembler on a real device:
ensure
micropython-esp32-ulp
is installed on the device (see docs/index.rst).upload
tools/disassemble.py
tools/decode.py
andtools/decode_s2.py
to the device (any directory will do, as long as those 3 files are in the same directory)the following example code assumes you placed the 3 files into the device's "root" directory
run the following (note, we must specify which the cpu the binary is for):
from disassemble import disassemble_file # then either: disassemble_file('path/to/file.ulp', cpu='esp32s2') # normal mode # or: disassemble_file('path/to/file.ulp', cpu='esp32s2', verbose=True) # verbose mode