Reverse Engineering MCU LPC2131FBD64 Heximal: Thirty-Two Kilobytes of Room
Array size is usually treated as a purchasing decision. On the smallest member of this branch it is a design constraint that leaves fingerprints all over the firmware, and those fingerprints are useful when the code arrives without documentation.
Thirty-two kilobytes of flash and eight kilobytes of SRAM is enough for a serious product and not enough for a comfortable one. Everything the firmware wants to do has to fit, and what it chose to leave out describes the product as clearly as what it chose to include.
The ratio that shapes everything
The boot loader occupies a fixed region at the top of the array regardless of density, so its share of the total grows as the array shrinks. On the large parts it is a rounding error. Here it is a noticeable slice of a small budget, and the application has to be built and linked around a hole it cannot use.
The practical consequences appear in three places:
- the image is compressed or overlapped, because there was no room for padding
- error handling is minimal, because a full reporting path costs kilobytes
- the product has no field update mechanism of its own, because a second image will not fit
That third point is the most useful observation. A device in this density almost never carries a bootloader written by the customer, which means the only programming interface is the factory one, which means recovery work starts at the factory loader rather than in the application.
What a heximal of a small device looks like
Small files are easy to underestimate. A heximal from this part is a few tens of kilobytes, dense from the first byte to the last, with a short erased tail. There is no space wasted on alignment and no room for a font table unless the font is the product.
Reading it, the density has an effect on interpretation. Functions are packed with no padding between them, so a linear disassembly desynchronises less often and the boundaries are easier to find. On the other hand, the compiler has been aggressive: code is Thumb almost everywhere, tail calls are common, and functions share epilogues in ways that make call graphs harder to follow than on a part with room to spare.
| Property | Effect on reading the image |
|---|---|
| Thumb throughout | instruction boundaries every two bytes, dense and readable |
| no padding between functions | boundaries visible from branch targets only |
| shared epilogues | the call graph needs branch analysis, not linear scanning |
| compressed tables | lookups appear as data with a short decoder in front |
| minimal error paths | handlers are short, and often fall through to a reset |
The eight kilobyte SRAM problem
SRAM is the second constraint and the more interesting one. Eight kilobytes has to hold the stack, the working buffers, the protocol state and anything copied out of flash for speed. There is very little left over.
The consequence for recovery is direct. On a device with generous RAM, a recovered image can be run on a bench with a small monitor routine loaded into RAM alongside it. Here there is barely room for the monitor, and what room exists is exactly the memory the firmware is using for its stack. Any live analysis has to be incremental: halt, read, resume, rather than load and observe.
It also means the firmware rarely relocates code to RAM, because it cannot afford to. That is good news: on this part, what is in the flash is almost exactly what runs, and the duplication problem described in LPC2114FBD64 code reading is largely absent.
Recovery routes in this density
The options are the same as everywhere in the family, but their relative cost changes.
The factory loader is the first choice, and on a small part it is fast — a full read is seconds. The debug port is the second, and it works better here than on a larger device because there is less state to capture. Invasive work is the last, and it is worth noting that a small die is not automatically easier to open; the array is smaller but the surrounding logic is the same.
The protection states behave as documented in LPC2101FBD48 protection states, and the erase consequence is as absolute as it is on any other density, because sector 0 is still the sector that holds the vectors — see LPC2104FBD48 erase behaviour.
What the customer usually wants
Requests for a small device are rarely about the code. They are about three things: a replacement board for an obsolete product, the calibration values that a repair needs, or an understanding of a protocol that the documentation lost.
All three are achievable from a thirty-two kilobyte image, and the third is where the density actually helps. A protocol implementation that fits in this much space is compact enough to read end to end, and compact implementations are the ones that can be described completely in a report. Larger images hide their protocol inside layers of abstraction; this one cannot afford them.
One thing to check before delivering
Confirm the device density from the part identifier rather than from the file size. A heximal that is thirty-two kilobytes long could have come from this part or from the bottom of a larger one, and the boot region sits at a different address in each case. Getting that wrong produces a file that verifies and does not boot, for reasons that take an afternoon to find.
The related note on where the boot region begins, and why it must be excluded from anything written back, is in LPC2124FBD64 binary anatomy. When the array is large enough that partial recovery becomes possible, LPC2132FBD64 data recovery covers the arithmetic of missing sectors.
Small is not simple
The instinct is that a small device is a simple job. It is a faster job, and the analysis is often more complete, but the constraints that made the firmware small are exactly the constraints that make its behaviour tight and interlocked. A product that had to fit into thirty-two kilobytes is a product where every byte earned its place, and reading it is correspondingly dense work.