Skip to content

STM32: Parse the IRQ vectors get_irq_handler was written to read - #763

Merged
twizmwazin merged 1 commit into
masterfrom
feature/fix-cle-stm32-irq
Aug 18, 2026
Merged

STM32: Parse the IRQ vectors get_irq_handler was written to read#763
twizmwazin merged 1 commit into
masterfrom
feature/fix-cle-stm32-irq

Conversation

@zardus

@zardus zardus commented Aug 17, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

VectorTable.get_irq_handler read self._data, an attribute the structure never set, so every call raised AttributeError. from_bytes built the structure with from_buffer_copy over the first 64 bytes and discarded the rest, which is where the peripheral IRQ vectors it wants live.

Those 64 bytes are the 16 system exception vectors the ctypes fields describe; how many IRQ vectors follow them is a property of the device that a raw flash image does not record. from_bytes now unpacks the words after them into irq_handlers and get_irq_handler indexes that. A Cortex-M NVIC drives at most 480 external interrupt lines, so the parse stops there rather than reporting whatever code follows the table as a handler, and a negative IRQ number is rejected.

The regression reads the IRQ vectors of the Nucleo-L152RE image already in binaries and checks them against Default_Handler in the ELF build of the same firmware; python -m pytest tests/test_stm32.py covers it.

Follow-up to #716, as requested in #716 (comment). Validation: #763 (comment)

@zardus

zardus commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head d6b2e5c317b62b41b14b4b2553fd1b61c7b127a3 against baseline a2ccdda9fb375e23d9ba6a4689712865566e91ed.

  • Regression: python -m pytest tests/test_stm32.py -q — 11 passed on head. With only cle/backends/stm32.py reverted to the baseline, four tests fail: two with AttributeError: 'VectorTable' object has no attribute '_data' at cle/backends/stm32.py:48, and two with AttributeError on irq_handlers and MAX_IRQ_VECTORS
  • Coverage of the four cases: the IRQ vectors of tests/armel/i2c_master_read-nucleol152re.bin resolve to Default_Handler in the ELF build of the same firmware, with the Thumb bit set, and agree with irq_handlers; that image runs 16328 words past its system vectors and the parse still stops at the NVIC's last interrupt line, so get_irq_handler(480) is 0 rather than a word of code; a table built from only the 64 system-vector bytes has an empty irq_handlers and reports 0; a negative IRQ number raises ValueError instead of returning a system exception vector
  • Full suite: python -m pytest -q in cle — 236 passed, 9 skipped on head, against 232 passed, 9 skipped on the baseline, so the delta is the 4 new tests
  • Lint/type: merge-base pylint and pyright comparison over the changed files — cle/backends/stm32.py 10.00 -> 10.00 and badness 0.0 -> 0.0, tests/test_stm32.py 10.00 -> 10.00 and badness 0.0 -> 0.0
  • Hooks: pre-commit run over the changed files — passed, tree unchanged
  • Complete workspace gate, with angr adopted so its suite is not skipped: cle 236 passed, 9 skipped; angr Python 2471 passed, 46 skipped, 2 xfailed, 260 subtests passed; angr Rust 35 passed, 0 failed; plus environment, workspace checks, test-inputs, all pre-commit hooks and per-feature instances. archinfo, pypcode, pyvex, claripy and angr-management were skipped as unadopted and are not callers of this backend.

Caveats: the 9 skips are the pre-existing TODO markers in tests/test_macho_bindinghelper.py. A raw flash image still does not record how many IRQ vectors its device has, so irq_handlers holds every word the image has room for up to the NVIC's limit, and a caller reading past its device's IRQ count gets whatever follows the table in flash; the architectural bound only stops that at 480.

@angr-bot

Copy link
Copy Markdown
Member

Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_763

@twizmwazin

Copy link
Copy Markdown
Member

Is there a better way this could be implemented? _data seems opaque, why don't we parse them instead?

VectorTable.from_bytes built the structure with from_buffer_copy over the first
64 bytes and discarded the rest, so get_irq_handler raised AttributeError on the
self._data it expected. The peripheral vectors it wants live past those 64 bytes
anyway, which is exactly the part the ctypes fields cannot describe: the fields
cover the 16 system exception vectors, and how many IRQ vectors follow them is a
property of the device that a raw flash image does not record.

from_bytes now unpacks the words after the system vectors into irq_handlers, and
get_irq_handler indexes that. A Cortex-M NVIC drives at most 480 external
interrupt lines, so the parse stops there instead of reporting whatever code
follows the table as a handler, and a negative IRQ number is rejected rather
than silently reading a system exception vector.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@zardus
zardus force-pushed the feature/fix-cle-stm32-irq branch from 4f523b9 to d6b2e5c Compare August 18, 2026 06:04
@zardus zardus changed the title STM32: Read the IRQ vectors get_irq_handler was written to read STM32: Parse the IRQ vectors get_irq_handler was written to read Aug 18, 2026
@zardus

zardus commented Aug 18, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Agreed, and done in d6b2e5c317b62b41b14b4b2553fd1b61c7b127a3. _data is gone: from_bytes unpacks the vectors into irq_handlers: tuple[int, ...] and get_irq_handler is an index into it.

What drove the raw read, for the record. The ctypes fields describe the 16 system exception vectors, which the architecture fixes, and every peripheral IRQ vector lies past the end of the structure — the format is just u32 words, but the structure cannot have a field for them because how many there are is a property of the device, and a raw flash image does not record it. The Nucleo-L152RE fixture runs 16328 words past its system vectors and only its device's IRQ count of them are vectors, so the old patch read bytes on demand rather than materialising all of that.

That is not a reason not to parse, though: a Cortex-M NVIC drives at most 480 external interrupt lines (ARMv8-M; 240 on ARMv7-M), so the table cannot be longer than that whatever the image holds. Parsing bounded by the NVIC costs 480 words and fixes a bug the byte read had — get_irq_handler(5000) used to return a word of code as if it were a handler, and now returns 0.

python -m pytest tests/test_stm32.py — 11 passed; four fail with cle/backends/stm32.py reverted. Full cle suite 236 passed, 9 skipped. Validation comment updated in place: #763 (comment)

@twizmwazin
twizmwazin merged commit 3f408bb into master Aug 18, 2026
19 checks passed
@twizmwazin
twizmwazin deleted the feature/fix-cle-stm32-irq branch August 18, 2026 15:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants