get_vector_table
Read a firmware interrupt vector table to show which handler services each interrupt, find gaps, and list unhandled slots.
Instructions
Read the interrupt vector table, and say what services each interrupt.
The vector table is how an interrupt reaches code. Nothing CALLS a handler — the hardware reads a slot and jumps — so a handler has no caller, and every other tool shows it as unreferenced. This tool reads the table itself, from the assembly the build compiles.
Use it to answer "which interrupts does this firmware service", to find the handler for one interrupt, or to find the interrupts that reach the trap loop.
The slot number is the position in the table. What that position
means belongs to the architecture, not to the index. On Cortex-M
slots 0 to 15 are the system exceptions and slot 16 + n is external
interrupt n, so TIM2_IRQHandler in slot 44 is TIM2_IRQn = 28.
On other architectures the same position means something else.
The status field says what services the interrupt:
"c"— a definition outside assembly. Code runs. When the index also holds the weak definition that this one replaced, the row hasoverriddenwith its file and line."assembly"— an assembly definition that is not an alias. Assembly services the interrupt."unhandled"— an assembly name that is an alias (.thumb_set) of another symbol, and nothing overrode it. The row holdsaliases, the name, file and line of the alias target. A CMSIS startup file aliases each unserviced interrupt toDefault_Handler, which is an infinite loop. If the interrupt fires, the device stops."runtime"— the image holds that same alias, and the code installs a real handler into this slot by callingNVIC_SetVector. A target that definesCMSIS_VECTAB_VIRTUALkeeps its vector table in RAM and fills it that way, so the interrupt IS serviced once the registering code has run — and not before it. The row holdsinstalled, one entry per call site with the handler name, its file and line, andat, where the registration happens. Followatto see WHEN it happens: on the Mbed projectus_ticker_irq_handlerreaches slot 25 fromus_ticker_init, so the tick source is unserviced until the ticker starts. A row with a real static definition keeps its own status and still carriesinstalled."data"— the slot holds an address BUILT from the symbol it names (.word z_main_stack + CONFIG_MAIN_STACK_SIZE), so the symbol is a base and nothing jumps to it. On Cortex-M this is slot 0 of a Zephyr table: the initial stack pointer. Do not read it as code."linker"— the linker script gives the address and no compiled file defines the name. Slot 0 holds the initial stack pointer, not a handler, and looks like this. When the index read the script,fileandlinename the assignment in it — on the Mbed project,__StackTopat.link_script.ld:148. Do not read this row as code: there is no function to follow."dispatcher"— the slot reaches a function that holds more than one slot of this table AND calls through a pointer. It cannot be servicing one particular interrupt; it decides at run time where to go. Zephyr fills every external IRQ slot with_isr_wrapper, which reads the interrupt number and jumps through_sw_isr_table. Follow it:get_symbol_contexton the name, thenfind_referenceson the table it uses. A handler that merely calls one registered callback is NOT this — it holds a single slot and keeps"c".
A "c" row with overridden is the CMSIS pattern: the startup
file defines each handler weakly, the project defines the same name
again, and the linker keeps the strong one.
Three sources are read, and source says which one a row came
from:
"assembly"— a table of address words,.wordor.longin a vector section, which is what a CMSIS startup file writes."c"— an array whose elements are addresses of functions, which is what a build that generates its table produces. Zephyr writes its external interrupts this way, withgen_isr_tables.py. These rows also carrytable_name, the array the slot belongs to."build"— the registration the build itself recorded, for a slot the other two could not name. A generator writes a resolved ADDRESS into every slot that is in use, so those slots have no name in the source at all — and they are the interrupts the firmware actually services. Measured on an nRF54L application: 284 of 290 slots name the spurious stub, and the 6 without a name are IRQ 89, 198, 219, 228, 269 and 270, which these rows fill in.Such a row can carry
argument, the symbol the build passes to the handler. Read it as an argument and not as a second handler: behind thenrfx_isrshim it is the real worker (nrfx_power_clock_irq_handler), while for another driver it is the device (__device_dts_ord_116). When the build enables run-time registration (CONFIG_DYNAMIC_INTERRUPTS), theinterruptsdict says so, because an interrupt connected at run time leaves nothing to read and the rows are then not all of them. A"build"row hasfileempty andline0 when the handler name has no single definition in the index.
Recognition is by shape, never by name, so any array of function
addresses is reported and the row names its table. A table of
interrupt handlers and a table of state machine steps are the same
construct, and table_name is how they are told apart.
Slot numbers are not joined across tables. Each slot is the index
inside its own table, so two tables both start at 0 — read slot
together with table_name and source. They are not renumbered
into one run because the index does not hold the length of the
assembly table, only its occupied slots, and an offset derived from
that would be silently wrong for every entry of a 290-entry table.
A coverage row follows the slots for each table longer than the
number of slots that name a function. It says how many of the declared
elements were named and which slot numbers were not, because a name is
not always there to be read: an element can be a zero, or an address
the linker resolved before the table was written.
Read it in both directions. A hole in a table of handlers is a vector
nothing services. A hole in Zephyr's _sw_isr_table is the
opposite — measured on an nRF54L application, 284 of 290 slots name the
spurious stub and the 6 without a name are the interrupts in use. The
tool reports where to look; which meaning applies depends on the table.
An interrupts row answers "which are unserviced" wherever the
build recorded its registrations, and it is the answer under
unhandled_only too. The row-level unhandled status is read
from an alias edge, which a CMSIS startup writes and a generator does
not — measured, zero unhandled rows on all eleven images of a Zephyr
project against 39 to 72 on four CMSIS and Mbed ones. The
registrations settle it from the other side: what the build connected
is the whole list, so anything else has nothing servicing it, and no
handler has to be recognised by name.
The complement is taken over the length of the table, NOT over the
slots that hold a stub. Measured on an mcuboot image: its software
table names 44 of 48 slots, and one of those 44 is
uarte_0_direct_isr, an interrupt wired straight into the vector
table. It IS serviced, and counting stubs would report it as not.
What is still not covered: an architecture that builds its table from
branch instructions (arm64, Xtensa, MIPS) writes no table of
addresses in either form. A handler whose address the build resolved
at link time has no name to report either — coverage names its slot
but not the function. For an interrupt this tool cannot show,
find_references on the handler name still gives every reference
the index holds.
Read-only. No side effects. Requires an index of the assembly and the
reference index (fw-context index — refs on by default).
Args:
project_root: Project root. Auto-detected if omitted.
project: Project name or project_id — call list_projects to get them. Use
it to ask about a project that is not the project of the current
directory. It is an alternative to project_root, which takes a root
path. Give one of the two, not both.
unhandled_only: When True, return only the "unhandled" slots.
limit: Maximum slots (default 400, max 1000).
offset: Skip this many slots. Reads the next page; the page
notice names the offset to use.
variant: Variant of the build (a board, or a PlatformIO
environment). On a project of more than one build, give the
arguments of one build as the refusal lists them: the
variant, the image, or the two. fw-context does not select a
build. One query answers for ONE build.
image: Image of the build: a separate program, such as the
application or the bootloader. Give it when the build has an
image name.
Returns:
The page notice {total, offset, shown, more, hint} comes
before the rows of the answer: total counts every slot of every table. Then a list
of slot dicts sorted by source, then table, then slot. Each holds:
slot (int), name, file, line, source ("assembly", "c" or
"build"),
status ("c", "assembly", "unhandled", "runtime",
"data", "linker" or "dispatcher"), and table_file and table_line
(where the slot is written). Every path in this answer is
absolute, at included. A "c" source row also holds
table_name and table_usr. A "c" status row can hold
overridden, a dict with file and line. An "unhandled" row
holds aliases, a dict with name, file and line. Any assembly row
can hold installed, a list of dicts with name, file, line and at.
Never empty: one dict with ``error`` (no index) or ``info`` (no
vector table in this build). Check both keys first. A dict with
``coverage`` follows the slots for each table that has unnamed
elements, and a dict with ``interrupts`` says which are connected
and which are not — the latter in both modes. Neither is subject
to the page: they describe the whole table, the longest table is
where they matter most, and they come after the slots of each
page. A page after the last slot gives one ``info`` dict that
names the total, and then those two.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| image | No | Image of the build: a separate program, such as the application or the bootloader. On a project of more than one build, give it when that build has an image name, as the refusal lists it. | |
| limit | No | Maximum slots (default 400, max 1000). | |
| offset | No | Skip this many slots. Reads the next page of a long table. | |
| project | No | Project name or project_id — call list_projects to get them. Use it to ask about a project that is not the project of the current directory. It is an alternative to project_root, which takes a root path. Give one of the two, not both. | |
| variant | No | Variant of the build (a board, or a PlatformIO environment). On a project of more than one build, give the arguments of one build as the refusal lists them: the variant, the image, or the two. fw-context does not select a build for you. One query answers for ONE build. | |
| project_root | No | Project root. Auto-detected if omitted. This field also accepts a project name or a project_id, but project is the clear field for those. | |
| unhandled_only | No | Return only the slots with status 'unhandled' (an alias of a default handler, nothing installed at run time). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |