The Oracle, the Priest, and the Scribe:
Creating a low-level analyzer
for the BDM debug protocol

/nl_img1

TL;DR: If you’d like to see what your BDM debugger is actually doing under the hood, check out our repo →

The journey of hardware reverse engineering often begins with the eyes. You find a device, rip it out of its shell, and put it under a bright light to inspect the handiwork of another engineer. You look over at the component selection and ask, “I wonder what led to that decision?”

You poke and prod, looking at the interfaces laid out, building a mental map of the traces, as if trying to make sure you can find your way back out of the maze.

On this particular day, you spot a nice array of pins, already populated with headers. Ten pins, and a small silkscreen label, three letters, “BDM”.

Well that’s not JTAG, that’s not UART, definitely not SWD. No, this is a little different. As anyone would do, you hit the search engine. The top result is your old friend,

(ever notice the A at the end is stylized?)

Your old friend gives you the low-down, what is “BDM”? Why it’s:

Background debug mode (BDM) interface is an electronic interface that allows debugging of embedded systems. Specifically, it provides in-circuit debugging functionality in microcontrollers. It requires a single wire and specialized electronics in the system being debugged. It appears in many Freescale Semiconductor products.”

You think to yourself, “That almost sounds right.” You can see your processor is an MPC860, an old PowerQUICC III device, which was certainly made by Freescale 2–3 buyouts ago. You look again. “It requires a single wire.” You have 10 pins here, certainly those aren’t just for show.

Let’s go back to the and figure out what we’re actually looking at.

The datasheet (aka the goods)

Sometimes in life, the simplest pleasures can be found in a 1,320-page datasheet, so let’s step back and go to the source. Datasheets are a lovely place where branding and logos are updated, but old technical writing still tells a story like rings on a tree stump. In this case, we have a datasheet with old links to http://www.freescale.com and NXP logos on the headers. A grand ol’ time to be had by all.

So you open your datasheet, scroll down to Chapter 44: System Development and Debugging, continuing to scroll down to Chapter 44.3: Development System Interface, and then you keep scrolling until you see something familiar:

(hey, that’s 10 pins!)

Ah, you sigh in relief, there’s simply TWO BDM interfaces used historically by Freescale. That makes everything extremely clear. (There’s actually even more but that’s for another time.)


Going a little deeper

Simply using the debug interface with an off-the-shelf debugger was not our end goal. It’s actually quite easy to go out and spend a few thousand on an off the shelf debugging device, and you’re off to the races doing some of your favorite activities, such as breaking, continuing, breaking at your favorite while-loop, reading registers, etc. We wanted to understand this debugging protocol in order to make this device do our bidding without the expensive off-the-shelf debugger in play.

The documentation was helpful. It provided information on the building blocks of this interface. BDM doesn’t use normal op-codes understood by the processor to manipulate processor state. The actual protocol is a mix of PowerPC instructions and debug-specific instructions that are interwoven to directly take control and execute instructions on the processor.

Block Diagram of how the BDM interface integrates with the main core.

Physically BDM:

At first glance, some of the signal names may remind the reader of other debugging protocols:

…will evoke the memory of JTAG’s:

These protocols quickly diverge. In addition to the serial data lines and the clock, BDM also makes use of two reset signals: HRESET for hardware reset and SRESET for software reset. While the device being probed is powered and running, these signals assert high, and if brought low, can trigger the appropriate reset of the processor. 

Finally, we have two more signals, VLFS0 and VLFS0, the freeze pins used to indicate to the debugger and other off-chip devices that the processor has halted execution.

In conjunction with the physical interface, BDM makes use of four internal registers to facilitate communication. These are: 

Keep in mind that DPSR provides double duty, shifting in instructions and data from DSDI, and shifting data and status out of DSDO.

Typically for JTAG, you will shift in some instruction or op-code, then shift some kind of null value as a placeholder to read the response. For BDM, the response from a previous instruction is shifted out while the next instruction is shifted in, cascading through the transaction. Since there is no state machine to delineate between modes, BDM is more or less a stream of layered instructions.

A typical view of a BDM message as seen through a logic analyzer.

Metaphysically BDM

The last aspect of BDM needed to understand the flow is how the command structure interacts with the BDM state machine. DSDI and DSDO carry messages in a similar format. Both can be either 35 bits or 10 bits depending on the command type and mode. For both formats, the first three bits will carry control flow encoding.

In the case of commands shifted into DPSR, the general format will be:

Start (1) | Mode (1) | Control (1) | Data (7 or 32) |

For DSDO, you’ll see:

Ready (0) | Status 0 (1) | Status 1 | Data (7 or 32) |

