Skip to main content
Glama

get_vector_table

Reads the interrupt vector table to show which interrupts a firmware services, including handler status, unhandled traps, and runtime-installed vectors.

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 has overridden with its file and line.

  • "assembly" — a strong assembly definition. Assembly services the interrupt.

  • "unhandled" — a weak assembly definition that nothing overrode. A CMSIS startup file makes this an alias of Default_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 calling NVIC_SetVector. A target that defines CMSIS_VECTAB_VIRTUAL keeps 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 holds installed, one entry per call site with the handler name, its file and line, and at, where the registration happens. Follow at to see WHEN it happens: on the Mbed project us_ticker_irq_handler reaches slot 25 from us_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 carries installed.

  • "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, file and line name the assignment in it — on the Mbed project, __StackTop at .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_context on the name, then find_references on 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.

Two sources are read, and source says which one a row came from:

  • "assembly" — a table of address words, .word or .long in 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, with gen_isr_tables.py. These rows also carry table_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 the nrfx_isr shim 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, a dict with info says so, because an interrupt connected at run time leaves nothing to read and the rows are then not all of them.

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 (fw-context index).

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). variant: Build variant (multi-build project). Omit to use default_variant. One query answers for ONE build. image: Sysbuild image within the variant. Required when the variant holds several: each image is a separate program.

Returns: list of 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). A "c" source row also holds table_name and table_usr. A "c" status row can hold overridden, a dict with 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 ``limit``: they describe the whole table, and the longest table
is where they matter most.  When more slots exist than ``limit``,
a dict with ``truncated`` sits between the slots and those two,
saying how many slots are not shown.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
imageNoSysbuild image within the variant. Required when the variant holds several: each image is a separate program.
limitNoMaximum slots (default 400).
projectNoProject 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.
variantNoBuild variant (multi-build project). Omit to use default_variant. One query answers for ONE build.
project_rootNoProject 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_onlyNoReturn only the slots that reach the default handler.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed3 schema fields changedv0.32.0
    • changedInput schema / properties / image / description
      Previous value: -"Sysbuild image name within the variant (multi-project). Omit for all images of the variant."New value: +"Sysbuild image within the variant. Required when the variant holds several: each image is a separate program."
    • addedInput schema / properties / limit / minimum
      Added value: +1
    • changedInput schema / properties / variant / description
      Previous value: -"Build variant name (multi-project). Omit to use default_variant or fail-closed. Use '*' for all variants."New value: +"Build variant (multi-build project). Omit to use default_variant. One query answers for ONE build."
  2. Addedv0.30.0

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden—and it delivers extensively. It declares 'Read-only. No side effects. Requires an index of the assembly', explains the meaning of every status value, describes the two sources and their implications, warns that slot numbers are not joined across tables, and details edge cases like coverage rows and runtime-installed handlers. No contradiction with annotations exists because none were provided.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but the tool is genuinely complex and the length is organized with bold headers, bullet-like status explanations, and a front-loaded purpose statement. Some measured examples are verbose and could be trimmed, but the structure makes the detail navigable and each major section addresses a distinct decision an agent must make.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is exceptionally complete for a complex tool: it covers all statuses, both sources, the unspecified-source 'build' rows, table boundaries, coverage holes, interrupts rows, limit/truncation behavior, and the non-empty error/info response contract. It even documents failure modes ('Never empty: one dict with error... or info...'). An agent has enough context to select and invoke the tool correctly in nearly any scenario.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds some meaning beyond the schema: it states the limit max is 1000 (schema only gives default 400), clarifies that 'One query answers for ONE build' for variant, and reinforces the project_root/project exclusivity. Most parameter text repeats the schema, but the extra constraints justify a 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Read the interrupt vector table, and say what services each interrupt.' It explains the conceptual role of the vector table and gives concrete use cases ('which interrupts does this firmware service', 'find the handler for one interrupt'), clearly distinguishing this tool from the sibling lookup tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit use cases are listed ('Use it to answer...'), and the description names fallback alternatives: 'get_symbol_context on the name, then find_references on the table it uses' for dispatcher rows, and 'find_references on the handler name' for interrupts this tool cannot show. It also states what is not covered (branch-instruction architectures, link-time-resolved addresses), giving clear when-to-use and when-not-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.