116 lines
3.6 KiB
Python
116 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Dump K1 persisted W25Q field log via J-Link RTT (`log flash dump` / `log flash scan`)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import time
|
|
from datetime import datetime
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from rtt_log_dump import ( # noqa: E402
|
|
connect_jlink,
|
|
find_rtt_control_block,
|
|
import_deps,
|
|
send_command,
|
|
wait_rtt_ready,
|
|
)
|
|
|
|
DUMP_CMD = b"log flash dump\n"
|
|
SCAN_CMD = b"log flash scan 65536\n"
|
|
|
|
|
|
def dump_flash_log(out_path: str, device: str, timeout_s: float, cmd: bytes = DUMP_CMD) -> None:
|
|
jlink = connect_jlink(device)
|
|
lines: list[str] = []
|
|
try:
|
|
cb = find_rtt_control_block(jlink)
|
|
if cb is None:
|
|
raise SystemExit("SEGGER RTT control block not found in SRAM")
|
|
print(f"RTT CB @ 0x{cb:08X}")
|
|
|
|
jlink.rtt_start(cb)
|
|
wait_rtt_ready(jlink)
|
|
|
|
for _ in range(5):
|
|
stale = jlink.rtt_read(0, 4096)
|
|
if stale:
|
|
text = bytes(stale).decode("utf-8", errors="replace")
|
|
if text.strip():
|
|
print("RTT0 stale:", text.strip()[:200])
|
|
time.sleep(0.05)
|
|
|
|
print(f"Sending: {cmd!r}")
|
|
send_command(jlink, cmd)
|
|
|
|
buffer = b""
|
|
deadline = time.time() + timeout_s
|
|
while time.time() < deadline:
|
|
chunk = jlink.rtt_read(0, 8192)
|
|
if chunk:
|
|
buffer += bytes(chunk)
|
|
if len(buffer) >= 4096 and (len(buffer) % 8192) < 300:
|
|
print(f" received {len(buffer)} bytes...")
|
|
if b"LOG_FLASH_DUMP_END" in buffer:
|
|
break
|
|
else:
|
|
time.sleep(0.01)
|
|
|
|
if b"LOG_FLASH_DUMP_END" not in buffer:
|
|
preview = buffer[-500:].decode("utf-8", errors="replace") if buffer else ""
|
|
raise SystemExit(
|
|
"Timeout waiting for LOG_FLASH_DUMP_END.\n"
|
|
f"got {len(buffer)} bytes. tail={preview!r}"
|
|
)
|
|
|
|
text = buffer.decode("utf-8", errors="replace")
|
|
capture = False
|
|
for line in text.splitlines():
|
|
if line.strip() == "LOG_FLASH_DUMP_BEGIN":
|
|
capture = True
|
|
continue
|
|
if line.strip() == "LOG_FLASH_DUMP_END":
|
|
break
|
|
if capture:
|
|
lines.append(line)
|
|
|
|
header = (
|
|
f"# K1 flash log dump {datetime.now().isoformat(timespec='seconds')}\n"
|
|
f"# device={device}\n"
|
|
f"# cmd={cmd.decode('ascii', errors='replace').strip()}\n"
|
|
)
|
|
body = "\n".join(lines) + ("\n" if lines else "")
|
|
with open(out_path, "w", encoding="utf-8", newline="\n") as f:
|
|
f.write(header)
|
|
f.write(body)
|
|
print(f"Saved {len(lines)} lines to {out_path}")
|
|
print(f"Full path: {os.path.abspath(out_path)}")
|
|
finally:
|
|
try:
|
|
jlink.rtt_stop()
|
|
except Exception:
|
|
pass
|
|
jlink.close()
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Dump K1 Flash field log via RTT")
|
|
parser.add_argument("--out", default="crash.log")
|
|
parser.add_argument("--device", default="AT32F403AC")
|
|
parser.add_argument("--timeout", type=float, default=300.0)
|
|
parser.add_argument(
|
|
"--scan",
|
|
action="store_true",
|
|
help="Ignore write pointer; scan first 64KB data for recoverable lines",
|
|
)
|
|
args = parser.parse_args()
|
|
import_deps()
|
|
cmd = SCAN_CMD if args.scan else DUMP_CMD
|
|
dump_flash_log(args.out, args.device, args.timeout, cmd=cmd)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|