In the state machine, when the core is ready for the next command, the DSDO line will be driven low, to set the first “Ready” bit. The debugger will check that DSDO is held low before starting a transmission with the “Start” bit. Once the BDM core detects “Start”, data will begin to be shifted out on DSDO on the falling clock edge, as the debugger shifts in the next command.

One important note: BDM has a few modes which can be controlled at boot by asserting signals with strict timing. In the context of this blog post, the only thing you need to know is we’re detecting BDM in asynchronous mode, where the debugger provides a clock signal. These can be read about in the datasheet if you’re a curious soul who is interested in the minutia.

With that, we have enough information to write an analyzer module to use in conjunction with our Logic Analyzer. Decoding binary messages with our eyes and paper is fun and all, but we have work to do.

The Oracle and the Priest (Saleae Analyzer and Logic 2)

In order to get eyes on our signals, we’ll be using a USB Logic Analyzer that comes with a price tag similar to the laptop you plug it into and a name that everyone you meet will say differently.

If you’re new to hardware debugging/reverse engineering, a logic analyzer is a device designed to measure arbitrary analog electrical signals and turn those into discrete digital signals that carry the data we’re actually curious about. These come in many configurations with varying capabilities. In my case, we’re using the Saleae Logic Pro 16.

(image courtesy of Saleae web shop listing)

To pair with the hardware, Saleae also provides a desktop application to use with the device. The Oracle passes on wisdom that the priest must then convey.

Out of the box, it allows you to configure which channels you want to measure, with each capable of capturing in both digital and analog modes, at various sample rates, voltage trigger levels, automatic triggers, filters, etc. It’s a helpful piece of software. Among the most useful parts, though, are the various built-in protocol analyzers that let you go from zero to an exported CSV of data ready to analyze with a script. These are great until you run into a protocol not packaged, or in the extension marketplace. What is there to do?

Why, write our own, of course!

Saleae, with their hardware logic analyzer, and their Logic software, also provide a few APIs for interacting with these tools. Two of which let you make analyzer extensions. They have the High-Level Analyzer and the Low-Level Analyzer. The high-level analyzer Python API relies on the underlying protocol to already be understood. It’s useful in the case where you are trying to decode a protocol encoded on top of another protocol (i.e., a display driver over I2C). The low-level analyzer is there for when you need to start from scratch and define something new from rising and falling edges.

Thankfully for us, Saleae also provides a Sample Analyzer project that can be cloned and modified for your own purposes. This saves us the time of messing with the build system and understanding how Logic needs to be spoken to. The outline of the analyzer is all there, and you only need to worry about implementing your protocol’s logic. They even provide a rename_analyzer.py that will go through all the source and build scripts, replacing “SampleAnalyzer” with the name of your choosing.

The Scribe (Low-Level Analyzer)

This is where we come to our invention: BDMAnalyzer.

A few higher-level components that warrant discussion:

BDMAnalyzer.cpp

This is where the actual logic of decoding happens. We start in the main WorkerThread, where we have some initialization and a main loop over a switch statement. In writing this tool, I found it unnecessary to implement the various states and used a bit of a shortcut to find the initialization of the debug mode, then move past it to the first instance of the BDM Core announcing ready.

From there, we move to is CollectPackets, where we detect the Mode/Control for DSDI and Status 0/1 for DSDO. This happens first, as the results will determine if we’re reading a 32-bit data sequence or a 7-bit data sequence. With that information in hand, we can continue advancing over the capture, shifting the newest bit into our respective DSDI/DSDO packets.

A few tricks are at play here. With the multiple channels and control flow, we end up using the clock signal to find the next falling edge, then we need to synchronize the current sample across all of the channels to read the correct value. Once synchronized, we can sample the data. Data is technically sampled on the falling edge of the clock, but due to how technicalities live on paper, and we live in a squishy physical plane, signals are not always perfect. Bouncing, glitching, a butterfly flapping its wings can all contribute to that sample being slightly off at that exact point in time. To fix this without thinking too hard about the nature of the universe (don’t forget, we’re trying to debug a PowerPC chip from the mid-90s), we use another trick.

We read the signal on the falling edge, but also peek at a future sample with a user-defined tolerance of n_samples, where n_samples < sample_rate/bit_rate and where the sample rate is the speed at which the Saleae is sampling the signal, and the bitrate is our data speed. In this case, the Saleae is configured to capture at 100 MS/s and DSCK runs at 1 MHz, with 1 bit clocked out on each falling edge. That gives us 1 Mbps. With a little mental math, we get ~100 samples of tolerance. I found that a sweet spot is 10 samples past the sampled edge. This gets us past the occasional 1-2 sample glitch without sampling at the end of the period.

