Files
homeclaw/web-ui/vendor/rp2040js/gdb/gdb-server.js
T
kim 209f3a3aef v3.0.6: RP2040 에뮬레이터 개선 — USB CDC, vendor화, 부팅 감지
- USB CDC 경로 복원: #define Serial Serial1 우회 제거
- rp2040js/avr8js/codemirror/xterm/monaco-editor vendor화
- /vendor 정적 라우팅 추가 (Express 5 catch-all 우회)
- 부팅 완료 타임아웃 감지 (Serial 출력 없어도 8초 후 RAF 전환)
- 리셋 시 Simulator 재생성으로 주변 장치 상태 초기화
- 시리얼 패널의 내부 메시지를 console.log로 이동
2026-06-19 15:49:36 +09:00

261 lines
13 KiB
JavaScript

/**
* RP2040 GDB Server
*
* Copyright (C) 2021, Uri Shaked
*/
import { SYSM_CONTROL, SYSM_MSP, SYSM_PRIMASK, SYSM_PSP } from '../cortex-m0-core.js';
import { ConsoleLogger, LogLevel } from '../utils/logging.js';
import { decodeHexBuf, encodeHexBuf, encodeHexByte, encodeHexUint32, gdbMessage, } from './gdb-utils.js';
export const STOP_REPLY_SIGINT = 'S02';
export const STOP_REPLY_TRAP = 'S05';
/* string value: armv6m-none-unknown-eabi */
const lldbTriple = '61726d76366d2d6e6f6e652d756e6b6e6f776e2d65616269';
const registers = [
`name:r0;bitsize:32;offset:0;encoding:int;format:hex;set:General Purpose Registers;generic:arg1;gcc:0;dwarf:0;`,
`name:r1;bitsize:32;offset:4;encoding:int;format:hex;set:General Purpose Registers;generic:arg2;gcc:1;dwarf:1;`,
`name:r2;bitsize:32;offset:8;encoding:int;format:hex;set:General Purpose Registers;generic:arg3;gcc:2;dwarf:2;`,
`name:r3;bitsize:32;offset:12;encoding:int;format:hex;set:General Purpose Registers;generic:arg4;gcc:3;dwarf:3;`,
`name:r4;bitsize:32;offset:16;encoding:int;format:hex;set:General Purpose Registers;gcc:4;dwarf:4;`,
`name:r5;bitsize:32;offset:20;encoding:int;format:hex;set:General Purpose Registers;gcc:5;dwarf:5;`,
`name:r6;bitsize:32;offset:24;encoding:int;format:hex;set:General Purpose Registers;gcc:6;dwarf:6;`,
`name:r7;bitsize:32;offset:28;encoding:int;format:hex;set:General Purpose Registers;gcc:7;dwarf:7;`,
`name:r8;bitsize:32;offset:32;encoding:int;format:hex;set:General Purpose Registers;gcc:8;dwarf:8;`,
`name:r9;bitsize:32;offset:36;encoding:int;format:hex;set:General Purpose Registers;gcc:9;dwarf:9;`,
`name:r10;bitsize:32;offset:40;encoding:int;format:hex;set:General Purpose Registers;gcc:10;dwarf:10;`,
`name:r11;bitsize:32;offset:44;encoding:int;format:hex;set:General Purpose Registers;generic:fp;gcc:11;dwarf:11;`,
`name:r12;bitsize:32;offset:48;encoding:int;format:hex;set:General Purpose Registers;gcc:12;dwarf:12;`,
`name:sp;bitsize:32;offset:52;encoding:int;format:hex;set:General Purpose Registers;generic:sp;alt-name:r13;gcc:13;dwarf:13;`,
`name:lr;bitsize:32;offset:56;encoding:int;format:hex;set:General Purpose Registers;generic:ra;alt-name:r14;gcc:14;dwarf:14;`,
`name:pc;bitsize:32;offset:60;encoding:int;format:hex;set:General Purpose Registers;generic:pc;alt-name:r15;gcc:15;dwarf:15;`,
`name:cpsr;bitsize:32;offset:64;encoding:int;format:hex;set:General Purpose Registers;generic:flags;alt-name:psr;gcc:16;dwarf:16;`,
];
const targetXML = `<?xml version="1.0"?>
<!DOCTYPE target SYSTEM "gdb-target.dtd">
<target version="1.0">
<architecture>arm</architecture>
<feature name="org.gnu.gdb.arm.m-profile">
<reg name="r0" bitsize="32" regnum="0" save-restore="yes" type="int" group="general"/>
<reg name="r1" bitsize="32" regnum="1" save-restore="yes" type="int" group="general"/>
<reg name="r2" bitsize="32" regnum="2" save-restore="yes" type="int" group="general"/>
<reg name="r3" bitsize="32" regnum="3" save-restore="yes" type="int" group="general"/>
<reg name="r4" bitsize="32" regnum="4" save-restore="yes" type="int" group="general"/>
<reg name="r5" bitsize="32" regnum="5" save-restore="yes" type="int" group="general"/>
<reg name="r6" bitsize="32" regnum="6" save-restore="yes" type="int" group="general"/>
<reg name="r7" bitsize="32" regnum="7" save-restore="yes" type="int" group="general"/>
<reg name="r8" bitsize="32" regnum="8" save-restore="yes" type="int" group="general"/>
<reg name="r9" bitsize="32" regnum="9" save-restore="yes" type="int" group="general"/>
<reg name="r10" bitsize="32" regnum="10" save-restore="yes" type="int" group="general"/>
<reg name="r11" bitsize="32" regnum="11" save-restore="yes" type="int" group="general"/>
<reg name="r12" bitsize="32" regnum="12" save-restore="yes" type="int" group="general"/>
<reg name="sp" bitsize="32" regnum="13" save-restore="yes" type="data_ptr" group="general"/>
<reg name="lr" bitsize="32" regnum="14" save-restore="yes" type="int" group="general"/>
<reg name="pc" bitsize="32" regnum="15" save-restore="yes" type="code_ptr" group="general"/>
<reg name="xPSR" bitsize="32" regnum="16" save-restore="yes" type="int" group="general"/>
</feature>
<feature name="org.gnu.gdb.arm.m-system">
<reg name="msp" bitsize="32" regnum="17" save-restore="yes" type="data_ptr" group="system"/>
<reg name="psp" bitsize="32" regnum="18" save-restore="yes" type="data_ptr" group="system"/>
<reg name="primask" bitsize="1" regnum="19" save-restore="yes" type="int8" group="system"/>
<reg name="basepri" bitsize="8" regnum="20" save-restore="yes" type="int8" group="system"/>
<reg name="faultmask" bitsize="1" regnum="21" save-restore="yes" type="int8" group="system"/>
<reg name="control" bitsize="2" regnum="22" save-restore="yes" type="int8" group="system"/>
</feature>
</target>`;
const LOG_NAME = 'GDBServer';
export class GDBServer {
constructor(target) {
this.target = target;
this.logger = new ConsoleLogger(LogLevel.Warn, true);
this.connections = new Set();
}
processGDBMessage(cmd) {
const { rp2040 } = this.target;
const { core } = rp2040;
if (cmd === 'Hg0') {
return gdbMessage('OK');
}
switch (cmd[0]) {
case '?':
return gdbMessage(STOP_REPLY_TRAP);
case 'q':
// Query things
if (cmd.startsWith('qSupported:')) {
return gdbMessage('PacketSize=4000;vContSupported+;qXfer:features:read+');
}
if (cmd === 'qAttached') {
return gdbMessage('1');
}
if (cmd.startsWith('qXfer:features:read:target.xml')) {
return gdbMessage('l' + targetXML);
}
if (cmd.startsWith('qRegisterInfo')) {
const index = parseInt(cmd.substring(13), 16);
const register = registers[index];
if (register) {
return gdbMessage(register);
}
else {
return gdbMessage(`E45`);
}
}
if (cmd === 'qHostInfo') {
return gdbMessage(`triple:${lldbTriple};endian:little;ptrsize:4;`);
}
if (cmd === 'qProcessInfo') {
return gdbMessage('pid:1;endian:little;ptrsize:4;');
}
return gdbMessage('');
case 'v':
if (cmd === 'vCont?') {
return gdbMessage('vCont;c;C;s;S');
}
if (cmd.startsWith('vCont;c')) {
if (!this.target.executing) {
this.target.execute();
}
return;
}
if (cmd.startsWith('vCont;s')) {
rp2040.step();
const registerStatus = [];
for (let i = 0; i < 17; i++) {
const value = i === 16 ? core.xPSR : core.registers[i];
registerStatus.push(`${encodeHexByte(i)}:${encodeHexUint32(value)}`);
}
return gdbMessage(`T05${registerStatus.join(';')};reason:trace;`);
}
break;
case 'c':
if (!this.target.executing) {
this.target.execute();
}
return gdbMessage('OK');
case 'g': {
// Read registers
const buf = new Uint32Array(17);
buf.set(core.registers);
buf[16] = core.xPSR;
return gdbMessage(encodeHexBuf(new Uint8Array(buf.buffer)));
}
case 'p': {
// Read register
const registerIndex = parseInt(cmd.substr(1), 16);
if (registerIndex >= 0 && registerIndex <= 15) {
return gdbMessage(encodeHexUint32(core.registers[registerIndex]));
}
const specialRegister = (sysm) => gdbMessage(encodeHexUint32(core.readSpecialRegister(sysm)));
switch (registerIndex) {
case 0x10:
return gdbMessage(encodeHexUint32(core.xPSR));
case 0x11:
return specialRegister(SYSM_MSP);
case 0x12:
return specialRegister(SYSM_PSP);
case 0x13:
return specialRegister(SYSM_PRIMASK);
case 0x14:
this.logger.warn(LOG_NAME, 'TODO BASEPRI');
return gdbMessage(encodeHexUint32(0)); // TODO BASEPRI
case 0x15:
this.logger.warn(LOG_NAME, 'TODO faultmask');
return gdbMessage(encodeHexUint32(0)); // TODO faultmask
case 0x16:
return specialRegister(SYSM_CONTROL);
}
break;
}
case 'P': {
// Write register
const params = cmd.substring(1).split('=');
const registerIndex = parseInt(params[0], 16);
const registerValue = params[1].trim();
const registerBytes = registerIndex > 0x12 ? 1 : 4;
const decodedValue = decodeHexBuf(registerValue);
if (registerIndex < 0 || registerIndex > 0x16 || decodedValue.length !== registerBytes) {
return gdbMessage('E00');
}
const valueBuffer = new Uint8Array(4);
valueBuffer.set(decodedValue.slice(0, 4));
const value = new DataView(valueBuffer.buffer).getUint32(0, true);
switch (registerIndex) {
case 0x10:
core.xPSR = value;
break;
case 0x11:
core.writeSpecialRegister(SYSM_MSP, value);
break;
case 0x12:
core.writeSpecialRegister(SYSM_PSP, value);
break;
case 0x13:
core.writeSpecialRegister(SYSM_PRIMASK, value);
break;
case 0x14:
this.logger.warn(LOG_NAME, 'TODO BASEPRI');
break; // TODO BASEPRI
case 0x15:
this.logger.warn(LOG_NAME, 'TODO faultmask');
break; // TODO faultmask
case 0x16:
core.writeSpecialRegister(SYSM_CONTROL, value);
break;
default:
core.registers[registerIndex] = value;
break;
}
return gdbMessage('OK');
}
case 'm': {
// Read memory
const params = cmd.substr(1).split(',');
const address = parseInt(params[0], 16);
const length = parseInt(params[1], 16);
let result = '';
for (let i = 0; i < length; i++) {
result += encodeHexByte(rp2040.readUint8(address + i));
}
return gdbMessage(result);
}
case 'M': {
// Write memory
const params = cmd.substr(1).split(/[,:]/);
const address = parseInt(params[0], 16);
const length = parseInt(params[1], 16);
const data = decodeHexBuf(params[2].substr(0, length * 2));
for (let i = 0; i < data.length; i++) {
this.debug(`Write ${data[i].toString(16)} to ${(address + i).toString(16)}`);
rp2040.writeUint8(address + i, data[i]);
}
return gdbMessage('OK');
}
}
return gdbMessage('');
}
addConnection(connection) {
const { rp2040 } = this.target;
this.connections.add(connection);
rp2040.onBreak = () => {
this.target.stop();
rp2040.core.PC -= rp2040.core.breakRewind;
for (const connection of this.connections) {
connection.onBreakpoint();
}
};
}
removeConnection(connection) {
this.connections.delete(connection);
}
debug(msg) {
this.logger.debug(LOG_NAME, msg);
}
info(msg) {
this.logger.info(LOG_NAME, msg);
}
warn(msg) {
this.logger.warn(LOG_NAME, msg);
}
error(msg) {
this.logger.error(LOG_NAME, msg);
}
}