mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-10-20 18:52:23 +00:00
Add standaolne module gdb-gateway
This commit is contained in:
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
|
||||
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
|
||||
|
||||
## [unreleased][unreleased]
|
||||
- Added standalone mode `GDB_GATEWAY` (PM5): GDB Remote Serial Protocol server on the USB CDC link, flash and SRAM access, no probe needed (@nieldk)
|
||||
- Fixed `lf t55xx detect -p` - the password is no longer sent to a tag whose PWD bit could not be confirmed, which could set it and destroy the tag's data; pass `-o` to override (@mfcarroll)
|
||||
- Fixed `hf mf hardnested`/`hf mf autopwn` on PM5 with hardened MIFARE Classic EV1 (#3595, #3664) (@ByteOrderMarc)
|
||||
- Added PM5 FPGA open source (@xianglin1998)
|
||||
|
||||
@@ -24,6 +24,7 @@ PLATFORM=PM3RDV4
|
||||
#PLATFORM_EXTRAS=BWM
|
||||
#PLATFORM_EXTRAS=NO_LOWBATT_SHUTDOWN
|
||||
#PLATFORM_EXTRAS=NO_LOWBATT_BEEP
|
||||
#STANDALONE=GDB_GATEWAY
|
||||
|
||||
# If you want more than one PLATFORM_EXTRAS option, separate them by spaces:
|
||||
#PLATFORM_EXTRAS=BTADDON
|
||||
|
||||
@@ -149,13 +149,16 @@ define KNOWN_STANDALONE_DEFINITIONS
|
||||
| DANKARMULTI | Load multiple standalone modes. |
|
||||
| | - Daniel Karling |
|
||||
+-----------------------------------------------------------+
|
||||
| GDB_GATEWAY | Read-only GDB server over USB CDC |
|
||||
| | (PM5) |
|
||||
+-----------------------------------------------------------+
|
||||
endef
|
||||
|
||||
|
||||
STANDALONE_MODES := LF_SKELETON
|
||||
STANDALONE_MODES += LF_EM4100EMUL LF_EM4100RSWB LF_EM4100RSWW LF_EM4100RWC LF_HIDBRUTE LF_HIDFCBRUTE LF_ICEHID LF_MULTIHID LF_NEDAP_SIM LF_NEXID LF_PROXBRUTE LF_PROX2BRUTE LF_SAMYRUN LF_THAREXDE
|
||||
STANDALONE_MODES += HF_14ASNIFF HF_14BSNIFF HF_15SIM HF_15SNIFF HF_AVEFUL HF_BOG HF_CARDHOPPER HF_COLIN HF_CRAFTBYTE HF_DOEGOX_AUTH0 HF_DOEGOX_COMMIT HF_EMVPNG HF_ICECLASS HF_LEGIC HF_LEGIC_RDV4 HF_LEGICSIM HF_MATTYRUN HF_MFCSIM HF_MSDSAL HF_REBLAY HF_ST25_TEAROFF HF_TCPRST HF_TMUDFORD HF_UNISNIFF HF_YOUNG
|
||||
STANDALONE_MODES += DANKARMULTI
|
||||
STANDALONE_MODES += DANKARMULTI GDB_GATEWAY
|
||||
STANDALONE_MODES_REQ_BT := HF_CARDHOPPER HF_REBLAY
|
||||
STANDALONE_MODES_REQ_SMARTCARD :=
|
||||
STANDALONE_MODES_REQ_FLASH := LF_HIDFCBRUTE LF_ICEHID LF_NEXID LF_THAREXDE HF_BOG HF_COLIN HF_ICECLASS HF_LEGIC_RDV4 HF_LEGICSIM HF_MFCSIM HF_ST25_TEAROFF
|
||||
|
||||
@@ -89,6 +89,10 @@ endif
|
||||
ifneq (,$(findstring WITH_STANDALONE_HF_15SNIFF,$(APP_CFLAGS)))
|
||||
SRC_STANDALONE = hf_15sniff.c
|
||||
endif
|
||||
# WITH_STANDALONE_GDB_GATEWAY
|
||||
ifneq (,$(findstring WITH_STANDALONE_GDB_GATEWAY,$(APP_CFLAGS)))
|
||||
SRC_STANDALONE = gdb_gateway.c
|
||||
endif
|
||||
# WITH_STANDALONE_HF_15SIM
|
||||
ifneq (,$(findstring WITH_STANDALONE_HF_15SIM,$(APP_CFLAGS)))
|
||||
SRC_STANDALONE = hf_15sim.c
|
||||
|
||||
@@ -118,6 +118,9 @@
|
||||
| HF_EMVPNG | Read and emulate EMV Visa cards |
|
||||
| | - Davi Mikael (Penegui) |
|
||||
+----------------------------------------------------------+
|
||||
| GDB_GATEWAY | Read-only GDB server over USB CDC |
|
||||
| | (PM5) |
|
||||
+----------------------------------------------------------+
|
||||
]]
|
||||
|
||||
# Default standalone if no standalone specified
|
||||
@@ -137,7 +140,7 @@ set(STANDALONE_MODES
|
||||
HF_14ASNIFF HF_14BSNIFF HF_15SNIFF HF_15SIM
|
||||
HF_AVEFUL HF_BOG HF_CARDHOPPER HF_COLIN HF_CRAFTBYTE HF_ICECLASS
|
||||
HF_LEGIC HF_LEGIC_RDV4 HF_LEGICSIM HF_MATTYRUN HF_MFCSIM HF_MSDSAL HF_REBLAY
|
||||
HF_ST25_TEAROFF HF_TCPRST HF_TMUDFORD HF_UNISNIFF HF_YOUNG HF_EMVPNG DANKARMULTI)
|
||||
HF_ST25_TEAROFF HF_TCPRST HF_TMUDFORD HF_UNISNIFF HF_YOUNG HF_EMVPNG DANKARMULTI GDB_GATEWAY)
|
||||
# List of modes that require bluetooth
|
||||
set(STANDALONE_MODES_REQ_BT HF_CARDHOPPER HF_REBLAY)
|
||||
# List of modes that require smartcard
|
||||
|
||||
@@ -76,6 +76,10 @@ if ("${APP_CFLAGS}" MATCHES "WITH_STANDALONE_HF_15SNIFF")
|
||||
set(SRC_STANDALONE "${DIR_STANDALONE}/hf_15sniff.c")
|
||||
endif ()
|
||||
|
||||
if ("${APP_CFLAGS}" MATCHES "WITH_STANDALONE_GDB_GATEWAY")
|
||||
set(SRC_STANDALONE "${DIR_STANDALONE}/gdb_gateway.c")
|
||||
endif ()
|
||||
|
||||
if ("${APP_CFLAGS}" MATCHES "WITH_STANDALONE_HF_15SIM")
|
||||
set(SRC_STANDALONE "${DIR_STANDALONE}/hf_15sim.c")
|
||||
endif ()
|
||||
|
||||
@@ -0,0 +1,552 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// See LICENSE.txt for the text of the license.
|
||||
//-----------------------------------------------------------------------------
|
||||
// Standalone GDB gateway: GDB Remote Serial Protocol over the USB CDC link.
|
||||
// Reads flash and SRAM, writes SRAM and flash (gdb 'load'), no execution control.
|
||||
// Flash writes are limited to the space above the running image; the bootrom is
|
||||
// never writable. 'monitor unlock-app' opens the app area, 'monitor lock' closes it.
|
||||
// Start with 'hw standalone', quit the client, then:
|
||||
// arm-none-eabi-gdb -ex 'target remote /dev/ttyACM0'
|
||||
// Leave with 'monitor exit' or a button press.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
|
||||
#define GDB_PKT_MAX 0x400u // advertised PacketSize
|
||||
#define GDB_MEM_MAX ((GDB_PKT_MAX - 8u) / 2u)
|
||||
#define GDB_NREGS 17u // r0-r12, sp, lr, pc, xpsr
|
||||
|
||||
#define GW_FLASH_BASE 0x08000000u
|
||||
#define GW_FLASH_SIZE 0x00100000u
|
||||
#define GW_APP_BASE 0x08004000u // bootrom below this is never writable
|
||||
#define GW_SECTOR 0x800u // 1 MB part: 2 KB sectors
|
||||
#define GW_SRAM_BASE 0x20000000u
|
||||
#define GW_SRAM_SIZE 0x00080000u // 512K, set by Extend_SRAM() in the bootrom
|
||||
|
||||
typedef struct {
|
||||
void (*tx)(const uint8_t *data, size_t len);
|
||||
// Copy up to len bytes from addr; return bytes copied, 0 if addr is unreadable.
|
||||
size_t (*mem_read)(uint32_t addr, uint8_t *out, size_t len);
|
||||
// The engine checks the address window before calling these.
|
||||
bool (*ram_write)(uint32_t addr, const uint8_t *data, size_t len);
|
||||
bool (*flash_erase)(uint32_t sector_addr);
|
||||
bool (*flash_write)(uint32_t addr, const uint8_t *data, size_t len);
|
||||
uint32_t app_end; // first sector above the running image
|
||||
} gdb_ops_t;
|
||||
|
||||
static const gdb_ops_t *s_ops;
|
||||
|
||||
static const char TARGET_XML[] =
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">"
|
||||
"<target version=\"1.0\">"
|
||||
"<architecture>armv7e-m</architecture>"
|
||||
"<feature name=\"org.gnu.gdb.arm.m-profile\">"
|
||||
"<reg name=\"r0\" bitsize=\"32\"/><reg name=\"r1\" bitsize=\"32\"/>"
|
||||
"<reg name=\"r2\" bitsize=\"32\"/><reg name=\"r3\" bitsize=\"32\"/>"
|
||||
"<reg name=\"r4\" bitsize=\"32\"/><reg name=\"r5\" bitsize=\"32\"/>"
|
||||
"<reg name=\"r6\" bitsize=\"32\"/><reg name=\"r7\" bitsize=\"32\"/>"
|
||||
"<reg name=\"r8\" bitsize=\"32\"/><reg name=\"r9\" bitsize=\"32\"/>"
|
||||
"<reg name=\"r10\" bitsize=\"32\"/><reg name=\"r11\" bitsize=\"32\"/>"
|
||||
"<reg name=\"r12\" bitsize=\"32\"/>"
|
||||
"<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>"
|
||||
"<reg name=\"lr\" bitsize=\"32\"/>"
|
||||
"<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>"
|
||||
"<reg name=\"xpsr\" bitsize=\"32\" regnum=\"25\"/>"
|
||||
"</feature></target>";
|
||||
|
||||
static const char MEMORY_MAP_XML[] =
|
||||
"<?xml version=\"1.0\"?>"
|
||||
"<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB Memory Map V1.0//EN\""
|
||||
" \"http://sourceware.org/gdb/gdb-memory-map.dtd\">"
|
||||
"<memory-map>"
|
||||
"<memory type=\"flash\" start=\"0x08000000\" length=\"0x100000\">"
|
||||
"<property name=\"blocksize\">0x800</property></memory>"
|
||||
"<memory type=\"ram\" start=\"0x20000000\" length=\"0x80000\"/>"
|
||||
"</memory-map>";
|
||||
|
||||
static const char HEXD[] = "0123456789abcdef";
|
||||
|
||||
typedef enum { ST_IDLE, ST_DATA, ST_CS1, ST_CS2 } rx_state_t;
|
||||
|
||||
static rx_state_t s_state;
|
||||
static uint8_t s_rx[GDB_PKT_MAX + 1];
|
||||
static uint16_t s_rxlen;
|
||||
static uint8_t s_sum;
|
||||
static int s_cs_hi;
|
||||
static bool s_ovf;
|
||||
static bool s_ack_pending;
|
||||
static bool s_exit;
|
||||
static bool s_unlocked; // app area writable
|
||||
|
||||
static uint8_t s_out[GDB_PKT_MAX]; // reply payload
|
||||
static uint8_t s_frame[GDB_PKT_MAX + 5]; // '+' '$' payload '#' cs cs
|
||||
static uint16_t s_frame_len;
|
||||
static uint16_t s_frame_off; // start of the resendable part (after any ack)
|
||||
|
||||
static int hexval(uint8_t c) {
|
||||
if (c >= '0' && c <= '9') return c - '0';
|
||||
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool parse_hex(const char **p, const char *end, uint32_t *v) {
|
||||
uint32_t r = 0;
|
||||
int n = 0;
|
||||
while (*p < end && hexval((uint8_t) * *p) >= 0) {
|
||||
if (n++ >= 8) return false;
|
||||
r = (r << 4) | (uint32_t)hexval((uint8_t) * *p);
|
||||
(*p)++;
|
||||
}
|
||||
*v = r;
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
static bool starts_with(const char *s, size_t len, const char *pre) {
|
||||
size_t n = strlen(pre);
|
||||
return len >= n && memcmp(s, pre, n) == 0;
|
||||
}
|
||||
|
||||
static void send_payload(const uint8_t *pl, size_t len) {
|
||||
size_t j = 0;
|
||||
uint8_t cs = 0;
|
||||
if (len > GDB_PKT_MAX) len = GDB_PKT_MAX;
|
||||
if (s_ack_pending) {
|
||||
s_frame[j++] = '+';
|
||||
s_ack_pending = false;
|
||||
}
|
||||
s_frame_off = (uint16_t)j;
|
||||
s_frame[j++] = '$';
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
s_frame[j++] = pl[i];
|
||||
cs = (uint8_t)(cs + pl[i]);
|
||||
}
|
||||
s_frame[j++] = '#';
|
||||
s_frame[j++] = (uint8_t)HEXD[cs >> 4];
|
||||
s_frame[j++] = (uint8_t)HEXD[cs & 0xF];
|
||||
s_frame_len = (uint16_t)j;
|
||||
s_ops->tx(s_frame, j);
|
||||
}
|
||||
|
||||
static void send_str(const char *s) {
|
||||
send_payload((const uint8_t *)s, strlen(s));
|
||||
}
|
||||
|
||||
static void send_err(uint8_t e) {
|
||||
char b[4] = { 'E', HEXD[e >> 4], HEXD[e & 0xF], 0 };
|
||||
send_str(b);
|
||||
}
|
||||
|
||||
static bool flash_window_ok(uint32_t addr, uint32_t len) {
|
||||
uint32_t floor = s_unlocked ? GW_APP_BASE : s_ops->app_end;
|
||||
uint32_t end = addr + len;
|
||||
return len && addr >= floor && end > addr && end <= GW_FLASH_BASE + GW_FLASH_SIZE;
|
||||
}
|
||||
|
||||
static bool sram_ok(uint32_t addr, uint32_t len) {
|
||||
uint32_t end = addr + len;
|
||||
return len && addr >= GW_SRAM_BASE && end > addr && end <= GW_SRAM_BASE + GW_SRAM_SIZE;
|
||||
}
|
||||
|
||||
// Undo gdb binary escaping ('}' then byte ^ 0x20). Returns -1 on a dangling '}'.
|
||||
static int unescape(const uint8_t *in, size_t n, uint8_t *out) {
|
||||
size_t o = 0;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (in[i] == '}') {
|
||||
if (++i >= n) return -1;
|
||||
out[o++] = in[i] ^ 0x20;
|
||||
} else {
|
||||
out[o++] = in[i];
|
||||
}
|
||||
}
|
||||
return (int)o;
|
||||
}
|
||||
|
||||
// Serve a qXfer document slice for "OFF,LEN".
|
||||
static void send_qxfer(const char *doc, const char *p, const char *end) {
|
||||
uint32_t off, len;
|
||||
if (!parse_hex(&p, end, &off) || p >= end || *p++ != ',' || !parse_hex(&p, end, &len)) {
|
||||
send_err(0x16);
|
||||
return;
|
||||
}
|
||||
size_t total = strlen(doc);
|
||||
if (off >= total) {
|
||||
send_str("l");
|
||||
return;
|
||||
}
|
||||
size_t n = total - off;
|
||||
if (n > len) n = len;
|
||||
if (n > GDB_PKT_MAX - 1u) n = GDB_PKT_MAX - 1u;
|
||||
s_out[0] = (off + n < total) ? 'm' : 'l';
|
||||
memcpy(&s_out[1], doc + off, n);
|
||||
send_payload(s_out, n + 1);
|
||||
}
|
||||
|
||||
static void do_mem_read(const char *p, const char *end) {
|
||||
uint32_t addr, len;
|
||||
if (!parse_hex(&p, end, &addr) || p >= end || *p++ != ',' || !parse_hex(&p, end, &len)) {
|
||||
send_err(0x16);
|
||||
return;
|
||||
}
|
||||
if (len > GDB_MEM_MAX) len = GDB_MEM_MAX;
|
||||
uint8_t tmp[GDB_MEM_MAX];
|
||||
size_t n = len ? s_ops->mem_read(addr, tmp, len) : 0;
|
||||
if (len && n == 0) {
|
||||
send_err(0x14); // EFAULT
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
s_out[2 * i] = (uint8_t)HEXD[tmp[i] >> 4];
|
||||
s_out[2 * i + 1] = (uint8_t)HEXD[tmp[i] & 0xF];
|
||||
}
|
||||
send_payload(s_out, 2 * n);
|
||||
}
|
||||
|
||||
static void do_mem_write(const char *p, const char *end) {
|
||||
uint32_t addr, len;
|
||||
if (!parse_hex(&p, end, &addr) || p >= end || *p++ != ',' || !parse_hex(&p, end, &len) ||
|
||||
p >= end || *p++ != ':' || len > GDB_MEM_MAX || (size_t)(end - p) != 2 * (size_t)len) {
|
||||
send_err(0x16);
|
||||
return;
|
||||
}
|
||||
if (len == 0) {
|
||||
send_str("OK");
|
||||
return;
|
||||
}
|
||||
uint8_t tmp[GDB_MEM_MAX];
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
int hi = hexval((uint8_t)p[2 * i]), lo = hexval((uint8_t)p[2 * i + 1]);
|
||||
if (hi < 0 || lo < 0) {
|
||||
send_err(0x16);
|
||||
return;
|
||||
}
|
||||
tmp[i] = (uint8_t)((hi << 4) | lo);
|
||||
}
|
||||
if (!sram_ok(addr, len)) {
|
||||
send_err(0x02); // flash goes through vFlashWrite
|
||||
return;
|
||||
}
|
||||
send_str(s_ops->ram_write(addr, tmp, len) ? "OK" : "E01");
|
||||
}
|
||||
|
||||
static void do_vflash(const char *s, size_t len) {
|
||||
const char *end = s + len;
|
||||
uint32_t addr, n;
|
||||
if (starts_with(s, len, "vFlashErase:")) {
|
||||
const char *p = s + strlen("vFlashErase:");
|
||||
if (!parse_hex(&p, end, &addr) || p >= end || *p++ != ',' || !parse_hex(&p, end, &n) ||
|
||||
n == 0 || (addr % GW_SECTOR) || (n % GW_SECTOR)) {
|
||||
send_err(0x16);
|
||||
return;
|
||||
}
|
||||
if (!flash_window_ok(addr, n)) {
|
||||
send_err(0x02);
|
||||
return;
|
||||
}
|
||||
for (uint32_t a = addr; a < addr + n; a += GW_SECTOR) {
|
||||
if (!s_ops->flash_erase(a)) {
|
||||
send_err(0x01);
|
||||
return;
|
||||
}
|
||||
}
|
||||
send_str("OK");
|
||||
} else if (starts_with(s, len, "vFlashWrite:")) {
|
||||
const char *p = s + strlen("vFlashWrite:");
|
||||
if (!parse_hex(&p, end, &addr) || p >= end || *p++ != ':') {
|
||||
send_err(0x16);
|
||||
return;
|
||||
}
|
||||
uint8_t data[GDB_PKT_MAX];
|
||||
int dn = unescape((const uint8_t *)p, (size_t)(end - p), data);
|
||||
if (dn < 0) {
|
||||
send_err(0x16);
|
||||
return;
|
||||
}
|
||||
if (!flash_window_ok(addr, (uint32_t)dn)) {
|
||||
send_err(0x02);
|
||||
return;
|
||||
}
|
||||
send_str(dn == 0 || s_ops->flash_write(addr, data, (size_t)dn) ? "OK" : "E01");
|
||||
} else if (starts_with(s, len, "vFlashDone")) {
|
||||
send_str("OK");
|
||||
} else {
|
||||
send_str("");
|
||||
}
|
||||
}
|
||||
|
||||
// monitor <cmd>: reply is the hex-encoded console text.
|
||||
static void do_monitor(const char *p, const char *end) {
|
||||
char cmd[24] = {0};
|
||||
size_t n = 0;
|
||||
while (p + 1 < end && n < sizeof(cmd) - 1) {
|
||||
int hi = hexval((uint8_t)p[0]), lo = hexval((uint8_t)p[1]);
|
||||
if (hi < 0 || lo < 0) break;
|
||||
cmd[n++] = (char)((hi << 4) | lo);
|
||||
p += 2;
|
||||
}
|
||||
const char *txt;
|
||||
if (strcmp(cmd, "unlock-app") == 0) {
|
||||
s_unlocked = true;
|
||||
txt = "app area writable; erasing the running image crashes the PM5\n";
|
||||
} else if (strcmp(cmd, "lock") == 0) {
|
||||
s_unlocked = false;
|
||||
txt = "writes limited to flash above the running image\n";
|
||||
} else if (strcmp(cmd, "exit") == 0) {
|
||||
s_exit = true;
|
||||
txt = "leaving gdb gateway\n";
|
||||
} else if (strcmp(cmd, "info") == 0) {
|
||||
txt = s_unlocked ? "flash 0x08000000+1M sram 0x20000000+512K app-write on\n"
|
||||
: "flash 0x08000000+1M sram 0x20000000+512K app-write off\n";
|
||||
} else if (strcmp(cmd, "help") == 0) {
|
||||
txt = "monitor info | unlock-app | lock | exit\n";
|
||||
} else {
|
||||
txt = "unknown command\n";
|
||||
}
|
||||
size_t t = strlen(txt);
|
||||
for (size_t i = 0; i < t; i++) {
|
||||
s_out[2 * i] = (uint8_t)HEXD[(uint8_t)txt[i] >> 4];
|
||||
s_out[2 * i + 1] = (uint8_t)HEXD[(uint8_t)txt[i] & 0xF];
|
||||
}
|
||||
send_payload(s_out, 2 * t);
|
||||
}
|
||||
|
||||
static void do_query(const char *s, size_t len) {
|
||||
const char *end = s + len;
|
||||
if (starts_with(s, len, "qSupported")) {
|
||||
send_str("PacketSize=400;qXfer:features:read+;qXfer:memory-map:read+");
|
||||
} else if (starts_with(s, len, "qXfer:features:read:target.xml:")) {
|
||||
send_qxfer(TARGET_XML, s + strlen("qXfer:features:read:target.xml:"), end);
|
||||
} else if (starts_with(s, len, "qXfer:memory-map:read::")) {
|
||||
send_qxfer(MEMORY_MAP_XML, s + strlen("qXfer:memory-map:read::"), end);
|
||||
} else if (starts_with(s, len, "qRcmd,")) {
|
||||
do_monitor(s + 6, end);
|
||||
} else if (starts_with(s, len, "qAttached")) {
|
||||
send_str("1");
|
||||
} else if (starts_with(s, len, "qfThreadInfo")) {
|
||||
send_str("m1");
|
||||
} else if (starts_with(s, len, "qsThreadInfo")) {
|
||||
send_str("l");
|
||||
} else if (len == 2 && starts_with(s, len, "qC")) {
|
||||
send_str("QC1");
|
||||
} else {
|
||||
send_str("");
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_packet(const char *s, size_t len) {
|
||||
if (len == 0) {
|
||||
send_str("");
|
||||
return;
|
||||
}
|
||||
switch (s[0]) {
|
||||
case '?':
|
||||
case 'c': case 'C': case 's': case 'S': // no execution control
|
||||
send_str("S05");
|
||||
break;
|
||||
case 'g': // registers are not available
|
||||
memset(s_out, '0', GDB_NREGS * 8);
|
||||
send_payload(s_out, GDB_NREGS * 8);
|
||||
break;
|
||||
case 'p':
|
||||
memset(s_out, '0', 8);
|
||||
send_payload(s_out, 8);
|
||||
break;
|
||||
case 'H': case 'T': case 'D':
|
||||
send_str("OK");
|
||||
break;
|
||||
case 'm':
|
||||
do_mem_read(s + 1, s + len);
|
||||
break;
|
||||
case 'M':
|
||||
do_mem_write(s + 1, s + len);
|
||||
break;
|
||||
case 'G': case 'P': // registers are read-only
|
||||
send_err(0x01);
|
||||
break;
|
||||
case 'v':
|
||||
do_vflash(s, len);
|
||||
break;
|
||||
case 'q':
|
||||
do_query(s, len);
|
||||
break;
|
||||
default: // X, Z, z, unknown
|
||||
send_str("");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void gdb_reset(const gdb_ops_t *ops) {
|
||||
s_ops = ops;
|
||||
s_state = ST_IDLE;
|
||||
s_rxlen = 0;
|
||||
s_ack_pending = false;
|
||||
s_exit = false;
|
||||
s_unlocked = false;
|
||||
s_frame_len = 0;
|
||||
s_frame_off = 0;
|
||||
}
|
||||
|
||||
static void gdb_feed(uint8_t b) {
|
||||
switch (s_state) {
|
||||
case ST_IDLE:
|
||||
if (b == '$') {
|
||||
s_rxlen = 0;
|
||||
s_sum = 0;
|
||||
s_ovf = false;
|
||||
s_state = ST_DATA;
|
||||
} else if (b == '-' && s_frame_len) {
|
||||
s_ops->tx(s_frame + s_frame_off, s_frame_len - s_frame_off); // resend
|
||||
} else if (b == 0x03) {
|
||||
send_str("S05"); // Ctrl-C, nothing to stop
|
||||
}
|
||||
break;
|
||||
case ST_DATA:
|
||||
if (b == '#') {
|
||||
s_state = ST_CS1;
|
||||
} else if (b == '$') {
|
||||
s_rxlen = 0;
|
||||
s_sum = 0;
|
||||
s_ovf = false;
|
||||
} else {
|
||||
if (s_rxlen < GDB_PKT_MAX) s_rx[s_rxlen++] = b;
|
||||
else s_ovf = true;
|
||||
s_sum = (uint8_t)(s_sum + b);
|
||||
}
|
||||
break;
|
||||
case ST_CS1:
|
||||
s_cs_hi = hexval(b);
|
||||
s_state = ST_CS2;
|
||||
break;
|
||||
case ST_CS2: {
|
||||
int lo = hexval(b);
|
||||
s_state = ST_IDLE;
|
||||
if (s_ovf || s_cs_hi < 0 || lo < 0 || (uint8_t)((s_cs_hi << 4) | lo) != s_sum) {
|
||||
s_ops->tx((const uint8_t *)"-", 1);
|
||||
} else {
|
||||
s_ack_pending = true;
|
||||
s_rx[s_rxlen] = 0;
|
||||
handle_packet((const char *)s_rx, s_rxlen);
|
||||
if (s_ack_pending) { // handler sent no reply
|
||||
s_ops->tx((const uint8_t *)"+", 1);
|
||||
s_ack_pending = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef GDB_GATEWAY_HOST_TEST
|
||||
|
||||
#include "appmain.h"
|
||||
#include "dbprint.h"
|
||||
#include "proxmark3_arm.h"
|
||||
#include "standalone.h"
|
||||
#include "ticks_apis.h"
|
||||
#include "util.h"
|
||||
#include "usb_cdc_apis.h"
|
||||
#include "usb_read_ng.h"
|
||||
#include "at32f435_437_flash.h"
|
||||
|
||||
extern const char __os_size__[]; // linker: size of the OS image
|
||||
|
||||
static void usb_tx(const uint8_t *data, size_t len) {
|
||||
usb_write(data, len);
|
||||
}
|
||||
|
||||
static size_t at32_mem_read(uint32_t addr, uint8_t *out, size_t len) {
|
||||
uint32_t end;
|
||||
if (addr - GW_FLASH_BASE < GW_FLASH_SIZE) {
|
||||
end = GW_FLASH_BASE + GW_FLASH_SIZE;
|
||||
} else if (addr - GW_SRAM_BASE < GW_SRAM_SIZE) {
|
||||
end = GW_SRAM_BASE + GW_SRAM_SIZE;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
if (len > end - addr) len = end - addr;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
out[i] = *(const volatile uint8_t *)(addr + i);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static bool at32_ram_write(uint32_t addr, const uint8_t *data, size_t len) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
*(volatile uint8_t *)(addr + i) = data[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool at32_flash_erase(uint32_t addr) {
|
||||
WDT_HIT();
|
||||
flash_unlock();
|
||||
flash_status_type st = flash_sector_erase(addr);
|
||||
flash_lock();
|
||||
return st == FLASH_OPERATE_DONE;
|
||||
}
|
||||
|
||||
// Words are merged with the current flash content, so partial words work
|
||||
// on erased sectors. Read back to verify.
|
||||
static bool at32_flash_write(uint32_t addr, const uint8_t *data, size_t len) {
|
||||
bool ok = true;
|
||||
uint32_t end = addr + (uint32_t)len;
|
||||
flash_unlock();
|
||||
for (uint32_t w = addr & ~3u; w < end && ok; w += 4) {
|
||||
uint32_t v = *(volatile uint32_t *)w;
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
if (w + i >= addr && w + i < end) {
|
||||
v = (v & ~(0xFFu << (8 * i))) | ((uint32_t)data[w + i - addr] << (8 * i));
|
||||
}
|
||||
}
|
||||
if (v != *(volatile uint32_t *)w) {
|
||||
ok = flash_word_program(w, v) == FLASH_OPERATE_DONE && *(volatile uint32_t *)w == v;
|
||||
}
|
||||
}
|
||||
flash_lock();
|
||||
WDT_HIT();
|
||||
return ok;
|
||||
}
|
||||
|
||||
static gdb_ops_t s_at32_ops = { usb_tx, at32_mem_read, at32_ram_write, at32_flash_erase, at32_flash_write, 0 };
|
||||
|
||||
void ModInfo(void) {
|
||||
DbpString(" GDB gateway, RSP over USB CDC");
|
||||
}
|
||||
|
||||
// No Dbprintf here: debug frames would corrupt the RSP stream.
|
||||
void RunMod(void) {
|
||||
s_at32_ops.app_end = (GW_APP_BASE + (uint32_t)(uintptr_t)__os_size__ + GW_SECTOR - 1) & ~(GW_SECTOR - 1);
|
||||
gdb_reset(&s_at32_ops);
|
||||
LED_A_ON();
|
||||
while (!s_exit && !BUTTON_PRESS()) {
|
||||
WDT_HIT();
|
||||
while (usb_read_ng_has_buffered_data() || usb_poll_validate_length()) {
|
||||
uint8_t b;
|
||||
if (usb_read_ng(&b, 1) != 1) break;
|
||||
gdb_feed(b);
|
||||
}
|
||||
}
|
||||
LED_A_OFF();
|
||||
while (BUTTON_PRESS()) { // keep the main loop from reading the press as power-off
|
||||
WDT_HIT();
|
||||
SpinDelay(10);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GDB_GATEWAY_HOST_TEST
|
||||
@@ -81,6 +81,7 @@ As such the information inside may be false or hallucinated.
|
||||
| Mode ID | Document | Description |
|
||||
|---------|----------|-------------|
|
||||
| DANKARMULTI | [Dankarmulti Loader](../../doc/standalone/dankarmulti.md) | Combine multiple standalone modes into one firmware image |
|
||||
| GDB_GATEWAY | [GDB Gateway](../../doc/standalone/gdb_gateway.md) | Read-only GDB server over USB CDC | PM5 |
|
||||
|
||||
# Developing Standalone Modes
|
||||
This contains functionality for different StandAlone modes. The fullimage will be built given the correct compiler flags used. Build targets for these files are contained in `Makefile.inc` and `Makefile.hal`
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# GDB_GATEWAY
|
||||
|
||||
GDB Remote Serial Protocol server on the USB CDC link (PM5).
|
||||
No probe or extra wiring. Reads flash and SRAM, writes SRAM and flash. No breakpoints, no step.
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
PLATFORM=PM5
|
||||
STANDALONE=GDB_GATEWAY
|
||||
```
|
||||
|
||||
## Use
|
||||
|
||||
```
|
||||
pm3 --> hw standalone
|
||||
```
|
||||
|
||||
Quit the client (the port stays up), then:
|
||||
|
||||
```
|
||||
arm-none-eabi-gdb -ex 'target remote /dev/ttyACM0'
|
||||
(gdb) x/16xw 0x08000000
|
||||
(gdb) dump memory flash.bin 0x08000000 0x08100000
|
||||
(gdb) monitor info
|
||||
(gdb) load patch.elf # flash above the running image
|
||||
(gdb) monitor unlock-app # allow writes to the app area
|
||||
(gdb) monitor lock
|
||||
(gdb) monitor exit
|
||||
```
|
||||
|
||||
Leave with `monitor exit` or a button press.
|
||||
|
||||
## Readable ranges
|
||||
|
||||
| Range | Size |
|
||||
|-------|------|
|
||||
| `0x08000000` | 1 MB flash |
|
||||
| `0x20000000` | 512 KB SRAM |
|
||||
|
||||
Other addresses return `E14`. Peripheral space is not exposed.
|
||||
|
||||
## Writes
|
||||
|
||||
| Target | Allowed |
|
||||
|--------|---------|
|
||||
| SRAM (`M`) | always |
|
||||
| Flash above the running image (`load`, `vFlashErase`/`vFlashWrite`) | always |
|
||||
| Flash from `0x08004000` (app area) | after `monitor unlock-app` |
|
||||
| Bootrom, `0x08000000`-`0x08003fff` | never |
|
||||
|
||||
Refused writes return `E02`. `monitor lock` restores the default; the gateway starts locked.
|
||||
Erasing the running image crashes the PM5, recover it through the bootrom.
|
||||
|
||||
## Notes
|
||||
|
||||
- Flash is erased in 2 KB sectors.
|
||||
- Register reads return zeros, the core is not halted.
|
||||
- `c`/`s` return an immediate stop reply; nothing is executed.
|
||||
- Debug output is off while running, it would corrupt the RSP stream.
|
||||
- Set `remotetimeout 60` in gdb for large dumps.
|
||||
Reference in New Issue
Block a user