mirror of
https://github.com/radareorg/radare2.git
synced 2024-10-07 02:23:58 +00:00
io: Add gprobe plugin (#9735)
GProbe is a protocol to communicate with various parts from Genesis/STMicro/MegaChips that are mostly used in video chipsets. Signed-off-by: Dirk Eibach <dirk.eibach@gdsys.cc>
This commit is contained in:
parent
f1eb03f79b
commit
fe3c10fc22
51
doc/gprobe.md
Normal file
51
doc/gprobe.md
Normal file
@ -0,0 +1,51 @@
|
||||
What is GProbe?
|
||||
===============
|
||||
GProbe is a protocol to communicate with various parts from
|
||||
Genesis/STMicro/MegaChips that are mostly used in video chipsets.
|
||||
|
||||
These chips have an integrated Turbo186 core. With GProbe you can read and write
|
||||
RAM, reset the CPU, execute code in RAM, ...
|
||||
There is a proprietary Windows tool to do this, but radare2 offers much more
|
||||
functionality.
|
||||
|
||||
Gprobe got some public attention with the
|
||||
[MonitorDarkly exploit](https://github.com/RedBalloonShenanigans/MonitorDarkly).
|
||||
|
||||
What is implemented?
|
||||
--------------------
|
||||
- Serial1 protocol wrapper
|
||||
- RAM read-/write-access
|
||||
- Reset
|
||||
- DebugOn/DebugOff
|
||||
- RunCode
|
||||
- GetDeviceId
|
||||
|
||||
TODOs
|
||||
-----
|
||||
- DDC2Bi3 and DisplayPort AUX Channel protocol wrappers
|
||||
- Flash commands
|
||||
|
||||
What is tested?
|
||||
---------------
|
||||
- building with sys/user.sh and sys/mingw32.sh on linux
|
||||
- running radare2 on Linux and Windows
|
||||
- communication via FTDI USB serial adaptor
|
||||
- controlling a MegaChips RD1-4320 DisplayPort 1.2a splitter reference board
|
||||
|
||||
How to use for dummies?
|
||||
-----------------------
|
||||
radare2 -n -w gprobe:///dev/ttyUSB0
|
||||
- "/dev/ttyUSB0" is the serial connection, use something like "COM3" on Windows
|
||||
- "-n" is important to avoid an initial 32k read to identify the binary type
|
||||
- "-w" if you want to allow writing to RAM
|
||||
|
||||
Setup for Turbo186 processor core:
|
||||
- e asm.bits=16
|
||||
- e asm.seggrn=8
|
||||
|
||||
Now enjoy all the great stuff that r2 offers, like:
|
||||
- run grobe commands with =!?
|
||||
- dump memory with px
|
||||
- Visual mode with V, including cursor mode and insert hexpairs
|
||||
- dumping segments to file
|
||||
- disassembly and analysis
|
@ -539,6 +539,7 @@ extern RIOPlugin r_io_plugin_null;
|
||||
extern RIOPlugin r_io_plugin_ar;
|
||||
extern RIOPlugin r_io_plugin_rbuf;
|
||||
extern RIOPlugin r_io_plugin_winedbg;
|
||||
extern RIOPlugin r_io_plugin_gprobe;
|
||||
|
||||
#if __cplusplus
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ files = [
|
||||
'p/io_debug.c',
|
||||
'p/io_default.c',
|
||||
'p/io_gdb.c',
|
||||
'p/io_gprobe.c',
|
||||
'p/io_gzip.c',
|
||||
'p/io_http.c',
|
||||
'p/io_ihex.c',
|
||||
|
@ -15,7 +15,7 @@ endif
|
||||
all: ${ALL_TARGETS}
|
||||
|
||||
ALL_TARGETS=
|
||||
PLUGINS=ptrace.mk debug.mk gdb.mk malloc.mk shm.mk mach.mk w32dbg.mk procpid.mk windbg.mk bochs.mk qnx.mk r2k.mk ar.mk rbuf.mk
|
||||
PLUGINS=ptrace.mk debug.mk gdb.mk malloc.mk shm.mk mach.mk w32dbg.mk procpid.mk windbg.mk bochs.mk qnx.mk r2k.mk ar.mk rbuf.mk gprobe.mk
|
||||
#zip.mk
|
||||
#PLUGINS=ptrace.mk debug.mk gdb.mk malloc.mk mach.mk w32dbg.mk procpid.mk
|
||||
include ${PLUGINS}
|
||||
|
16
libr/io/p/gprobe.mk
Normal file
16
libr/io/p/gprobe.mk
Normal file
@ -0,0 +1,16 @@
|
||||
OBJ_GPROBE=io_gprobe.o
|
||||
|
||||
STATIC_OBJ+=${OBJ_GPROBE}
|
||||
TARGET_GPROBE=io_gprobe.${EXT_SO}
|
||||
ALL_TARGETS+=${TARGET_GPROBE}
|
||||
|
||||
ifeq (${WITHPIC},0)
|
||||
LINKFLAGS+=../../util/libr_util.a
|
||||
LINKFLAGS+=../../io/libr_io.a
|
||||
else
|
||||
LINKFLAGS+=-L../../util -lr_util
|
||||
LINKFLAGS+=-L.. -lr_io
|
||||
endif
|
||||
|
||||
${TARGET_GPROBE}: ${OBJ_GPROBE}
|
||||
${CC_LIB} $(call libname,io_gprobe) ${CFLAGS} -o ${TARGET_GPROBE} ${OBJ_GPROBE} ${LINKFLAGS}
|
968
libr/io/p/io_gprobe.c
Normal file
968
libr/io/p/io_gprobe.c
Normal file
@ -0,0 +1,968 @@
|
||||
/* radare - LGPL - Copyright 2018 - Dirk Eibach, Guntermann & Drunck GmbH */
|
||||
|
||||
#include <r_io.h>
|
||||
#include <r_lib.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if __WINDOWS__
|
||||
#include <cfgmgr32.h>
|
||||
#include <setupapi.h>
|
||||
#include <tchar.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <termios.h>
|
||||
#endif
|
||||
|
||||
#define GPROBE_SIZE (1LL << 32)
|
||||
|
||||
/* serial port code adapted from git://sigrok.org/libserialport */
|
||||
struct sp_port {
|
||||
const char *name;
|
||||
#if __WINDOWS__
|
||||
HANDLE hdl;
|
||||
COMMTIMEOUTS timeouts;
|
||||
OVERLAPPED write_ovl;
|
||||
OVERLAPPED read_ovl;
|
||||
OVERLAPPED wait_ovl;
|
||||
DWORD events;
|
||||
BYTE pending_byte;
|
||||
BOOL writing;
|
||||
BOOL wait_running;
|
||||
#else
|
||||
int fd;
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct sp_port sp_port;
|
||||
ut64 offset;
|
||||
} RIOGprobe;
|
||||
|
||||
enum {
|
||||
GPROBE_DEBUGON = 0x09,
|
||||
GPROBE_DEBUGOFF = 0x0a,
|
||||
GPROBE_ACK = 0x0c,
|
||||
GPROBE_RESET = 0x20,
|
||||
GPROBE_GET_DEVICE_ID = 0x30,
|
||||
GPROBE_RAM_READ_2 = 0x52,
|
||||
GPROBE_RAM_WRITE_2 = 0x53,
|
||||
GPROBE_RUN_CODE_2 = 0x54,
|
||||
};
|
||||
|
||||
static int sp_close (struct sp_port *port) {
|
||||
#if __WINDOWS__
|
||||
/* Returns non-zero upon success, 0 upon failure. */
|
||||
if (CloseHandle (port->hdl) == 0)
|
||||
return -1;
|
||||
port->hdl = INVALID_HANDLE_VALUE;
|
||||
|
||||
/* Close event handles for overlapped structures. */
|
||||
#define CLOSE_OVERLAPPED(ovl) \
|
||||
do { \
|
||||
if (port->ovl.hEvent != INVALID_HANDLE_VALUE && \
|
||||
CloseHandle (port->ovl.hEvent) == 0) \
|
||||
return -1; \
|
||||
} while (0)
|
||||
CLOSE_OVERLAPPED (read_ovl);
|
||||
CLOSE_OVERLAPPED (write_ovl);
|
||||
CLOSE_OVERLAPPED (wait_ovl);
|
||||
|
||||
#else
|
||||
if (close (port->fd) == -1)
|
||||
return -1;
|
||||
|
||||
port->fd = -1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if __WINDOWS__
|
||||
/* To be called after port receive buffer is emptied. */
|
||||
static int restart_wait (struct sp_port *port) {
|
||||
DWORD wait_result;
|
||||
|
||||
if (port->wait_running) {
|
||||
/* Check status of running wait operation. */
|
||||
if (GetOverlappedResult (port->hdl, &port->wait_ovl,
|
||||
&wait_result, FALSE)) {
|
||||
port->wait_running = FALSE;
|
||||
} else if (GetLastError () == ERROR_IO_INCOMPLETE) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!port->wait_running) {
|
||||
/* Start new wait operation. */
|
||||
if (WaitCommEvent (port->hdl, &port->events,
|
||||
&port->wait_ovl)) {
|
||||
} else if (GetLastError () == ERROR_IO_PENDING) {
|
||||
port->wait_running = TRUE;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int sp_open (struct sp_port *port) {
|
||||
#if __WINDOWS__
|
||||
int ret;
|
||||
DWORD errors;
|
||||
char *escaped_port_name;
|
||||
COMSTAT status;
|
||||
DCB dcb;
|
||||
LPTSTR filename_;
|
||||
|
||||
/* Prefix port name with '\\.\' to work with ports above COM9. */
|
||||
if (!(escaped_port_name = malloc (strlen (port->name) + 5)))
|
||||
return -1;
|
||||
sprintf (escaped_port_name, "\\\\.\\%s", port->name);
|
||||
|
||||
filename_ = r_sys_conv_utf8_to_utf16 (escaped_port_name);
|
||||
|
||||
port->hdl = CreateFile (filename_, GENERIC_READ | GENERIC_WRITE, 0, 0,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);
|
||||
|
||||
free (escaped_port_name);
|
||||
|
||||
if (port->hdl == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
/* All timeouts initially disabled. */
|
||||
port->timeouts.ReadIntervalTimeout = 0;
|
||||
port->timeouts.ReadTotalTimeoutMultiplier = 0;
|
||||
port->timeouts.ReadTotalTimeoutConstant = 0;
|
||||
port->timeouts.WriteTotalTimeoutMultiplier = 0;
|
||||
port->timeouts.WriteTotalTimeoutConstant = 0;
|
||||
|
||||
if (SetCommTimeouts (port->hdl, &port->timeouts) == 0) {
|
||||
sp_close (port);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Prepare OVERLAPPED structures. */
|
||||
#define INIT_OVERLAPPED(ovl) \
|
||||
do { \
|
||||
memset (&port->ovl, 0, sizeof (port->ovl)); \
|
||||
port->ovl.hEvent = INVALID_HANDLE_VALUE; \
|
||||
if ((port->ovl.hEvent = CreateEvent (NULL, TRUE, TRUE, NULL)) == INVALID_HANDLE_VALUE) { \
|
||||
sp_close (port); \
|
||||
return -1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
INIT_OVERLAPPED (read_ovl);
|
||||
INIT_OVERLAPPED (write_ovl);
|
||||
INIT_OVERLAPPED (wait_ovl);
|
||||
|
||||
/* Set event mask for RX and error events. */
|
||||
if (SetCommMask (port->hdl, EV_RXCHAR | EV_ERR) == 0) {
|
||||
sp_close (port);
|
||||
return -1;
|
||||
}
|
||||
|
||||
port->writing = FALSE;
|
||||
port->wait_running = FALSE;
|
||||
|
||||
ret = restart_wait (port);
|
||||
|
||||
if (ret < 0) {
|
||||
sp_close (port);
|
||||
return -1;
|
||||
}
|
||||
|
||||
dcb.fBinary = TRUE;
|
||||
dcb.fDsrSensitivity = FALSE;
|
||||
dcb.fErrorChar = FALSE;
|
||||
dcb.fNull = FALSE;
|
||||
dcb.fAbortOnError = FALSE;
|
||||
|
||||
if (ClearCommError (port->hdl, &errors, &status) == 0)
|
||||
return -1;
|
||||
|
||||
dcb.BaudRate = CBR_115200;
|
||||
|
||||
dcb.ByteSize = 8;
|
||||
dcb.Parity = NOPARITY;
|
||||
dcb.StopBits = ONESTOPBIT;
|
||||
dcb.fRtsControl = RTS_CONTROL_DISABLE;
|
||||
dcb.fOutxCtsFlow = FALSE;
|
||||
dcb.fDtrControl = DTR_CONTROL_DISABLE;
|
||||
dcb.fOutxDsrFlow = FALSE;
|
||||
dcb.fInX = FALSE;
|
||||
dcb.fOutX = FALSE;
|
||||
|
||||
if (!SetCommState (port->hdl, &dcb))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
#else
|
||||
struct termios tty;
|
||||
|
||||
if ((port->fd = r_sandbox_open (port->name, O_NONBLOCK | O_NOCTTY | O_RDWR, 0)) < 0)
|
||||
return -1;
|
||||
|
||||
memset (&tty, 0, sizeof tty);
|
||||
if (tcgetattr (port->fd, &tty) != 0) {
|
||||
sp_close (port);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cfsetospeed (&tty, B115200);
|
||||
cfsetispeed (&tty, B115200);
|
||||
|
||||
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;
|
||||
tty.c_iflag &= ~IGNBRK;
|
||||
tty.c_lflag = 0;
|
||||
tty.c_oflag = 0;
|
||||
tty.c_cc[VMIN] = 0;
|
||||
tty.c_cc[VTIME] = 0;
|
||||
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
|
||||
|
||||
tty.c_cflag |= (CLOCAL | CREAD);
|
||||
tty.c_cflag &= ~(PARENB | PARODD);
|
||||
tty.c_cflag &= ~CSTOPB;
|
||||
tty.c_cflag &= ~CRTSCTS;
|
||||
|
||||
if (tcsetattr (port->fd, TCSANOW, &tty) != 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if __WINDOWS__
|
||||
/* Restart wait operation if buffer was emptied. */
|
||||
static int restart_wait_if_needed (struct sp_port *port, unsigned int bytes_read) {
|
||||
DWORD errors;
|
||||
COMSTAT comstat;
|
||||
|
||||
if (bytes_read == 0)
|
||||
return 0;
|
||||
|
||||
if (ClearCommError (port->hdl, &errors, &comstat) == 0)
|
||||
return -1;
|
||||
|
||||
if (comstat.cbInQue == 0)
|
||||
if (restart_wait (port))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int sp_blocking_read (struct sp_port *port, void *buf,
|
||||
size_t count, unsigned int timeout_ms) {
|
||||
#if __WINDOWS__
|
||||
DWORD bytes_read = 0;
|
||||
|
||||
/* Set timeout. */
|
||||
if (port->timeouts.ReadIntervalTimeout != 0 ||
|
||||
port->timeouts.ReadTotalTimeoutMultiplier != 0 ||
|
||||
port->timeouts.ReadTotalTimeoutConstant != timeout_ms) {
|
||||
port->timeouts.ReadIntervalTimeout = 0;
|
||||
port->timeouts.ReadTotalTimeoutMultiplier = 0;
|
||||
port->timeouts.ReadTotalTimeoutConstant = timeout_ms;
|
||||
if (SetCommTimeouts (port->hdl, &port->timeouts) == 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Start read. */
|
||||
if (ReadFile (port->hdl, buf, count, NULL, &port->read_ovl)) {
|
||||
bytes_read = count;
|
||||
} else if (GetLastError () == ERROR_IO_PENDING) {
|
||||
if (GetOverlappedResult (port->hdl, &port->read_ovl, &bytes_read, TRUE) == 0)
|
||||
return -1;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (restart_wait_if_needed (port, bytes_read))
|
||||
return -1;
|
||||
|
||||
return bytes_read;
|
||||
#else
|
||||
size_t bytes_read = 0;
|
||||
unsigned char *ptr = (unsigned char *)buf;
|
||||
struct timeval start, delta, now, end = {0, 0};
|
||||
int started = 0;
|
||||
fd_set fds;
|
||||
int result;
|
||||
|
||||
if (timeout_ms) {
|
||||
/* Get time at start of operation. */
|
||||
gettimeofday (&start, NULL);
|
||||
/* Define duration of timeout. */
|
||||
delta.tv_sec = timeout_ms / 1000;
|
||||
delta.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
/* Calculate time at which we should give up. */
|
||||
timeradd (&start, &delta, &end);
|
||||
}
|
||||
|
||||
FD_ZERO (&fds);
|
||||
FD_SET (port->fd, &fds);
|
||||
|
||||
/* Loop until we have the requested number of bytes. */
|
||||
while (bytes_read < count) {
|
||||
/*
|
||||
* Check timeout only if we have run select() at least once,
|
||||
* to avoid any issues if a short timeout is reached before
|
||||
* select() is even run.
|
||||
*/
|
||||
if (timeout_ms && started) {
|
||||
gettimeofday (&now, NULL);
|
||||
if (timercmp (&now, &end, >))
|
||||
/* Timeout has expired. */
|
||||
break;
|
||||
timersub (&end, &now, &delta);
|
||||
}
|
||||
result = select (port->fd + 1, &fds, NULL, NULL, timeout_ms ? &delta : NULL);
|
||||
started = 1;
|
||||
if (result < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (result == 0) {
|
||||
/* Timeout has expired. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Do read. */
|
||||
result = read (port->fd, ptr, count - bytes_read);
|
||||
|
||||
if (result < 0) {
|
||||
if (errno == EAGAIN)
|
||||
/*
|
||||
* This shouldn't happen because we did a
|
||||
* select() first, but handle anyway.
|
||||
*/
|
||||
continue;
|
||||
else
|
||||
/* This is an actual failure. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
bytes_read += result;
|
||||
ptr += result;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int sp_flush (struct sp_port *port) {
|
||||
#if __WINDOWS__
|
||||
/* Returns non-zero upon success, 0 upon failure. */
|
||||
if (PurgeComm (port->hdl, PURGE_RXCLEAR) == 0)
|
||||
return -1;
|
||||
|
||||
if (restart_wait (port))
|
||||
return -1;
|
||||
#else
|
||||
if (tcflush (port->fd, TCIFLUSH) < 0)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if __WINDOWS__
|
||||
static int await_write_completion (struct sp_port *port) {
|
||||
DWORD bytes_written;
|
||||
BOOL result;
|
||||
|
||||
/* Wait for previous non-blocking write to complete, if any. */
|
||||
if (port->writing) {
|
||||
result = GetOverlappedResult (port->hdl, &port->write_ovl, &bytes_written, TRUE);
|
||||
port->writing = 0;
|
||||
if (!result)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int sp_blocking_write (struct sp_port *port, const void *buf,
|
||||
size_t count, unsigned int timeout_ms) {
|
||||
#if __WINDOWS__
|
||||
DWORD bytes_written = 0;
|
||||
|
||||
if (await_write_completion (port))
|
||||
return -1;
|
||||
|
||||
/* Set timeout. */
|
||||
if (port->timeouts.WriteTotalTimeoutConstant != timeout_ms) {
|
||||
port->timeouts.WriteTotalTimeoutConstant = timeout_ms;
|
||||
if (SetCommTimeouts (port->hdl, &port->timeouts) == 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Start write. */
|
||||
if (WriteFile (port->hdl, buf, count, NULL, &port->write_ovl)) {
|
||||
return count;
|
||||
} else if (GetLastError () == ERROR_IO_PENDING) {
|
||||
if (GetOverlappedResult (port->hdl, &port->write_ovl, &bytes_written, TRUE) == 0) {
|
||||
if (GetLastError () == ERROR_SEM_TIMEOUT)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
return bytes_written;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
size_t bytes_written = 0;
|
||||
unsigned char *ptr = (unsigned char *)buf;
|
||||
struct timeval start, delta, now, end = {0, 0};
|
||||
int started = 0;
|
||||
fd_set fds;
|
||||
int result;
|
||||
|
||||
if (timeout_ms) {
|
||||
/* Get time at start of operation. */
|
||||
gettimeofday (&start, NULL);
|
||||
/* Define duration of timeout. */
|
||||
delta.tv_sec = timeout_ms / 1000;
|
||||
delta.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
/* Calculate time at which we should give up. */
|
||||
timeradd (&start, &delta, &end);
|
||||
}
|
||||
|
||||
FD_ZERO (&fds);
|
||||
FD_SET (port->fd, &fds);
|
||||
|
||||
/* Loop until we have written the requested number of bytes. */
|
||||
while (bytes_written < count) {
|
||||
/*
|
||||
* Check timeout only if we have run select() at least once,
|
||||
* to avoid any issues if a short timeout is reached before
|
||||
* select() is even run.
|
||||
*/
|
||||
if (timeout_ms && started) {
|
||||
gettimeofday (&now, NULL);
|
||||
if (timercmp (&now, &end, >))
|
||||
/* Timeout has expired. */
|
||||
break;
|
||||
timersub (&end, &now, &delta);
|
||||
}
|
||||
result = select (port->fd + 1, NULL, &fds, NULL, timeout_ms ? &delta : NULL);
|
||||
started = 1;
|
||||
if (result < 0) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (result == 0) {
|
||||
/* Timeout has expired. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Do write. */
|
||||
result = write (port->fd, ptr, count - bytes_written);
|
||||
|
||||
if (result < 0) {
|
||||
if (errno == EAGAIN)
|
||||
/* This shouldn't happen because we did a select() first, but handle anyway. */
|
||||
continue;
|
||||
else
|
||||
/* This is an actual failure. */
|
||||
return -1;
|
||||
}
|
||||
|
||||
bytes_written += result;
|
||||
ptr += result;
|
||||
}
|
||||
|
||||
return bytes_written;
|
||||
#endif
|
||||
}
|
||||
|
||||
static ut8 gprobe_checksum (const ut8 *p, unsigned int size) {
|
||||
ut8 res = 0;
|
||||
unsigned int k;
|
||||
|
||||
for (k = 0; k < size; ++k) {
|
||||
res += p[k];
|
||||
}
|
||||
|
||||
res = ~res + 1;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void gprobe_frame (RBuffer *frame) {
|
||||
ut8 size = r_buf_size (frame) + 2;
|
||||
ut8 checksum;
|
||||
|
||||
r_buf_prepend_bytes (frame, &size, 1);
|
||||
checksum = gprobe_checksum (r_buf_buffer (frame), size - 1);
|
||||
|
||||
r_buf_append_bytes (frame, &checksum, 1);
|
||||
}
|
||||
|
||||
static int gprobe_get_reply (struct sp_port *port, ut8 cmd, RBuffer *reply) {
|
||||
ut8 buf[256];
|
||||
int count = sp_blocking_read (port, buf, 2, 50);
|
||||
|
||||
if (count < 2)
|
||||
return -1;
|
||||
|
||||
if (cmd != buf[1])
|
||||
return -1;
|
||||
|
||||
if (!(buf[0] - 2))
|
||||
return 0;
|
||||
|
||||
count = sp_blocking_read (port, buf + 2, buf[0] - 2, 50) + 2;
|
||||
|
||||
if (count != buf[0])
|
||||
return -1;
|
||||
|
||||
/* checksumming answers does not work reliably */
|
||||
#if 0
|
||||
if (gprobe_checksum(buf, count - 1) != buf[count - 1]) {
|
||||
printf("### CHECKSUM FAILED\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
r_buf_append_bytes (reply, buf + 2, count - 3);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gprobe_read (struct sp_port *port, ut32 addr, ut8 *buf, ut32 count) {
|
||||
RBuffer *request = r_buf_new ();
|
||||
RBuffer *reply = r_buf_new ();
|
||||
const ut8 cmd = GPROBE_RAM_READ_2;
|
||||
ut8 addr_be[4];
|
||||
ut8 count_be[4];
|
||||
int res;
|
||||
|
||||
count = R_MIN (252, count);
|
||||
|
||||
r_write_be32 (addr_be, addr);
|
||||
r_write_be32 (count_be, count);
|
||||
|
||||
r_buf_append_bytes (request, &cmd, 1);
|
||||
r_buf_append_bytes (request, addr_be, 4);
|
||||
r_buf_append_bytes (request, count_be, 4);
|
||||
|
||||
gprobe_frame (request);
|
||||
|
||||
sp_flush (port);
|
||||
|
||||
if (sp_blocking_write (port, r_buf_buffer (request), r_buf_size (request),
|
||||
100) != r_buf_size (request))
|
||||
goto fail;
|
||||
|
||||
if (gprobe_get_reply (port, cmd, reply))
|
||||
goto fail;
|
||||
|
||||
res = r_buf_read_at (reply, 0, buf, r_buf_size (reply));
|
||||
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
|
||||
return res;
|
||||
|
||||
fail:
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int gprobe_write (struct sp_port *port, ut32 addr, const ut8 *buf, ut32 count) {
|
||||
RBuffer *request = r_buf_new ();
|
||||
RBuffer *reply = r_buf_new ();
|
||||
const ut8 cmd = GPROBE_RAM_WRITE_2;
|
||||
ut8 addr_be[4];
|
||||
ut8 count_be[4];
|
||||
|
||||
count = R_MIN (248, count);
|
||||
|
||||
r_write_be32 (addr_be, addr);
|
||||
r_write_be32 (count_be, count);
|
||||
|
||||
r_buf_append_bytes (request, &cmd, 1);
|
||||
r_buf_append_bytes (request, addr_be, 4);
|
||||
r_buf_append_bytes (request, buf, count);
|
||||
|
||||
gprobe_frame (request);
|
||||
|
||||
sp_flush (port);
|
||||
|
||||
if (sp_blocking_write (port, r_buf_buffer (request), r_buf_size (request),
|
||||
100) != r_buf_size (request))
|
||||
goto fail;
|
||||
|
||||
if (gprobe_get_reply (port, GPROBE_ACK, reply))
|
||||
goto fail;
|
||||
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
|
||||
return count;
|
||||
|
||||
fail:
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int gprobe_reset (struct sp_port *port, ut8 code) {
|
||||
RBuffer *request = r_buf_new ();
|
||||
RBuffer *reply = r_buf_new ();
|
||||
const ut8 cmd = GPROBE_RESET;
|
||||
|
||||
r_buf_append_bytes (request, &cmd, 1);
|
||||
r_buf_append_bytes (request, &code, 1);
|
||||
|
||||
gprobe_frame (request);
|
||||
|
||||
sp_flush (port);
|
||||
|
||||
if (sp_blocking_write (port, r_buf_buffer (request), r_buf_size (request),
|
||||
100) != r_buf_size (request))
|
||||
goto fail;
|
||||
|
||||
if (gprobe_get_reply (port, GPROBE_ACK, reply))
|
||||
goto fail;
|
||||
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int gprobe_debugon (struct sp_port *port) {
|
||||
RBuffer *request = r_buf_new ();
|
||||
RBuffer *reply = r_buf_new ();
|
||||
const ut8 cmd = GPROBE_DEBUGON;
|
||||
|
||||
r_buf_append_bytes (request, &cmd, 1);
|
||||
|
||||
gprobe_frame (request);
|
||||
|
||||
sp_flush (port);
|
||||
|
||||
if (sp_blocking_write (port, r_buf_buffer (request), r_buf_size (request),
|
||||
100) != r_buf_size (request))
|
||||
goto fail;
|
||||
|
||||
if (gprobe_get_reply (port, GPROBE_ACK, reply))
|
||||
goto fail;
|
||||
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int gprobe_debugoff (struct sp_port *port) {
|
||||
RBuffer *request = r_buf_new ();
|
||||
RBuffer *reply = r_buf_new ();
|
||||
const ut8 cmd = GPROBE_DEBUGOFF;
|
||||
|
||||
r_buf_append_bytes (request, &cmd, 1);
|
||||
|
||||
gprobe_frame (request);
|
||||
|
||||
sp_flush (port);
|
||||
|
||||
if (sp_blocking_write (port, r_buf_buffer (request), r_buf_size (request),
|
||||
100) != r_buf_size (request))
|
||||
goto fail;
|
||||
|
||||
if (gprobe_get_reply (port, GPROBE_ACK, reply))
|
||||
goto fail;
|
||||
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int gprobe_runcode (struct sp_port *port, ut32 addr) {
|
||||
RBuffer *request = r_buf_new ();
|
||||
RBuffer *reply = r_buf_new ();
|
||||
const ut8 cmd = GPROBE_RUN_CODE_2;
|
||||
ut8 addr_be[4];
|
||||
|
||||
r_write_be32 (addr_be, addr);
|
||||
|
||||
r_buf_append_bytes (request, &cmd, 1);
|
||||
r_buf_append_bytes (request, addr_be, 4);
|
||||
|
||||
gprobe_frame (request);
|
||||
|
||||
sp_flush (port);
|
||||
|
||||
if (sp_blocking_write (port, r_buf_buffer (request), r_buf_size (request),
|
||||
100) != r_buf_size (request))
|
||||
goto fail;
|
||||
|
||||
if (gprobe_get_reply (port, GPROBE_ACK, reply))
|
||||
goto fail;
|
||||
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int gprobe_getdeviceid (struct sp_port *port, ut8 index) {
|
||||
RBuffer *request = r_buf_new ();
|
||||
RBuffer *reply = r_buf_new ();
|
||||
const ut8 cmd = GPROBE_GET_DEVICE_ID;
|
||||
|
||||
r_buf_append_bytes (request, &cmd, 1);
|
||||
r_buf_append_bytes (request, &index, 1);
|
||||
|
||||
gprobe_frame (request);
|
||||
|
||||
sp_flush (port);
|
||||
|
||||
if (sp_blocking_write (port, r_buf_buffer (request), r_buf_size (request),
|
||||
100) != r_buf_size (request))
|
||||
goto fail;
|
||||
|
||||
if (gprobe_get_reply (port, cmd, reply))
|
||||
goto fail;
|
||||
|
||||
printf ("%s\n", r_buf_to_string (reply));
|
||||
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
r_buf_free (request);
|
||||
r_buf_free (reply);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int __write (RIO *io, RIODesc *fd, const ut8 *buf, int count) {
|
||||
RIOGprobe *gprobe;
|
||||
int res;
|
||||
int has_written = 0;
|
||||
|
||||
if (!fd || !fd->data || !buf) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
gprobe = (RIOGprobe *)fd->data;
|
||||
|
||||
if ((gprobe->offset + count) > GPROBE_SIZE)
|
||||
count = GPROBE_SIZE - gprobe->offset;
|
||||
|
||||
while (has_written < count) {
|
||||
res = gprobe_write (&gprobe->sp_port, gprobe->offset, buf + has_written, count - has_written);
|
||||
if (res <= 0)
|
||||
return -1;
|
||||
gprobe->offset += res;
|
||||
has_written += res;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static int __read (RIO *io, RIODesc *fd, ut8 *buf, int count) {
|
||||
int res;
|
||||
RIOGprobe *gprobe;
|
||||
int has_read = 0;
|
||||
|
||||
if (!fd || !fd->data || !buf) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
gprobe = (RIOGprobe *)fd->data;
|
||||
|
||||
if ((gprobe->offset + count) > GPROBE_SIZE)
|
||||
count = GPROBE_SIZE - gprobe->offset;
|
||||
|
||||
while (has_read < count) {
|
||||
res = gprobe_read (&gprobe->sp_port, gprobe->offset, buf + has_read, count - has_read);
|
||||
if (res <= 0)
|
||||
return -1;
|
||||
gprobe->offset += res;
|
||||
has_read += res;
|
||||
}
|
||||
|
||||
return has_read;
|
||||
}
|
||||
|
||||
static int __close (RIODesc *fd) {
|
||||
RIOGprobe *gprobe;
|
||||
|
||||
if (!fd || !fd->data) {
|
||||
return -1;
|
||||
}
|
||||
gprobe = (RIOGprobe *)fd->data;
|
||||
|
||||
sp_close (&gprobe->sp_port);
|
||||
R_FREE (fd->data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ut64 __lseek (RIO *io, RIODesc *fd, ut64 offset, int whence) {
|
||||
RIOGprobe *gprobe;
|
||||
if (!fd || !fd->data) {
|
||||
return offset;
|
||||
}
|
||||
gprobe = (RIOGprobe *)fd->data;
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
if (offset >= GPROBE_SIZE) {
|
||||
return gprobe->offset = GPROBE_SIZE - 1;
|
||||
}
|
||||
return gprobe->offset = offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
if ((gprobe->offset + offset) >= GPROBE_SIZE) {
|
||||
return gprobe->offset = GPROBE_SIZE - 1;
|
||||
}
|
||||
return gprobe->offset += offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
return gprobe->offset = GPROBE_SIZE - 1;
|
||||
break;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static bool __plugin_open (RIO *io, const char *pathname, bool many) {
|
||||
return pathname && r_str_startswith (pathname, "gprobe://") && strlen (pathname + strlen ("gprobe://"));
|
||||
}
|
||||
|
||||
static RIODesc *__open (RIO *io, const char *pathname, int rw, int mode) {
|
||||
if (__plugin_open (io, pathname, 0)) {
|
||||
RIOGprobe *gprobe = R_NEW0 (RIOGprobe);
|
||||
|
||||
gprobe->offset = 0LL;
|
||||
gprobe->sp_port.name = pathname + strlen ("gprobe://");
|
||||
if (sp_open (&gprobe->sp_port)) {
|
||||
R_FREE (gprobe);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return r_io_desc_new (io, &r_io_plugin_gprobe, pathname, rw, mode, gprobe);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static char *__system (RIO *io, RIODesc *fd, const char *cmd) {
|
||||
RIOGprobe *gprobe;
|
||||
|
||||
if (!fd || !fd->data) {
|
||||
return NULL;
|
||||
}
|
||||
gprobe = (RIOGprobe *)fd->data;
|
||||
|
||||
if (!cmd[0] || cmd[0] == '?' || !strcmp (cmd, "help")) {
|
||||
printf ("Usage: =!cmd args\n"
|
||||
" =!reset code\n"
|
||||
" =!debugon\n"
|
||||
" =!debugoff\n"
|
||||
" =!runcode address\n"
|
||||
" =!getdeviceid\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (r_str_startswith (cmd, "reset") && (strlen (cmd) > 6)) {
|
||||
ut32 code = (ut32)strtoul (cmd + 6, NULL, 10);
|
||||
|
||||
gprobe_reset (&gprobe->sp_port, code);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (r_str_startswith (cmd, "debugon")) {
|
||||
gprobe_debugon (&gprobe->sp_port);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (r_str_startswith (cmd, "debugoff")) {
|
||||
gprobe_debugoff (&gprobe->sp_port);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (r_str_startswith (cmd, "runcode") && (strlen (cmd) > 8)) {
|
||||
ut32 address = (ut32)strtoul (cmd + 8, NULL, 0);
|
||||
|
||||
gprobe_runcode (&gprobe->sp_port, address);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (r_str_startswith (cmd, "getdeviceid")) {
|
||||
ut8 index = 0;
|
||||
|
||||
while (!gprobe_getdeviceid (&gprobe->sp_port, index++)) {
|
||||
};
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
printf ("Try: '=!?'\n");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RIOPlugin r_io_plugin_gprobe = {
|
||||
.name = "gprobe",
|
||||
.desc = "open gprobe connection using gprobe://",
|
||||
.license = "LGPL3",
|
||||
.open = __open,
|
||||
.close = __close,
|
||||
.read = __read,
|
||||
.check = __plugin_open,
|
||||
.lseek = __lseek,
|
||||
.write = __write,
|
||||
.system = __system,
|
||||
};
|
||||
|
||||
#ifndef CORELIB
|
||||
RLibStruct radare_plugin = {
|
||||
.type = R_LIB_TYPE_IO,
|
||||
.data = &r_io_plugin_gprobe,
|
||||
.version = R2_VERSION};
|
||||
#endif
|
@ -209,6 +209,7 @@ io.bfdbg
|
||||
io.bochs
|
||||
io.debug
|
||||
io.default
|
||||
io.gprobe
|
||||
io.gdb
|
||||
io.qnx
|
||||
io.r2pipe
|
||||
|
Loading…
Reference in New Issue
Block a user