If we find there’s no transition within this tolerance, we accept the original sample’s measurement. If we find a transition within the tolerance, we’ll assume the sample on the falling edge was read too early/late in the rise or fall time of the signal. This isn’t quite scientific, and if these signals were faster, more work and better signal integrity would be needed. Thankfully we’re running at the speed of smell so we can continue on our way.

The rest of the code primarily labels these samples we captured and stores them somewhere for later use in exporting. By properly configuring the AnalyzerResults files, we can define a format for exporting our data. For simplicity’s sake, we export to a CSV table with either a DSDI packet or a DSDO packet per row, with the unused columns left empty and a “Type” field set to tell whatever future decoder what each row contains. We also export the Start timestamp and a Duration value. Duration isn’t as useful, but with Start time, we can keep track of when the last packet was read, and create delineation between transactions of many BDM commands and responses.

name,type,start_time,duration,Mode,Control,Instruction,Status 1,Status 2,Data
BDM,DSDI,1.93935036,0.0000351,0x00,0x00,0x7C769BA6,,,
BDM,DSDO,1.93935036,0.0000351,,,,0x01,0x01,0xFFFFFFFF
BDM,DSDI,1.94076241,0.00003505,0x00,0x00,0x7C969BA6,,,
BDM,DSDO,1.94076241,0.00003505,,,,0x00,0x00,0x00141480
BDM,DSDI,1.94211741,0.00003505,0x00,0x00,0x60000000,,,
BDM,DSDO,1.94211741,0.00003505,,,,0x00,0x00,0x00000000
BDM,DSDI,1.9427804,0.00003505,0x00,0x00,0x60000000,,,
BDM,DSDO,1.9427804,0.00003505,,,,0x01,0x01,0xFFFFFFFF
BDM,DSDI,1.9434584,0.00003505,0x00,0x00,0x60000000,,,

With the export defined, using the analyzer is basically complete. You simply build the analyzer, point Logic 2 at it, then capture your signals between your debugger and device under test. The BDM analyzer can be initialized by configuring your Saleae signal lines to the appropriate BDM pins, the Bitrate and Tolerance configured, and exporting the transactions is two clicks away.

Alongside the analyzer, we have also provided a script to decode the CSV into something even more human readable.

Here’s a comparison of the exported CSV data, and the same data decoded:


name,type,start_time,duration,Mode,Control,Instruction,Status 1,Status 2,Data

BDM,DSDI,12.2494536,0.00001005,0×01,0x01,0x0000003F,,,

BDM,DSDO,12.2494536,0.00001005,,,,0×01,0x01,0x0000003F

BDM,DSDI,12.25104359,0.00003505,0×00,0x00,0x7C769BA6,,,

BDM,DSDO,12.25104359,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.25254959,0.00003505,0×00,0x00,0x7C969BA6,,,

BDM,DSDO,12.25254959,0.00003505,,,,0×00,0x00,0x01706F80

BDM,DSDI,12.25389559,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.25389559,0.00003505,,,,0×00,0x00,0x00000000

BDM,DSDI,12.25466259,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.25466259,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.25537958,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.25537958,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.26790855,0.00003505,0×00,0x00,0x7C169BA6,,,

BDM,DSDO,12.26790855,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.26928355,0.00003505,0×00,0x00,0x7C369BA6,,,

BDM,DSDO,12.26928355,0.00003505,,,,0×00,0x00,0x000BBC2C

BDM,DSDI,12.27051155,0.00003505,0×00,0x00,0x7C569BA6,,,

BDM,DSDO,12.27051155,0.00003505,,,,0×00,0x00,0x00783F08

BDM,DSDI,12.27190454,0.00003505,0×00,0x00,0x7C769BA6,,,

BDM,DSDO,12.27190454,0.00003505,,,,0×00,0x00,0x800DB530

BDM,DSDI,12.27320254,0.00003505,0×00,0x00,0x7C969BA6,,,

BDM,DSDO,12.27320254,0.00003505,,,,0×00,0x00,0x01706F80

BDM,DSDI,12.32364441,0.00003505,0×00,0x00,0x7C769BA6,,,

BDM,DSDO,12.32364441,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.32425941,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.32425941,0.00003505,,,,0×00,0x00,0x00001000

BDM,DSDI,12.32563441,0.00003505,0×00,0x00,0x7C600026,,,

BDM,DSDO,12.32563441,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.32625841,0.00003505,0×00,0x00,0x7C769BA6,,,

BDM,DSDO,12.32625841,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.3269354,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.3269354,0.00003505,,,,0×00,0x00,0x24000400

BDM,DSDI,12.3283714,0.00003505,0×00,0x00,0x7C6902A6,,,

