import { ProbeBackend, CommandResult, GDBServerInfo } from "./backend"; import { ProcessManager } from "../utils/process-manager"; /** One entry from J-Link's internal device list. */ export interface SupportedDevice { manufacturer: string; name: string; core: string; /** Total flash across all areas, in bytes. */ flashSize: number; ramSize: number; } export interface JLinkConfig { installDir: string; device: string; interface: "SWD" | "JTAG"; speed: number; serialNumber?: string; gdbPort: number; rttTelnetPort: number; /** * Address of the SEGGER RTT control block, when it is known. * * J-Link normally locates this itself by scanning RAM. Knowing it lets us * re-point the probe at the block after a target reset, which J-Link does * not do on its own — see restartRTT(). */ rttControlBlockAddress?: number; swoTelnetPort: number; } export declare class JLinkBackend extends ProbeBackend { readonly type: "jlink"; readonly displayName = "SEGGER J-Link"; private config; private processManager; /** ExpDevList output, parsed once — the list is compiled into the DLL. */ private deviceCatalog; private gdbOutputBuffer; constructor(config: Partial, processManager: ProcessManager); private get jlinkExe(); private get gdbServerExe(); /** * Raw JLinkExe execution. Does NOT include preflight/locking. * Use the public methods (which call withPreflight) instead. * * Notes on flags: * - `-ExitOnError 1` is intentionally NOT passed. J-Link Commander * treats the transient "Failed to initialize DAP" line emitted before * a successful `connect under reset` fallback as an error, causing * the interpreter to bail before running the user's script. That * breaks any target where the first attach attempt is unreliable * (e.g. STM32L0 at 4 MHz SWD, MCU running from MSI). We classify * real failures below by parsing stdout instead. */ private execRaw; /** * Deterministic recovery sequence: * 1. Stop GDB server if running * 2. Try connect under reset * 3. If that fails, reduce speed (4000 → 1000 → 400) and retry */ recover(): Promise; /** * Override preflight to use execRaw directly (avoids deadlock since * preflight is called inside acquireLock from withPreflight). */ preflight(): Promise; /** True when we should prefer the GDB bridge over spawning JLinkExe. */ private useGdb; /** * Translate a caller-supplied register name into what GDB accepts. * * The `read_register` tool documents J-Link-style names ('PC', 'SP', * 'R0'), but GDB's register names are lowercase and case-sensitive — * `info registers PC` fails with "Invalid register `PC'". Strip an * optional `$` sigil, lowercase, and map the J-Link-only spellings * that have a GDB equivalent. */ private static toGdbRegName; /** Wrap a GDB command result in the shared `CommandResult` shape. */ private runViaGdb; getDeviceInfo(): Promise; halt(): Promise; resume(): Promise; /** * Reset the target, optionally leaving it stopped at the reset vector. * * This used to hand-roll a vector catch: set DEMCR.VC_CORERESET, reset, * clear it. That was reinventing something J-Link already does, and doing * it worse. Per SEGGER's reset-strategy reference, the default Cortex-M * strategy (type 0) *is* a vector-catch reset — "the device should halt * immediately after a reset (before it can execute any user-application * instruction), which is ensured by setting the VC_CORERESET in the DEMCR" * — and the GDB server documents `monitor reset` as "resets and halts the * target CPU". Type 0 also lets J-Link pick the per-device sequence, which * matters on parts whose reset needs vendor-specific handling; a hand-built * sequence silently opts out of that. * * https://kb.segger.com/J-Link_Reset_Strategies * * So the reset itself is J-Link's job. Ours is to check it actually * happened — see verifyResetHalt. A reset that quietly does nothing (the * probe owned by another process, say) otherwise reports success while the * core keeps running, which is how this landed as "PC in main after * reset(halt)" and got misread as a missing vector catch. * * @param strategy Optional J-Link reset type. Omit to let J-Link choose, * which SEGGER recommends. Type 1 resets the core only via VECTRESET and * leaves peripherals alone; type 2 drives the reset pin. */ reset(halt?: boolean, strategy?: number): Promise; /** Read one 32-bit little-endian word, or null if the read did not land. */ private readWord32; /** * Confirm a halting reset actually left the core at the reset vector. * * The check is against the vector table the *target* is using — VTOR, then * the word at VTOR+4 — rather than a hardcoded address, so it holds for * bootloaders and relocated tables too. The Thumb bit is masked off, and a * small window is allowed because some strategies stop a few instructions * in. * * If anything needed for the check cannot be read, the original result is * returned untouched. An unverifiable reset is not a failed one, and * inventing a failure here would be the same class of lie as the silent * success this exists to catch. */ private verifyResetHalt; step(): Promise; /** * Read `length` bytes at `address`. * * The byte count goes to J-Link Commander as bare hex digits. * * `mem` parses its length as hex, so a decimal count is silently misread: * `mem 0x0, 20` returns 0x20 = 32 bytes and `mem 0x0, 256` returns 0x256 = * 598. Both observed on hardware. Every caller passing a decimal length — * readFaultRegisters asking for 20, snapshot asking for 64 — was * over-reading, and any caller counting bytes back got the wrong answer. * * It must be bare hex, NOT 0x-prefixed: `mem 0xe000edf0, 0x4` is rejected * outright, which took out even the DHCSR preflight read and made every * memory tool report "Target may be unreachable". Address takes 0x, length * does not. */ readMemory(address: number, length: number): Promise; writeMemory(address: number, value: number): Promise; readAllRegisters(): Promise; /** * Read one named register. * * The JLinkExe path deliberately does NOT use `rreg`. J-Link Commander * rejects both the ARM mnemonics and the architectural names it prints as * valid — `rreg PC` and `rreg R15` both answer "Illegal register name." and * dump a 100-entry list — so the tool returned an error page instead of a * value. `regs` prints the whole set reliably, so read the set and pick the * register out of it with the parser that already understands both the * J-Link and GDB formats. * * This also makes the tool answer the question that was asked: previously a * successful call returned the entire register dump. */ readRegister(name: string): Promise; /** * Normalize a register name to the spelling `parseRegisters` produces. * Accepts the ARM mnemonics, the Rn forms, and a `$` sigil. */ private static toCanonicalRegName; /** * Read memory over the GDB session and normalize the output to the * J-Link Commander format (`ADDR = XX XX ... ASCII`) so downstream * consumers like `readFaultRegisters` / `parseMemoryDump` don't need to * care which channel served the read. */ private readMemoryViaGdb; flash(filePath: string, baseAddress?: number): Promise; erase(): Promise; /** * Breakpoints during a GDB session must go through GDB. * * The JLinkExe path is doubly wrong once a session is live. It evicts the * GDB server (one client per probe), and the breakpoint it sets dies with * the transient JLinkExe process anyway — so the caller loses their session * and does not even get a breakpoint for it. GDB's own breakpoints persist * for the life of the session and are what `resume`/`gdb_wait` will actually * stop on. */ setBreakpoint(address: number): Promise; clearBreakpoints(): Promise; executeRaw(commands: string[]): Promise; startGDBServer(): Promise<{ success: boolean; message: string; }>; /** * Wait for the GDB server to claim the probe, or to fail trying. * * Readiness is the server's own "Waiting for GDB connection" banner. The * failure to watch for is the probe being held by someone else — most often * the previous session's server, which had been killed moments earlier but * had not yet released the USB device. */ private awaitGdbServerReady; stopGDBServer(): { success: boolean; message: string; }; isGDBServerRunning(): boolean; getGDBServerStatus(): GDBServerInfo; getGDBServerOutput(lines?: number): string[]; isDeviceConfigured(): boolean; getDeviceName(): string; setDevice(device: string): void; /** * Point the server at the firmware's RTT control block at runtime. * * Every tool that touches RTT tells people to set JLINK_RTT_ADDR, and until * now the only way to do that was an environment variable read at startup — * so a caller holding the exact value, from a symbol table it had just read, * had nowhere to put it. Nagging about something you give no way to supply * is worse than silence. */ setRttControlBlockAddress(address: number): void; listDevices(): Promise; supportsRTT(): boolean; getRTTPort(): number; getRttControlBlockAddress(): number | undefined; /** * DHCSR bit 17 (S_HALT) is the core's own answer. Read it rather than * tracking what we think we last did to the target — GDB, an assistant, or * another tool may have stopped it since. */ isTargetRunning(): Promise; checkInstallation(): { ok: boolean; detail: string; suggestedAction?: string; }; /** * Every device name this J-Link installation accepts. * * `set_device` takes an exact string and the tool description offers two * examples, which leaves a caller guessing at part numbers for any chip that * is not one of them — and a wrong guess fails in a way that looks like * broken hardware. J-Link knows the answer: ExpDevList dumps the DLL's * internal list, 9800-odd parts across 75 manufacturers. * * The list is compiled into the DLL, so it is fixed for an installation and * fetched once. It also does not need the probe — measured with none * attached, the connect failed and the file was still written — but the * spawn is serialised anyway, because a J-Link serves one client at a time * and evicting a live GDB server to read a static list would be a poor * trade. */ listSupportedDevices(): Promise; /** Identity of the J-Link install, so an upgrade invalidates the cache. */ private catalogCachePath; private readCachedCatalog; private writeCachedCatalog; /** * Parse one ExpDevList line. * * "ST", "STM32F407IE", "Cortex-M4", {0x08000000, 0x00080000}, {0x20000000, 0x00020000} * "Nordic Semi", "nRF52840_xxAA", "Cortex-M4", { {0x0, 0x100000}, {0x10001000, 0x1000} }, {0x20000000, 0x00040000} * * Half the entries carry several flash areas and nest an extra brace level, * so rather than match the punctuation, take the address/size numbers in * order: the last pair is RAM and everything before it is flash. That holds * for both shapes above, and for anything else with the same trailing RAM * convention. */ static parseDeviceList(text: string): SupportedDevice[]; /** * Re-point the probe at the RTT control block after a target reset. * * A reset does not stop the target logging, but it does stop J-Link * collecting. Measured on an nRF52840 across a reset, sampling the control * block from both sides: * * WrOff (target writes): 582 -> 802 * RdOff (host reads): 0 -> 0 * * The firmware was writing and 582 bytes sat unread in the buffer; the * probe had simply stopped draining it. Reconnecting the telnet client does * not help — that is downstream of the collector, not the collector itself. * * `SetRTTAddr` is the documented way back in: "In some cases J-Link cannot * locate the RTT buffer in known RAM. This command is used to set the exact * address manually." Issuing it restarts collection at that address. * * https://kb.segger.com/J-Link_Command_Strings * * Requires knowing the address, which J-Link found for itself and does not * report back. Without one, say so rather than leaving a caller believing a * silent stream is a quiet target. */ restartRTT(): Promise<{ ok: boolean; detail: string; }>; dispose(): void; } //# sourceMappingURL=jlink.d.ts.map