BDM,DSDO,12.3283714,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.3290194,0.00003505,0×00,0x00,0x7C769BA6,,,

BDM,DSDO,12.3290194,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.3296354,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.3296354,0.00003505,,,,0×00,0x00,0x00000001

BDM,DSDI,12.33098039,0.00003505,0×00,0x00,0x7C6102A6,,,

BDM,DSDO,12.33098039,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.33163039,0.00003505,0×00,0x00,0x7C769BA6,,,

BDM,DSDO,12.33163039,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.33225139,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.33225139,0.00003505,,,,0×00,0x00,0x20000000

BDM,DSDI,12.33361839,0.00003505,0×00,0x00,0x7C7042A6,,,

BDM,DSDO,12.33361839,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.33427139,0.00003505,0×00,0x00,0x7C769BA6,,,

BDM,DSDO,12.33427139,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.33492738,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.33492738,0.00003505,,,,0×00,0x00,0x800DB530

..

BDM,DSDI,12.37525128,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.37525128,0.00003505,,,,0×00,0x00,0x00000000

BDM,DSDI,12.37677328,0.00003505,0×00,0x00,0x7C769AA6,,,

BDM,DSDO,12.37677328,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.37741728,0.00003505,0×00,0x01,0x01706F80,,,

BDM,DSDO,12.37741728,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.37862927,0.00003505,0×00,0x00,0x7C969AA6,,,

BDM,DSDO,12.37862927,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.37928727,0.00003505,0×00,0x01,0x00000000,,,

BDM,DSDO,12.37928727,0.00003505,,,,0×01,0x01,0xFFFFFFFF

BDM,DSDI,12.38053327,0.00003505,0×00,0x00,0x60000000,,,

BDM,DSDO,12.38053327,0.00003505,,,,0×01,0x01,0xFFFFFFFF

##################################################

##################### BLOCK 1 ####################

LAST: 2.23526979 ____________ THIS: 12.2494536

= PC ===== DSDI ============================= DSDO

———————————————-NULL

0x0114: ASRT_MASK_BREAK 0x0000003F————NULL

0x0115: mtspr DPDR, r3————0x01706F80

0x0116: mtspr DPDR, r4————0x00000000

0x0117: nop——————————–NULL

0x0118: nop——————————–NULL

0x0119: nop——————————–NULL

0x011a: mtspr DPDR, r0————0x000BBC2C

0x011b: mtspr DPDR, r1————0x00783F08

0x011c: mtspr DPDR, r2————0x800DB530

0x011d: mtspr DPDR, r3————0x01706F80

0x0143: mfmsr r3————————NULL

0x0144: mtspr DPDR, r3————0x00001000

0x0145: nop——————————–NULL

0x0146: mfspr r3, DPDR——————NULL

0x0147: CORE_DATA: 0x00001000—————–NULL

0x0148: mtmsr r3————————NULL

0x0149: mfmsr r3————————NULL

0x014a: mtspr DPDR, r3————0x00001000

0x014b: nop——————————–NULL

0x014c: mfcr r3————————-NULL

0x014d: mtspr DPDR, r3————0x24000400

0x014e: nop——————————–NULL

0x014f: mfctr r3————————NULL

0x0150: mtspr DPDR, r3————0x00000001

0x0151: nop——————————–NULL

0x0152: mfxer r3————————NULL

0x0153: mtspr DPDR, r3————0x20000000

0x0154: nop——————————–NULL

0x0155: mfspr r3, SPRG0—————–NULL

0x0156: mtspr DPDR, r3————0x800DB530

..

0x0182: mftbu r3————————NULL

0x0183: mtspr DPDR, r3————0x00000000

0x0184: nop——————————–NULL

0x0185: mfspr r3, DPDR——————NULL

0x0186: CORE_DATA: 0x01706F80—————–NULL

0x0187: mfspr r4, DPDR——————NULL

0x0188: CORE_DATA: 0x00000000—————–NULL0x0189: nop————————————

If you made it this far, thanks for reading!

Get BDMAnalyzer, our latest open-source tool

Download BDMAnalyzer from GitHub, and start decoding BDM signals ⇢

And check out more open-source tools by Zetier:

Bungeegum: automates in-memory execution testing for Android cyber tools within real-world SELinux contexts. Read the article →

Lariat: streamlines Android device testing by integrating seamlessly with Device Farmer’s REST API. Read the article →

flaShMASH: designed for analyzing and reconstructing data from multiple flash memory dumps. Read the article →

Enjoyed this post?

Share on Hacker News

Illustrations by Rebecca DeField.

Your Next Read

Discover more from Zetier

Subscribe now to keep reading and get access to the full archive.

Continue reading