Add mini gdb stub
This commit is contained in:
parent
e0decc5721
commit
5f7b8ee030
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "(gdb) Launch",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/mini-gdbstub/build/emu",
|
||||||
|
"args": ["./mini-gdbstub/build/emu_test.bin"],
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"cwd": "${workspaceFolder}/mini-gdbstub",
|
||||||
|
"environment": [],
|
||||||
|
"externalConsole": false,
|
||||||
|
"MIMode": "gdb",
|
||||||
|
"setupCommands": [
|
||||||
|
{
|
||||||
|
"description": "Enable pretty-printing for gdb",
|
||||||
|
"text": "-enable-pretty-printing",
|
||||||
|
"ignoreFailures": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Set Disassembly Flavor to Intel",
|
||||||
|
"text": "-gdb-set disassembly-flavor intel",
|
||||||
|
"ignoreFailures": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
build
|
|
@ -0,0 +1,47 @@
|
||||||
|
CFLAGS = -Iinclude -Wall -Wextra -MMD #-Werror
|
||||||
|
|
||||||
|
CURDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
|
O ?= build
|
||||||
|
OUT := $(O)
|
||||||
|
EMU_OUT := $(abspath $(OUT))
|
||||||
|
|
||||||
|
SHELL_HACK := $(shell mkdir -p $(OUT))
|
||||||
|
GIT_HOOKS := .git/hooks/applied
|
||||||
|
|
||||||
|
LIBSRCS = $(shell find ./src -name '*.c')
|
||||||
|
_LIB_OBJ = $(notdir $(LIBSRCS))
|
||||||
|
LIB_OBJ = $(_LIB_OBJ:%.c=$(OUT)/%.o)
|
||||||
|
|
||||||
|
TEST_OBJ = $(EMU_OUT)/emu_test.obj
|
||||||
|
TEST_BIN = $(EMU_OUT)/emu_test.bin
|
||||||
|
|
||||||
|
vpath %.c $(sort $(dir $(LIBSRCS)))
|
||||||
|
.PHONY: all debug clean
|
||||||
|
|
||||||
|
all: CFLAGS += -O0 -g
|
||||||
|
all: LDFLAGS += -O0
|
||||||
|
|
||||||
|
debug: CFLAGS += -O0 -g -DDEBUG
|
||||||
|
debug: LDFLAGS += -O0
|
||||||
|
|
||||||
|
$(GIT_HOOKS):
|
||||||
|
@scripts/install-git-hooks
|
||||||
|
@echo
|
||||||
|
|
||||||
|
$(OUT)/%.o: %.c
|
||||||
|
$(CC) -c $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
emu:
|
||||||
|
cd src && make all
|
||||||
|
|
||||||
|
stub: emu
|
||||||
|
$(EMU_OUT)/emu $(TEST_BIN)
|
||||||
|
|
||||||
|
gdb: emu
|
||||||
|
gdb-multiarch $(TEST_OBJ) -ex "set architecture riscv:rv32" -ex "target remote 127.0.0.1:1234"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) -rf build
|
||||||
|
|
||||||
|
-include $(OUT)/*.d
|
|
@ -0,0 +1,128 @@
|
||||||
|
# mini-gdbstub
|
||||||
|
|
||||||
|
`mini-gdbstub` is an implementation of the
|
||||||
|
[GDB Remote Serial Protocol](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html)
|
||||||
|
that gives your emulators debugging capabilities.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The very first thing you should do is to build the statically-linked library `libgdbstub.a`.
|
||||||
|
```
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
To use the library in your project, you should include the file `gdbstub.h` first.
|
||||||
|
Then, you have to initialize the pre-allocated structure `gdbstub_t` with `gdbstub_init`.
|
||||||
|
|
||||||
|
```c
|
||||||
|
bool gdbstub_init(gdbstub_t *gdbstub, struct target_ops *ops, arch_info_t arch, char *s);
|
||||||
|
```
|
||||||
|
|
||||||
|
The parameters `s` is the easiest one to understand. It is a string of the socket
|
||||||
|
which your emulator would like to bind as gdb server.
|
||||||
|
|
||||||
|
The `struct target_ops` is made up of function pointers. Each member function represents an
|
||||||
|
abstraction of your emulator's operation. The following lists the requirement
|
||||||
|
that should be provided for each method:
|
||||||
|
|
||||||
|
Method | Description
|
||||||
|
---------------|------------------
|
||||||
|
`cont` | Run the emulator until hitting breakpoint or exit.
|
||||||
|
`stepi` | Do one step on the emulator. You may define your own step for the emulator. For example, the common design is executing one instruction.
|
||||||
|
`get_reg_bytes` | Get the register size in bytes for the register specified by `regno` as a return value.
|
||||||
|
`read_reg` | Read the value of the register specified by `regno` to `*value`. Return zero if the operation success, otherwise return an errno for the corresponding error.
|
||||||
|
`write_reg` | Write value `value` to the register specified by `regno`. Return zero if the operation success, otherwise return an errno for the corresponding error.
|
||||||
|
`read_mem` | Read the memory according to the address specified by `addr` with size `len` to the buffer `*val`. Return zero if the operation success, otherwise return an errno for the corresponding error.
|
||||||
|
`write_mem` | Write data in the buffer `val` with size `len` to the memory which address is specified by `addr`. Return zero if the operation success, otherwise return an errno for the corresponding error.
|
||||||
|
`set_bp` | Set type `type` breakpoint on the address specified by `addr`. Return true if we set the breakpoint successfully, otherwise return false.
|
||||||
|
`del_bp` | Delete type `type` breakpoint on the address specified by `addr`. Return true if we delete the breakpoint successfully, otherwise return false.
|
||||||
|
`on_interrupt` | Do something when receiving interrupt from GDB client. This method will run concurrently with `cont`, so you should be careful if there're shared data between them. You will need a lock or something similar to avoid data race.
|
||||||
|
`set_cpu` | Set the debug target CPU to `cpuid`.
|
||||||
|
`get_cpu` | Get the current debug target CPU `cpuid` as return value.
|
||||||
|
|
||||||
|
```c
|
||||||
|
struct target_ops {
|
||||||
|
gdb_action_t (*cont)(void *args);
|
||||||
|
gdb_action_t (*stepi)(void *args);
|
||||||
|
size_t (*get_reg_bytes)(int regno);
|
||||||
|
int (*read_reg)(void *args, int regno, void *value);
|
||||||
|
int (*write_reg)(void *args, int regno, void* value);
|
||||||
|
int (*read_mem)(void *args, size_t addr, size_t len, void *val);
|
||||||
|
int (*write_mem)(void *args, size_t addr, size_t len, void *val);
|
||||||
|
bool (*set_bp)(void *args, size_t addr, bp_type_t type);
|
||||||
|
bool (*del_bp)(void *args, size_t addr, bp_type_t type);
|
||||||
|
void (*on_interrupt)(void *args);
|
||||||
|
|
||||||
|
void (*set_cpu)(void *args, int cpuid);
|
||||||
|
int (*get_cpu)(void *args);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
For `cont` and `stepi` which are used to process the execution of emulator, their return type
|
||||||
|
should be `gdb_action_t`. After performing the relevant operation, you should return `ACT_RESUME`
|
||||||
|
to continue debugging; otherwise, return `ACT_SHUTDOWN` to finish debugging. The library
|
||||||
|
typically uses `ACT_NONE` to take no action.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
ACT_NONE,
|
||||||
|
ACT_RESUME,
|
||||||
|
ACT_SHUTDOWN,
|
||||||
|
} gdb_action_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
For `set_bp` and `del_bp`, the type of breakpoint which should be set or deleted is described
|
||||||
|
in the type `bp_type_t`. In fact, its value will always be `BP_SOFTWARE` currently.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef enum {
|
||||||
|
BP_SOFTWARE = 0,
|
||||||
|
} bp_type_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
Another structure you have to declare is `arch_info_t`. You must explicitly specify about the
|
||||||
|
following field within `arch_info_t` while integrating into your emulator:
|
||||||
|
* `smp`: Number of target's CPU
|
||||||
|
* `reg_num`: Number of target's registers
|
||||||
|
|
||||||
|
The `target_desc` is an optional member which could be
|
||||||
|
`TARGET_RV32`, `TARGET_RV64` if the emulator is RISC-V 32-bit or 64-bit instruction
|
||||||
|
set architecture or `TARGET_X86_64` if the emulator is x86_64 instruction set architecture. Alternatively, it can be a custom target description document
|
||||||
|
string used by gdb. If none of these apply, simply set it to NULL.
|
||||||
|
|
||||||
|
* Although the value of `reg_num` may be determined by `target_desc`, those
|
||||||
|
members are still required to be filled correctly.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
char *target_desc;
|
||||||
|
int smp;
|
||||||
|
int reg_num;
|
||||||
|
} arch_info_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
After startup, we can use `gdbstub_run` to run the emulator as gdbstub. The `args`
|
||||||
|
can be used to pass the argument to any function in `struct target_ops`.
|
||||||
|
|
||||||
|
```c
|
||||||
|
bool gdbstub_run(gdbstub_t *gdbstub, void *args);
|
||||||
|
```
|
||||||
|
|
||||||
|
When exiting from `gdbstub_run`, `gdbstub_close` should be called to recycle the resource on
|
||||||
|
the initialization.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void gdbstub_close(gdbstub_t *gdbstub);
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, you can build you project with the statically-linked library `libgdbstub.a` now!
|
||||||
|
Additionally, it is advised that you check the reference emulator in the directory `emu,` which
|
||||||
|
demonstrates how to integrate `mini-gdbstub` into your project.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
### Project
|
||||||
|
* [bet4it/gdbserver](https://github.com/bet4it/gdbserver)
|
||||||
|
* [devbored/minigdbstub](https://github.com/devbored/minigdbstub)
|
||||||
|
### Article
|
||||||
|
* [Howto: GDB Remote Serial Protocol](https://www.embecosm.com/appnotes/ean4/embecosm-howto-rsp-server-ean4-issue-2.html)
|
||||||
|
* [TLMBoy: Implementing the GDB Remote Serial Protocol](https://www.chciken.com/tlmboy/2022/04/03/gdb-z80.html)
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef CONN_H
|
||||||
|
#define CONN_H
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "packet.h"
|
||||||
|
|
||||||
|
#define MAX_SEND_PACKET_SIZE (0x1000)
|
||||||
|
#define MAX_DATA_PAYLOAD (MAX_SEND_PACKET_SIZE - (2 + CSUM_SIZE + 2))
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int listen_fd;
|
||||||
|
int socket_fd;
|
||||||
|
|
||||||
|
pktbuf_t pktbuf;
|
||||||
|
} conn_t;
|
||||||
|
|
||||||
|
bool conn_init(conn_t *conn, char *addr_str, int port);
|
||||||
|
void conn_recv_packet(conn_t *conn);
|
||||||
|
packet_t *conn_pop_packet(conn_t *conn);
|
||||||
|
bool conn_try_recv_intr(conn_t *conn);
|
||||||
|
void conn_send_str(conn_t *conn, char *str);
|
||||||
|
void conn_send_pktstr(conn_t *conn, char *pktstr);
|
||||||
|
void conn_close(conn_t *conn);
|
||||||
|
#endif
|
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef GDB_SIGNAL_H
|
||||||
|
#define GDB_SIGNAL_H
|
||||||
|
|
||||||
|
#define GDB_SIGNAL_TRAP 5
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,69 @@
|
||||||
|
#ifndef GDBSTUB_H
|
||||||
|
#define GDBSTUB_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define TARGET_RV32 \
|
||||||
|
"<target version=\"1.0\"><architecture>riscv:rv32</architecture></target>"
|
||||||
|
#define TARGET_RV64 \
|
||||||
|
"<target version=\"1.0\"><architecture>riscv:rv64</architecture></target>"
|
||||||
|
#define TARGET_X86_64 \
|
||||||
|
"<target " \
|
||||||
|
"version=\"1.0\"><architecture>i386:x86-64</architecture></target>"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EVENT_NONE,
|
||||||
|
EVENT_CONT,
|
||||||
|
EVENT_DETACH,
|
||||||
|
EVENT_STEP,
|
||||||
|
} gdb_event_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ACT_NONE,
|
||||||
|
ACT_RESUME,
|
||||||
|
ACT_SHUTDOWN,
|
||||||
|
} gdb_action_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BP_SOFTWARE = 0,
|
||||||
|
} bp_type_t;
|
||||||
|
|
||||||
|
struct target_ops {
|
||||||
|
gdb_action_t (*cont)(void *args);
|
||||||
|
gdb_action_t (*stepi)(void *args);
|
||||||
|
size_t (*get_reg_bytes)(int regno);
|
||||||
|
int (*read_reg)(void *args, int regno, void *value);
|
||||||
|
int (*write_reg)(void *args, int regno, void *value);
|
||||||
|
int (*read_mem)(void *args, size_t addr, size_t len, void *val);
|
||||||
|
int (*write_mem)(void *args, size_t addr, size_t len, void *val);
|
||||||
|
bool (*set_bp)(void *args, size_t addr, bp_type_t type);
|
||||||
|
bool (*del_bp)(void *args, size_t addr, bp_type_t type);
|
||||||
|
void (*on_interrupt)(void *args);
|
||||||
|
|
||||||
|
void (*set_cpu)(void *args, int cpuid);
|
||||||
|
int (*get_cpu)(void *args);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct gdbstub_private gdbstub_private_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *target_desc;
|
||||||
|
int smp;
|
||||||
|
int reg_num;
|
||||||
|
} arch_info_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct target_ops *ops;
|
||||||
|
arch_info_t arch;
|
||||||
|
gdbstub_private_t *priv;
|
||||||
|
} gdbstub_t;
|
||||||
|
|
||||||
|
bool gdbstub_init(gdbstub_t *gdbstub,
|
||||||
|
struct target_ops *ops,
|
||||||
|
arch_info_t arch,
|
||||||
|
char *s);
|
||||||
|
bool gdbstub_run(gdbstub_t *gdbstub, void *args);
|
||||||
|
void gdbstub_close(gdbstub_t *gdbstub);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef PACKET_H
|
||||||
|
#define PACKET_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define STR_ACK "+"
|
||||||
|
#define INTR_CHAR '\x03'
|
||||||
|
|
||||||
|
#define CSUM_SIZE (2)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int end_pos;
|
||||||
|
uint8_t data[];
|
||||||
|
} packet_t;
|
||||||
|
|
||||||
|
/* A naive packet buffer: maintain a big array to fill the packet */
|
||||||
|
typedef struct {
|
||||||
|
int size; /* the size for all valid characters in data buffer */
|
||||||
|
int cap; /* the capacity (1 << cap) of the data buffer */
|
||||||
|
int end_pos; /* the end position of the first packet in data buffer */
|
||||||
|
uint8_t *data;
|
||||||
|
} pktbuf_t;
|
||||||
|
|
||||||
|
bool pktbuf_init(pktbuf_t *pktbuf);
|
||||||
|
ssize_t pktbuf_fill_from_file(pktbuf_t *pktbuf, int fd);
|
||||||
|
bool pktbuf_is_complete(pktbuf_t *pktbuf);
|
||||||
|
packet_t *pktbuf_pop_packet(pktbuf_t *pktbuf);
|
||||||
|
void pktbuf_destroy(pktbuf_t *pktbuf);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef REG_H
|
||||||
|
#define REG_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *buf;
|
||||||
|
size_t sz;
|
||||||
|
} regbuf_t;
|
||||||
|
|
||||||
|
bool regbuf_init(regbuf_t *reg);
|
||||||
|
void *regbuf_get(regbuf_t *reg, size_t reg_sz);
|
||||||
|
void regbuf_destroy(regbuf_t *reg);
|
||||||
|
#endif
|
|
@ -0,0 +1,9 @@
|
||||||
|
#ifndef CSUM_H
|
||||||
|
#define CSUM_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
uint8_t compute_checksum(char *buf, size_t len);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef LOG_H
|
||||||
|
#define LOG_H
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static inline void log_print(const char *format, ...)
|
||||||
|
{
|
||||||
|
va_list vargs;
|
||||||
|
va_start(vargs, format);
|
||||||
|
vfprintf(stderr, format, vargs);
|
||||||
|
va_end(vargs);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define warn(format, ...) \
|
||||||
|
log_print("ERRNO: %s\n" format, strerror(errno), ##__VA_ARGS__)
|
||||||
|
#define info(format, ...) log_print(format, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,10 @@
|
||||||
|
#ifndef UTILS_H
|
||||||
|
#define UTILS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void hex_to_str(uint8_t *num, char *str, int bytes);
|
||||||
|
void str_to_hex(char *str, uint8_t *num, int bytes);
|
||||||
|
int unescape(char *msg, char *end);
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,35 @@
|
||||||
|
CFLAGS = -I../include -Wall -Wextra -g
|
||||||
|
LDFLAGS =
|
||||||
|
|
||||||
|
CURDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
|
O ?= ../build
|
||||||
|
OUT := $(O)
|
||||||
|
|
||||||
|
BIN = $(OUT)/emu
|
||||||
|
SHELL_HACK := $(shell mkdir -p $(OUT))
|
||||||
|
|
||||||
|
CSRCS = $(shell find ./ -name '*.c')
|
||||||
|
_COBJ = $(notdir $(CSRCS))
|
||||||
|
COBJ = $(_COBJ:%.c=$(OUT)/%.o)
|
||||||
|
|
||||||
|
TEST_NAME = emu_test
|
||||||
|
TEST_OBJ = $(OUT)/$(TEST_NAME).obj
|
||||||
|
TEST_BIN = $(OUT)/$(TEST_NAME).bin
|
||||||
|
|
||||||
|
vpath %.c $(sort $(dir $(CSRCS)))
|
||||||
|
.PHONY: all
|
||||||
|
|
||||||
|
all: $(BIN) $(TEST_BIN)
|
||||||
|
|
||||||
|
$(OUT)/%.o: %.c
|
||||||
|
$(CC) -c $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
$(TEST_OBJ): emu_test.cc
|
||||||
|
riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -Wl,-Ttext=0x0 -nostdlib -g -o $@ $<
|
||||||
|
|
||||||
|
$(TEST_BIN): $(TEST_OBJ)
|
||||||
|
riscv64-unknown-elf-objcopy -O binary $< $@
|
||||||
|
|
||||||
|
$(BIN): $(COBJ)
|
||||||
|
$(CC) $^ -o $@ $(LDFLAGS)
|
|
@ -0,0 +1,177 @@
|
||||||
|
#include "conn.h"
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "utils/csum.h"
|
||||||
|
#include "utils/log.h"
|
||||||
|
|
||||||
|
static bool socket_poll(int socket_fd, int timeout, int events)
|
||||||
|
{
|
||||||
|
struct pollfd pfd = (struct pollfd){
|
||||||
|
.fd = socket_fd,
|
||||||
|
.events = events,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (poll(&pfd, 1, timeout) > 0) && (pfd.revents & events);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool socket_readable(int socket_fd, int timeout)
|
||||||
|
{
|
||||||
|
return socket_poll(socket_fd, timeout, POLLIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool socket_writable(int socket_fd, int timeout)
|
||||||
|
{
|
||||||
|
return socket_poll(socket_fd, timeout, POLLOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool conn_init(conn_t *conn, char *addr_str, int port)
|
||||||
|
{
|
||||||
|
if (!pktbuf_init(&conn->pktbuf))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
struct in_addr addr_ip;
|
||||||
|
if (inet_aton(addr_str, &addr_ip) != 0) {
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_addr.s_addr = addr_ip.s_addr;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
conn->listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (conn->listen_fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int optval = 1;
|
||||||
|
if (setsockopt(conn->listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval,
|
||||||
|
sizeof(optval)) < 0) {
|
||||||
|
warn("Set sockopt fail.\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind(conn->listen_fd, (struct sockaddr *) &addr, sizeof(addr)) <
|
||||||
|
0) {
|
||||||
|
warn("Bind fail.\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
struct sockaddr_un addr;
|
||||||
|
addr.sun_family = AF_UNIX;
|
||||||
|
strncpy(addr.sun_path, addr_str, sizeof(addr.sun_path) - 1);
|
||||||
|
unlink(addr_str);
|
||||||
|
conn->listen_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
if (conn->listen_fd < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (bind(conn->listen_fd, (struct sockaddr *) &addr, sizeof(addr)) <
|
||||||
|
0) {
|
||||||
|
warn("Bind fail.\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listen(conn->listen_fd, 1) < 0) {
|
||||||
|
warn("Listen fail.\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->socket_fd = accept(conn->listen_fd, NULL, NULL);
|
||||||
|
if (conn->socket_fd < 0) {
|
||||||
|
warn("Accept fail.\n");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
close(conn->listen_fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void conn_recv_packet(conn_t *conn)
|
||||||
|
{
|
||||||
|
while (!pktbuf_is_complete(&conn->pktbuf) &&
|
||||||
|
socket_readable(conn->socket_fd, -1)) {
|
||||||
|
ssize_t nread = pktbuf_fill_from_file(&conn->pktbuf, conn->socket_fd);
|
||||||
|
if (nread == -1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_send_str(conn, STR_ACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
packet_t *conn_pop_packet(conn_t *conn)
|
||||||
|
{
|
||||||
|
packet_t *pkt = pktbuf_pop_packet(&conn->pktbuf);
|
||||||
|
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool conn_try_recv_intr(conn_t *conn)
|
||||||
|
{
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
if (!socket_readable(conn->socket_fd, 0))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ssize_t nread = read(conn->socket_fd, &ch, 1);
|
||||||
|
if (nread != 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* FIXME: The character must be INTR_CHAR, otherwise the library
|
||||||
|
* may work incorrectly. However, I'm not sure if this implementation
|
||||||
|
* can always meet our expectation (concurrent is so hard QAQ). */
|
||||||
|
assert(ch == INTR_CHAR);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void conn_send_str(conn_t *conn, char *str)
|
||||||
|
{
|
||||||
|
size_t len = strlen(str);
|
||||||
|
|
||||||
|
while (len > 0 && socket_writable(conn->socket_fd, -1)) {
|
||||||
|
ssize_t nwrite = write(conn->socket_fd, str, len);
|
||||||
|
if (nwrite == -1)
|
||||||
|
break;
|
||||||
|
len -= nwrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void conn_send_pktstr(conn_t *conn, char *pktstr)
|
||||||
|
{
|
||||||
|
char packet[MAX_SEND_PACKET_SIZE];
|
||||||
|
size_t len = strlen(pktstr);
|
||||||
|
|
||||||
|
/* 2: '$' + '#'
|
||||||
|
* 2: checksum digits(maximum)
|
||||||
|
* 1: '\0' */
|
||||||
|
assert(len + 2 + CSUM_SIZE + 1 < MAX_SEND_PACKET_SIZE);
|
||||||
|
|
||||||
|
packet[0] = '$';
|
||||||
|
memcpy(packet + 1, pktstr, len);
|
||||||
|
packet[len + 1] = '#';
|
||||||
|
|
||||||
|
char csum_str[4];
|
||||||
|
uint8_t csum = compute_checksum(pktstr, len);
|
||||||
|
size_t csum_len = snprintf(csum_str, sizeof(csum_str) - 1, "%02x", csum);
|
||||||
|
assert(csum_len == CSUM_SIZE);
|
||||||
|
memcpy(packet + len + 2, csum_str, csum_len);
|
||||||
|
packet[len + 2 + csum_len] = '\0';
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("send packet = %s,", packet);
|
||||||
|
printf(" checksum = %d\n", csum);
|
||||||
|
#endif
|
||||||
|
conn_send_str(conn, packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void conn_close(conn_t *conn)
|
||||||
|
{
|
||||||
|
close(conn->socket_fd);
|
||||||
|
close(conn->listen_fd);
|
||||||
|
pktbuf_destroy(&conn->pktbuf);
|
||||||
|
}
|
|
@ -0,0 +1,549 @@
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "gdbstub.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
#define read_len(bit, ptr, value) \
|
||||||
|
do { \
|
||||||
|
value = 0; \
|
||||||
|
for (int i = 0; i < ((bit) >> 3); i++) \
|
||||||
|
value |= ((uint64_t) (*((ptr) + i))) << (i << 3); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define write_len(bit, ptr, value) \
|
||||||
|
do { \
|
||||||
|
for (int i = 0; i < ((bit) >> 3); i++) \
|
||||||
|
*((ptr) + i) = (value >> (i << 3)) & 0xff; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define MEM_SIZE (0x1000)
|
||||||
|
#define TOHOST_ADDR (MEM_SIZE - 4)
|
||||||
|
|
||||||
|
struct mem {
|
||||||
|
uint8_t *mem;
|
||||||
|
size_t code_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct emu {
|
||||||
|
struct mem m;
|
||||||
|
uint64_t x[32];
|
||||||
|
uint64_t pc;
|
||||||
|
|
||||||
|
bool bp_is_set;
|
||||||
|
uint64_t bp_addr;
|
||||||
|
|
||||||
|
bool halt;
|
||||||
|
|
||||||
|
gdbstub_t gdbstub;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void emu_halt(struct emu *emu)
|
||||||
|
{
|
||||||
|
__atomic_store_n(&emu->halt, true, __ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void emu_start_run(struct emu *emu)
|
||||||
|
{
|
||||||
|
__atomic_store_n(&emu->halt, false, __ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool emu_is_halt(struct emu *emu)
|
||||||
|
{
|
||||||
|
return __atomic_load_n(&emu->halt, __ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct inst {
|
||||||
|
uint64_t inst;
|
||||||
|
|
||||||
|
uint8_t rd;
|
||||||
|
uint8_t rs1;
|
||||||
|
uint8_t rs2;
|
||||||
|
uint8_t funct3;
|
||||||
|
uint8_t funct7;
|
||||||
|
} inst_t;
|
||||||
|
|
||||||
|
static inline int opcode_3(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
uint8_t *ptr;
|
||||||
|
uint64_t value;
|
||||||
|
uint64_t imm = (int32_t) (inst->inst & 0xfff00000) >> 20;
|
||||||
|
|
||||||
|
switch (inst->funct3) {
|
||||||
|
case 0x2:
|
||||||
|
// lw
|
||||||
|
ptr = emu->m.mem + emu->x[inst->rs1] + imm;
|
||||||
|
read_len(32, ptr, value);
|
||||||
|
emu->x[inst->rd] = value;
|
||||||
|
return 0;
|
||||||
|
case 0x3:
|
||||||
|
// ld
|
||||||
|
ptr = emu->m.mem + emu->x[inst->rs1] + imm;
|
||||||
|
read_len(64, ptr, value);
|
||||||
|
emu->x[inst->rd] = value;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_13(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
uint64_t imm = (int32_t) (inst->inst & 0xfff00000) >> 20;
|
||||||
|
|
||||||
|
switch (inst->funct3) {
|
||||||
|
case 0x0:
|
||||||
|
// addi
|
||||||
|
emu->x[inst->rd] = emu->x[inst->rs1] + imm;
|
||||||
|
return 0;
|
||||||
|
case 0x2:
|
||||||
|
// slti
|
||||||
|
emu->x[inst->rd] = (int64_t) emu->x[inst->rs1] < (int64_t) imm ? 1 : 0;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_17(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
// auipc
|
||||||
|
uint64_t imm = (int32_t) (inst->inst & 0xfffff000);
|
||||||
|
emu->x[inst->rd] = emu->pc + imm - 4;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_1b(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
uint64_t imm = (int32_t) (inst->inst & 0xfff00000) >> 20;
|
||||||
|
|
||||||
|
switch (inst->funct3) {
|
||||||
|
case 0x0:
|
||||||
|
// addiw
|
||||||
|
emu->x[inst->rd] =
|
||||||
|
(int32_t) (((uint32_t) emu->x[inst->rs1] + (uint32_t) imm));
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_23(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
uint8_t *ptr;
|
||||||
|
uint64_t imm = (((int32_t) (inst->inst & 0xfe000000) >> 20) |
|
||||||
|
(int32_t) ((inst->inst >> 7) & 0x1f));
|
||||||
|
|
||||||
|
switch (inst->funct3) {
|
||||||
|
case 0x0:
|
||||||
|
// sb
|
||||||
|
ptr = emu->m.mem + emu->x[inst->rs1] + imm;
|
||||||
|
write_len(8, ptr, emu->x[inst->rs2]);
|
||||||
|
return 0;
|
||||||
|
case 0x2:
|
||||||
|
// sw
|
||||||
|
ptr = emu->m.mem + emu->x[inst->rs1] + imm;
|
||||||
|
write_len(32, ptr, emu->x[inst->rs2]);
|
||||||
|
return 0;
|
||||||
|
case 0x3:
|
||||||
|
// sd
|
||||||
|
ptr = emu->m.mem + emu->x[inst->rs1] + imm;
|
||||||
|
write_len(64, ptr, emu->x[inst->rs2]);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_33(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
switch (inst->funct3) {
|
||||||
|
case 0x0:
|
||||||
|
switch (inst->funct7) {
|
||||||
|
case 0x00:
|
||||||
|
// add
|
||||||
|
emu->x[inst->rd] = emu->x[inst->rs1] + emu->x[inst->rs2];
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_37(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
// lui
|
||||||
|
uint64_t imm = (int32_t) (inst->inst & 0xfffff000);
|
||||||
|
emu->x[inst->rd] = imm;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_3b(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
switch (inst->funct3) {
|
||||||
|
case 0x0:
|
||||||
|
switch (inst->funct7) {
|
||||||
|
case 0x00:
|
||||||
|
// addw
|
||||||
|
emu->x[inst->rd] = (int32_t) ((uint32_t) emu->x[inst->rs1] +
|
||||||
|
(uint32_t) emu->x[inst->rs2]);
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_67(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
// jalr
|
||||||
|
uint64_t imm = (int32_t) (inst->inst & 0xfff00000) >> 20;
|
||||||
|
emu->x[inst->rd] = emu->pc;
|
||||||
|
emu->pc = (emu->x[inst->rs1] + imm) & ~1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int opcode_6f(struct emu *emu, inst_t *inst)
|
||||||
|
{
|
||||||
|
uint64_t imm;
|
||||||
|
|
||||||
|
// jal
|
||||||
|
emu->x[inst->rd] = emu->pc;
|
||||||
|
// imm[20|10:1|11|19:12] = inst[31|30:21|20|19:12]
|
||||||
|
imm = ((int32_t) (inst->inst & 0x80000000) >> 11) // 20
|
||||||
|
| (int32_t) ((inst->inst & 0xff000)) // 19:12
|
||||||
|
| (int32_t) ((inst->inst >> 9) & 0x800) // 11
|
||||||
|
| (int32_t) ((inst->inst >> 20) & 0x7fe); // 10:1
|
||||||
|
emu->pc = emu->pc + imm - 4;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int emu_exec(struct emu *emu, uint32_t raw_inst)
|
||||||
|
{
|
||||||
|
int ret = -1;
|
||||||
|
uint8_t opcode = raw_inst & 0x7f;
|
||||||
|
|
||||||
|
inst_t inst;
|
||||||
|
|
||||||
|
// Emulate register x0 to 0
|
||||||
|
emu->x[0] = 0;
|
||||||
|
|
||||||
|
inst.inst = raw_inst;
|
||||||
|
inst.rd = (raw_inst >> 7) & 0x1f;
|
||||||
|
inst.rs1 = (raw_inst >> 15) & 0x1f;
|
||||||
|
inst.rs2 = (raw_inst >> 20) & 0x1f;
|
||||||
|
inst.funct3 = (raw_inst >> 12) & 0x7;
|
||||||
|
inst.funct7 = (raw_inst >> 25) & 0x7f;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("[%4lx] opcode: %2x, funct3: %x, funct7: %2x\n", emu->pc - 4, opcode,
|
||||||
|
inst.funct3, inst.funct7);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
switch (opcode) {
|
||||||
|
case 0x3:
|
||||||
|
ret = opcode_3(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x13:
|
||||||
|
ret = opcode_13(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x17:
|
||||||
|
ret = opcode_17(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x1b:
|
||||||
|
ret = opcode_1b(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x23:
|
||||||
|
ret = opcode_23(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x33:
|
||||||
|
ret = opcode_33(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x37:
|
||||||
|
ret = opcode_37(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x3b:
|
||||||
|
ret = opcode_3b(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x67:
|
||||||
|
ret = opcode_67(emu, &inst);
|
||||||
|
break;
|
||||||
|
case 0x6f:
|
||||||
|
ret = opcode_6f(emu, &inst);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
static char *abi_name[] = {
|
||||||
|
"z", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0",
|
||||||
|
"a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3", "s4", "s5",
|
||||||
|
"s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < 32; i++) {
|
||||||
|
printf("x%-2ld(%-3s) = 0x%-16lx, ", i, abi_name[i], emu->x[i]);
|
||||||
|
if (!((i + 1) & 1))
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
|
printf("Not implemented or invalid instruction@%lx\n", emu->pc - 4);
|
||||||
|
printf("opcode:%x, funct3:%x, funct7:%x\n", opcode, inst.funct3,
|
||||||
|
inst.funct7);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emu_init(struct emu *emu)
|
||||||
|
{
|
||||||
|
memset(emu, 0, sizeof(struct emu));
|
||||||
|
emu->pc = 0;
|
||||||
|
emu->x[2] = TOHOST_ADDR;
|
||||||
|
emu->bp_addr = -1;
|
||||||
|
emu_halt(emu);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int init_mem(struct mem *m, const char *filename)
|
||||||
|
{
|
||||||
|
if (!filename) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *fp = fopen(filename, "rb");
|
||||||
|
if (!fp)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
size_t sz = ftell(fp) * sizeof(uint8_t);
|
||||||
|
rewind(fp);
|
||||||
|
|
||||||
|
/* We leave extra four bytes as the hint stop the emulator, so
|
||||||
|
* the size of the binary should not exceed this. */
|
||||||
|
if (sz > TOHOST_ADDR)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
m->mem = malloc(MEM_SIZE);
|
||||||
|
if (!m->mem) {
|
||||||
|
fclose(fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
memset(m->mem, 0, MEM_SIZE);
|
||||||
|
size_t read_size = fread(m->mem, sizeof(uint8_t), sz, fp);
|
||||||
|
|
||||||
|
if (read_size != sz) {
|
||||||
|
fclose(fp);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
m->code_size = read_size;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_mem(struct mem *m)
|
||||||
|
{
|
||||||
|
free(m->mem);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t emu_get_reg_bytes(int regno __attribute__((unused)))
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int emu_read_reg(void *args, int regno, void *reg_value)
|
||||||
|
{
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
if (regno > 32) {
|
||||||
|
return EFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regno == 32) {
|
||||||
|
memcpy(reg_value, &emu->pc, 4);
|
||||||
|
} else {
|
||||||
|
memcpy(reg_value, &emu->x[regno], 4);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int emu_write_reg(void *args, int regno, void *data)
|
||||||
|
{
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
|
||||||
|
if (regno > 32) {
|
||||||
|
return EFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regno == 32) {
|
||||||
|
memcpy(&emu->pc, data, 4);
|
||||||
|
} else {
|
||||||
|
memcpy(&emu->x[regno], data, 4);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int emu_read_mem(void *args, size_t addr, size_t len, void *val)
|
||||||
|
{
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
if (addr + len > MEM_SIZE) {
|
||||||
|
return EFAULT;
|
||||||
|
}
|
||||||
|
memcpy(val, (void *) emu->m.mem + addr, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int emu_write_mem(void *args, size_t addr, size_t len, void *val)
|
||||||
|
{
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
if (addr + len > MEM_SIZE) {
|
||||||
|
return EFAULT;
|
||||||
|
}
|
||||||
|
memcpy((void *) emu->m.mem + addr, val, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gdb_action_t emu_cont(void *args)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
uint8_t *tohost_addr = emu->m.mem + TOHOST_ADDR;
|
||||||
|
|
||||||
|
emu_start_run(emu);
|
||||||
|
while (emu->pc < emu->m.code_size && emu->pc != emu->bp_addr &&
|
||||||
|
!emu_is_halt(emu)) {
|
||||||
|
uint32_t inst;
|
||||||
|
uint8_t value;
|
||||||
|
emu_read_mem(args, emu->pc, 4, &inst);
|
||||||
|
emu->pc += 4;
|
||||||
|
ret = emu_exec(emu, inst);
|
||||||
|
if (ret < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* We assume the binary that run on this emulator will
|
||||||
|
* be stopped after writing the specific memory address.
|
||||||
|
* In this way, we can simply design our testing binary. */
|
||||||
|
read_len(8, tohost_addr, value);
|
||||||
|
if (value)
|
||||||
|
return ACT_SHUTDOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ACT_RESUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gdb_action_t emu_stepi(void *args)
|
||||||
|
{
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
|
||||||
|
emu_start_run(emu);
|
||||||
|
if (emu->pc < emu->m.code_size) {
|
||||||
|
uint32_t inst;
|
||||||
|
emu_read_mem(args, emu->pc, 4, &inst);
|
||||||
|
emu->pc += 4;
|
||||||
|
emu_exec(emu, inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ACT_RESUME;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool emu_set_bp(void *args, size_t addr, bp_type_t type)
|
||||||
|
{
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
if (type != BP_SOFTWARE || emu->bp_is_set)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
emu->bp_is_set = true;
|
||||||
|
emu->bp_addr = addr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool emu_del_bp(void *args, size_t addr, bp_type_t type)
|
||||||
|
{
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
|
||||||
|
// It's fine when there's no matching breakpoint, just doing nothing
|
||||||
|
if (type != BP_SOFTWARE || !emu->bp_is_set || emu->bp_addr != addr)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
emu->bp_is_set = false;
|
||||||
|
emu->bp_addr = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void emu_on_interrupt(void *args)
|
||||||
|
{
|
||||||
|
struct emu *emu = (struct emu *) args;
|
||||||
|
emu_halt(emu);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct target_ops emu_ops = {
|
||||||
|
.get_reg_bytes = emu_get_reg_bytes,
|
||||||
|
.read_reg = emu_read_reg,
|
||||||
|
.write_reg = emu_write_reg,
|
||||||
|
.read_mem = emu_read_mem,
|
||||||
|
.write_mem = emu_write_mem,
|
||||||
|
.cont = emu_cont,
|
||||||
|
.stepi = emu_stepi,
|
||||||
|
.set_bp = emu_set_bp,
|
||||||
|
.del_bp = emu_del_bp,
|
||||||
|
.on_interrupt = emu_on_interrupt,
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc != 2) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct emu emu;
|
||||||
|
emu_init(&emu);
|
||||||
|
|
||||||
|
if (init_mem(&emu.m, argv[1]) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gdbstub_init(&emu.gdbstub, &emu_ops,
|
||||||
|
(arch_info_t){
|
||||||
|
.smp = 1,
|
||||||
|
.reg_num = 33,
|
||||||
|
.target_desc = TARGET_RV32,
|
||||||
|
},
|
||||||
|
"127.0.0.1:1234")) {
|
||||||
|
fprintf(stderr, "Fail to create socket.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gdbstub_run(&emu.gdbstub, (void *) &emu)) {
|
||||||
|
fprintf(stderr, "Fail to run in debug mode.\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
gdbstub_close(&emu.gdbstub);
|
||||||
|
free_mem(&emu.m);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
int add(int a, int b);
|
||||||
|
|
||||||
|
// The main function put on 0x0 intentionally
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
volatile char *tohost_addr;
|
||||||
|
|
||||||
|
int c = add(3, 4);
|
||||||
|
|
||||||
|
/* Because the binary will be run on baremetal environment
|
||||||
|
* which doesn't support C-runtime. Stop the program by writing
|
||||||
|
* to this specicial address */
|
||||||
|
tohost_addr = (volatile char *) (0x1000 - 4);
|
||||||
|
*tohost_addr = 0xff;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int add(int a, int b)
|
||||||
|
{
|
||||||
|
return a + b;
|
||||||
|
}
|
|
@ -0,0 +1,725 @@
|
||||||
|
#include "gdbstub.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include "conn.h"
|
||||||
|
#include "gdb_signal.h"
|
||||||
|
#include "packet.h"
|
||||||
|
#include "regbuf.h"
|
||||||
|
#include "utils/csum.h"
|
||||||
|
#include "utils/translate.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
struct gdbstub_private {
|
||||||
|
conn_t conn;
|
||||||
|
regbuf_t regbuf;
|
||||||
|
|
||||||
|
pthread_t tid;
|
||||||
|
bool async_io_enable;
|
||||||
|
void *args;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void async_io_enable(struct gdbstub_private *priv)
|
||||||
|
{
|
||||||
|
__atomic_store_n(&priv->async_io_enable, true, __ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void async_io_disable(struct gdbstub_private *priv)
|
||||||
|
{
|
||||||
|
__atomic_store_n(&priv->async_io_enable, false, __ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool async_io_is_enable(struct gdbstub_private *priv)
|
||||||
|
{
|
||||||
|
return __atomic_load_n(&priv->async_io_enable, __ATOMIC_RELAXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
static volatile bool thread_stop = false;
|
||||||
|
static void *socket_reader(gdbstub_t *gdbstub)
|
||||||
|
{
|
||||||
|
void *args = gdbstub->priv->args;
|
||||||
|
int socket_fd = gdbstub->priv->conn.socket_fd;
|
||||||
|
|
||||||
|
fd_set readfds;
|
||||||
|
struct timeval timeout;
|
||||||
|
|
||||||
|
while (!__atomic_load_n(&thread_stop, __ATOMIC_RELAXED)) {
|
||||||
|
if (!async_io_is_enable(gdbstub->priv)) {
|
||||||
|
usleep(10000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FD_ZERO(&readfds);
|
||||||
|
FD_SET(socket_fd, &readfds);
|
||||||
|
|
||||||
|
timeout.tv_sec = 0;
|
||||||
|
timeout.tv_usec = 100000;
|
||||||
|
|
||||||
|
int result = select(socket_fd + 1, &readfds, NULL, NULL, &timeout);
|
||||||
|
|
||||||
|
if (result > 0 && FD_ISSET(socket_fd, &readfds)) {
|
||||||
|
char ch;
|
||||||
|
ssize_t nread = read(socket_fd, &ch, 1);
|
||||||
|
if (nread == 1 && ch == INTR_CHAR) {
|
||||||
|
gdbstub->ops->on_interrupt(args);
|
||||||
|
}
|
||||||
|
} else if (result < 0) {
|
||||||
|
perror("select error in socket_reader");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gdbstub_init(gdbstub_t *gdbstub,
|
||||||
|
struct target_ops *ops,
|
||||||
|
arch_info_t arch,
|
||||||
|
char *s)
|
||||||
|
{
|
||||||
|
char *addr_str = NULL;
|
||||||
|
|
||||||
|
if (s == NULL || ops == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memset(gdbstub, 0, sizeof(gdbstub_t));
|
||||||
|
gdbstub->ops = ops;
|
||||||
|
gdbstub->arch = arch;
|
||||||
|
gdbstub->priv = calloc(1, sizeof(struct gdbstub_private));
|
||||||
|
if (gdbstub->priv == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// This is a naive implementation to parse the string
|
||||||
|
addr_str = strdup(s);
|
||||||
|
char *port_str = strchr(addr_str, ':');
|
||||||
|
int port = 0;
|
||||||
|
if (addr_str == NULL)
|
||||||
|
goto addr_fail;
|
||||||
|
|
||||||
|
if (port_str != NULL) {
|
||||||
|
*port_str = '\0';
|
||||||
|
port_str += 1;
|
||||||
|
|
||||||
|
if (sscanf(port_str, "%d", &port) <= 0)
|
||||||
|
goto addr_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!regbuf_init(&gdbstub->priv->regbuf))
|
||||||
|
goto addr_fail;
|
||||||
|
|
||||||
|
if (!conn_init(&gdbstub->priv->conn, addr_str, port))
|
||||||
|
goto conn_fail;
|
||||||
|
|
||||||
|
free(addr_str);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
conn_fail:
|
||||||
|
regbuf_destroy(&gdbstub->priv->regbuf);
|
||||||
|
addr_fail:
|
||||||
|
free(addr_str);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SEND_ERR(gdbstub, err) conn_send_pktstr(&gdbstub->priv->conn, err)
|
||||||
|
#define SEND_EPERM(gdbstub) SEND_ERR(gdbstub, "E01")
|
||||||
|
#define SEND_EINVAL(gdbstub) SEND_ERR(gdbstub, "E22")
|
||||||
|
|
||||||
|
static void process_reg_read(gdbstub_t *gdbstub, void *args)
|
||||||
|
{
|
||||||
|
char packet_str[MAX_SEND_PACKET_SIZE];
|
||||||
|
|
||||||
|
for (int i = 0; i < gdbstub->arch.reg_num; i++) {
|
||||||
|
size_t reg_sz = gdbstub->ops->get_reg_bytes(i);
|
||||||
|
void *reg_value = regbuf_get(&gdbstub->priv->regbuf, reg_sz);
|
||||||
|
|
||||||
|
int ret = gdbstub->ops->read_reg(args, i, reg_value);
|
||||||
|
#ifdef DEBUG
|
||||||
|
char debug_hex[MAX_SEND_PACKET_SIZE];
|
||||||
|
hex_to_str((uint8_t *) reg_value, debug_hex, reg_sz);
|
||||||
|
printf("reg read = regno %d data 0x%s (size %zu)\n", i, debug_hex,
|
||||||
|
reg_sz);
|
||||||
|
#endif
|
||||||
|
if (!ret) {
|
||||||
|
hex_to_str((uint8_t *) reg_value, &packet_str[i * reg_sz * 2],
|
||||||
|
reg_sz);
|
||||||
|
} else {
|
||||||
|
sprintf(packet_str, "E%d", ret);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_reg_read_one(gdbstub_t *gdbstub, char *payload, void *args)
|
||||||
|
{
|
||||||
|
char packet_str[MAX_SEND_PACKET_SIZE];
|
||||||
|
int regno;
|
||||||
|
|
||||||
|
assert(sscanf(payload, "%x", ®no) == 1);
|
||||||
|
size_t reg_sz = gdbstub->ops->get_reg_bytes(regno);
|
||||||
|
void *reg_value = regbuf_get(&gdbstub->priv->regbuf, reg_sz);
|
||||||
|
|
||||||
|
int ret = gdbstub->ops->read_reg(args, regno, reg_value);
|
||||||
|
#ifdef DEBUG
|
||||||
|
char debug_hex[MAX_SEND_PACKET_SIZE];
|
||||||
|
hex_to_str((uint8_t *) reg_value, debug_hex, reg_sz);
|
||||||
|
printf("reg read = regno %d data 0x%s (size %zu)\n", regno, debug_hex,
|
||||||
|
reg_sz);
|
||||||
|
#endif
|
||||||
|
if (!ret) {
|
||||||
|
hex_to_str((uint8_t *) reg_value, packet_str, reg_sz);
|
||||||
|
} else {
|
||||||
|
sprintf(packet_str, "E%d", ret);
|
||||||
|
}
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_reg_write(gdbstub_t *gdbstub, char *payload, void *args)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < gdbstub->arch.reg_num; i++) {
|
||||||
|
size_t reg_sz = gdbstub->ops->get_reg_bytes(i);
|
||||||
|
void *reg_value = regbuf_get(&gdbstub->priv->regbuf, reg_sz);
|
||||||
|
|
||||||
|
str_to_hex(&payload[i * reg_sz * 2], (uint8_t *) reg_value, reg_sz);
|
||||||
|
#ifdef DEBUG
|
||||||
|
char debug_hex[MAX_SEND_PACKET_SIZE];
|
||||||
|
hex_to_str((uint8_t *) reg_value, debug_hex, reg_sz);
|
||||||
|
printf("reg write = regno %d data 0x%s (size %zu)\n", i, debug_hex,
|
||||||
|
reg_sz);
|
||||||
|
#endif
|
||||||
|
int ret = gdbstub->ops->write_reg(args, i, reg_value);
|
||||||
|
if (ret) {
|
||||||
|
/* FIXME: Even if we fail to modify this register, some
|
||||||
|
* registers could be writen before. This may not be
|
||||||
|
* an expected behavior. */
|
||||||
|
char packet_str[MAX_SEND_PACKET_SIZE];
|
||||||
|
sprintf(packet_str, "E%d", ret);
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_reg_write_one(gdbstub_t *gdbstub, char *payload, void *args)
|
||||||
|
{
|
||||||
|
int regno;
|
||||||
|
char *regno_str = payload;
|
||||||
|
char *data_str = strchr(payload, '=');
|
||||||
|
if (data_str) {
|
||||||
|
*data_str = '\0';
|
||||||
|
data_str++;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sscanf(regno_str, "%x", ®no) == 1);
|
||||||
|
size_t reg_sz = gdbstub->ops->get_reg_bytes(regno);
|
||||||
|
void *data = regbuf_get(&gdbstub->priv->regbuf, reg_sz);
|
||||||
|
|
||||||
|
assert(strlen(data_str) == reg_sz * 2);
|
||||||
|
|
||||||
|
str_to_hex(data_str, (uint8_t *) data, reg_sz);
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("reg write = regno %d data 0x%s (size %zu)\n", regno, data_str,
|
||||||
|
reg_sz);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int ret = gdbstub->ops->write_reg(args, regno, data);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
} else {
|
||||||
|
char packet_str[MAX_SEND_PACKET_SIZE];
|
||||||
|
sprintf(packet_str, "E%d", ret);
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_mem_read(gdbstub_t *gdbstub, char *payload, void *args)
|
||||||
|
{
|
||||||
|
size_t maddr, mlen;
|
||||||
|
assert(sscanf(payload, "%lx,%lx", &maddr, &mlen) == 2);
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("mem read = addr %lx / len %lx\n", maddr, mlen);
|
||||||
|
#endif
|
||||||
|
char packet_str[MAX_SEND_PACKET_SIZE];
|
||||||
|
|
||||||
|
uint8_t *mval = malloc(mlen);
|
||||||
|
int ret = gdbstub->ops->read_mem(args, maddr, mlen, mval);
|
||||||
|
if (!ret) {
|
||||||
|
hex_to_str(mval, packet_str, mlen);
|
||||||
|
} else {
|
||||||
|
sprintf(packet_str, "E%d", ret);
|
||||||
|
}
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
free(mval);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_mem_write(gdbstub_t *gdbstub, char *payload, void *args)
|
||||||
|
{
|
||||||
|
size_t maddr, mlen;
|
||||||
|
char *content = strchr(payload, ':');
|
||||||
|
if (content) {
|
||||||
|
*content = '\0';
|
||||||
|
content++;
|
||||||
|
}
|
||||||
|
assert(sscanf(payload, "%lx,%lx", &maddr, &mlen) == 2);
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("mem write = addr %lx / len %lx\n", maddr, mlen);
|
||||||
|
printf("mem write = content %s\n", content);
|
||||||
|
#endif
|
||||||
|
uint8_t *mval = malloc(mlen);
|
||||||
|
str_to_hex(content, mval, mlen);
|
||||||
|
int ret = gdbstub->ops->write_mem(args, maddr, mlen, mval);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
} else {
|
||||||
|
char packet_str[MAX_SEND_PACKET_SIZE];
|
||||||
|
sprintf(packet_str, "E%d", ret);
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
}
|
||||||
|
free(mval);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_mem_xwrite(gdbstub_t *gdbstub,
|
||||||
|
char *payload,
|
||||||
|
uint8_t *packet_end,
|
||||||
|
void *args)
|
||||||
|
{
|
||||||
|
size_t maddr, mlen;
|
||||||
|
char *content = strchr(payload, ':');
|
||||||
|
if (content) {
|
||||||
|
*content = '\0';
|
||||||
|
content++;
|
||||||
|
}
|
||||||
|
assert(sscanf(payload, "%lx,%lx", &maddr, &mlen) == 2);
|
||||||
|
assert(unescape(content, (char *) packet_end) == (int) mlen);
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("mem xwrite = addr %lx / len %lx\n", maddr, mlen);
|
||||||
|
for (size_t i = 0; i < mlen; i++) {
|
||||||
|
printf("\tmem xwrite, byte %ld: %x\n", i, content[i]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
gdbstub->ops->write_mem(args, maddr, mlen, content);
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void process_xfer(gdbstub_t *gdbstub, char *s)
|
||||||
|
{
|
||||||
|
char *name = s;
|
||||||
|
char *args = strchr(s, ':');
|
||||||
|
if (args) {
|
||||||
|
*args = '\0';
|
||||||
|
args++;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("xfer = %s %s\n", name, args);
|
||||||
|
#endif
|
||||||
|
if (!strcmp(name, "features") && gdbstub->arch.target_desc != NULL) {
|
||||||
|
/* Check the args */
|
||||||
|
char *action = strtok(args, ":");
|
||||||
|
assert(strcmp(action, "read") == 0);
|
||||||
|
char *annex = strtok(NULL, ":");
|
||||||
|
assert(strcmp(annex, "target.xml") == 0);
|
||||||
|
|
||||||
|
char buf[MAX_SEND_PACKET_SIZE];
|
||||||
|
int offset = 0, length = 0;
|
||||||
|
sscanf(strtok(NULL, ":"), "%x,%x", &offset, &length);
|
||||||
|
|
||||||
|
int total_len = strlen(gdbstub->arch.target_desc);
|
||||||
|
int payload_length =
|
||||||
|
MAX_DATA_PAYLOAD > length ? length : MAX_DATA_PAYLOAD;
|
||||||
|
|
||||||
|
// Determine if the remaining data fits within the buffer
|
||||||
|
buf[0] = (total_len - offset < payload_length) ? 'l' : 'm';
|
||||||
|
snprintf(buf + 1, payload_length, "%s",
|
||||||
|
gdbstub->arch.target_desc + offset);
|
||||||
|
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, buf);
|
||||||
|
} else {
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_query(gdbstub_t *gdbstub, char *payload, void *args)
|
||||||
|
{
|
||||||
|
char packet_str[MAX_SEND_PACKET_SIZE];
|
||||||
|
char *name = payload;
|
||||||
|
char *qargs = strchr(payload, ':');
|
||||||
|
if (qargs) {
|
||||||
|
*qargs = '\0';
|
||||||
|
qargs++;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("query = %s %s\n", name, qargs);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!strcmp(name, "C")) {
|
||||||
|
if (gdbstub->ops->get_cpu != NULL) {
|
||||||
|
int cpuid = gdbstub->ops->get_cpu(args);
|
||||||
|
sprintf(packet_str, "QC%04d", cpuid);
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
} else
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "");
|
||||||
|
} else if (!strcmp(name, "Supported")) {
|
||||||
|
if (gdbstub->arch.target_desc != NULL)
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn,
|
||||||
|
"PacketSize=1024;qXfer:features:read+");
|
||||||
|
else
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "PacketSize=1024");
|
||||||
|
} else if (!strcmp(name, "Attached")) {
|
||||||
|
/* assume attached to an existing process */
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "1");
|
||||||
|
} else if (!strcmp(name, "Xfer")) {
|
||||||
|
process_xfer(gdbstub, qargs);
|
||||||
|
} else if (!strcmp(name, "Symbol")) {
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
} else if (!strcmp(name, "fThreadInfo")) {
|
||||||
|
/* Assume at least 1 CPU if user didn't specific
|
||||||
|
* the CPU counts */
|
||||||
|
int smp = gdbstub->arch.smp ? gdbstub->arch.smp : 1;
|
||||||
|
char *ptr;
|
||||||
|
char cpuid_str[6];
|
||||||
|
|
||||||
|
/* Make assumption on the CPU counts, so
|
||||||
|
* that we can use the buffer very simply. */
|
||||||
|
assert(smp < 10000);
|
||||||
|
|
||||||
|
packet_str[0] = 'm';
|
||||||
|
ptr = packet_str + 1;
|
||||||
|
for (int cpuid = 0; cpuid < smp; cpuid++) {
|
||||||
|
sprintf(cpuid_str, "%04d,", cpuid);
|
||||||
|
memcpy(ptr, cpuid_str, 5);
|
||||||
|
ptr += 5;
|
||||||
|
}
|
||||||
|
*ptr = 0;
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
} else if (!strcmp(name, "sThreadInfo")) {
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "l");
|
||||||
|
} else {
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline gdb_event_t process_vcont(gdbstub_t *gdbstub, char *args)
|
||||||
|
{
|
||||||
|
gdb_event_t event = EVENT_NONE;
|
||||||
|
|
||||||
|
switch (args[0]) {
|
||||||
|
case 'c':
|
||||||
|
if (gdbstub->ops->cont != NULL)
|
||||||
|
event = EVENT_CONT;
|
||||||
|
else
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
if (gdbstub->ops->stepi != NULL)
|
||||||
|
event = EVENT_STEP;
|
||||||
|
else
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define VCONT_DESC "vCont;%s%s"
|
||||||
|
static inline void process_vcont_support(gdbstub_t *gdbstub)
|
||||||
|
{
|
||||||
|
char packet_str[MAX_SEND_PACKET_SIZE];
|
||||||
|
char *str_s = (gdbstub->ops->stepi == NULL) ? "" : "s;S;";
|
||||||
|
char *str_c = (gdbstub->ops->cont == NULL) ? "" : "c;C;";
|
||||||
|
sprintf(packet_str, VCONT_DESC, str_s, str_c);
|
||||||
|
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gdb_event_t process_vpacket(gdbstub_t *gdbstub, char *payload)
|
||||||
|
{
|
||||||
|
gdb_event_t event = EVENT_NONE;
|
||||||
|
char *name = payload;
|
||||||
|
char *args = strchr(payload, ';');
|
||||||
|
if (args) {
|
||||||
|
*args = '\0';
|
||||||
|
args++;
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("vpacket = %s %s\n", name, args);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!strcmp("Cont", name))
|
||||||
|
event = process_vcont(gdbstub, args);
|
||||||
|
else if (!strcmp("Cont?", name))
|
||||||
|
process_vcont_support(gdbstub);
|
||||||
|
else
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "");
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_del_break_points(gdbstub_t *gdbstub,
|
||||||
|
char *payload,
|
||||||
|
void *args)
|
||||||
|
{
|
||||||
|
size_t type, addr, kind;
|
||||||
|
assert(sscanf(payload, "%zx,%zx,%zx", &type, &addr, &kind) == 3);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("remove breakpoints = %zx %zx %zx\n", type, addr, kind);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool ret = gdbstub->ops->del_bp(args, addr, type);
|
||||||
|
if (ret)
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
else
|
||||||
|
SEND_EINVAL(gdbstub);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_set_break_points(gdbstub_t *gdbstub,
|
||||||
|
char *payload,
|
||||||
|
void *args)
|
||||||
|
{
|
||||||
|
size_t type, addr, kind;
|
||||||
|
assert(sscanf(payload, "%zx,%zx,%zx", &type, &addr, &kind) == 3);
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("set breakpoints = %zx %zx %zx\n", type, addr, kind);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool ret = gdbstub->ops->set_bp(args, addr, type);
|
||||||
|
if (ret)
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
else
|
||||||
|
SEND_EINVAL(gdbstub);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_set_cpu(gdbstub_t *gdbstub, char *payload, void *args)
|
||||||
|
{
|
||||||
|
int cpuid;
|
||||||
|
/* We don't support deprecated Hc packet, GDB
|
||||||
|
* should send only send vCont;c and vCont;s here. */
|
||||||
|
if (payload[0] == 'g') {
|
||||||
|
assert(sscanf(payload, "g%d", &cpuid) == 1);
|
||||||
|
gdbstub->ops->set_cpu(args, cpuid);
|
||||||
|
}
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool packet_csum_verify(packet_t *inpkt)
|
||||||
|
{
|
||||||
|
/* We add extra 1 for leading '$' and minus extra 1 for trailing '#' */
|
||||||
|
uint8_t csum_rslt = compute_checksum((char *) inpkt->data + 1,
|
||||||
|
inpkt->end_pos - CSUM_SIZE - 1);
|
||||||
|
uint8_t csum_expected;
|
||||||
|
str_to_hex((char *) &inpkt->data[inpkt->end_pos - CSUM_SIZE + 1],
|
||||||
|
&csum_expected, sizeof(uint8_t));
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("csum rslt = %x / csum expected = %s / ", csum_rslt,
|
||||||
|
&inpkt->data[inpkt->end_pos - CSUM_SIZE + 1]);
|
||||||
|
printf("csum expected = %x \n", csum_expected);
|
||||||
|
#endif
|
||||||
|
return csum_rslt == csum_expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gdb_event_t gdbstub_process_packet(gdbstub_t *gdbstub,
|
||||||
|
packet_t *inpkt,
|
||||||
|
void *args)
|
||||||
|
{
|
||||||
|
assert(inpkt->data[0] == '$');
|
||||||
|
assert(packet_csum_verify(inpkt));
|
||||||
|
|
||||||
|
/* After checking the checksum result, ignore those bytes */
|
||||||
|
inpkt->data[inpkt->end_pos - CSUM_SIZE] = 0;
|
||||||
|
uint8_t request = inpkt->data[1];
|
||||||
|
char *payload = (char *) &inpkt->data[2];
|
||||||
|
gdb_event_t event = EVENT_NONE;
|
||||||
|
|
||||||
|
switch (request) {
|
||||||
|
case 'g':
|
||||||
|
if (gdbstub->ops->read_reg != NULL) {
|
||||||
|
process_reg_read(gdbstub, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
if (gdbstub->ops->read_mem != NULL) {
|
||||||
|
process_mem_read(gdbstub, payload, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
if (gdbstub->ops->read_reg != NULL) {
|
||||||
|
process_reg_read_one(gdbstub, payload, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'q':
|
||||||
|
process_query(gdbstub, payload, args);
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
event = process_vpacket(gdbstub, payload);
|
||||||
|
break;
|
||||||
|
case 'z':
|
||||||
|
if (gdbstub->ops->del_bp != NULL) {
|
||||||
|
process_del_break_points(gdbstub, payload, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '?':
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "S05");
|
||||||
|
break;
|
||||||
|
case 'D':
|
||||||
|
event = EVENT_DETACH;
|
||||||
|
break;
|
||||||
|
case 'G':
|
||||||
|
if (gdbstub->ops->write_reg != NULL) {
|
||||||
|
process_reg_write(gdbstub, payload, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'H':
|
||||||
|
if (gdbstub->ops->set_cpu != NULL) {
|
||||||
|
process_set_cpu(gdbstub, payload, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
if (gdbstub->ops->write_mem != NULL) {
|
||||||
|
process_mem_write(gdbstub, payload, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
if (gdbstub->ops->write_reg != NULL) {
|
||||||
|
process_reg_write_one(gdbstub, payload, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
/* FIXME: Assume all CPUs are alive here, any exception case
|
||||||
|
* that user may want to handle? */
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "OK");
|
||||||
|
break;
|
||||||
|
case 'X':
|
||||||
|
if (gdbstub->ops->write_mem != NULL) {
|
||||||
|
/* It is important for xwrite to know the end position of packet,
|
||||||
|
* because there're escape characters which block us interpreting
|
||||||
|
* the packet as a string just like other packets do. */
|
||||||
|
process_mem_xwrite(gdbstub, payload,
|
||||||
|
&inpkt->data[inpkt->end_pos - CSUM_SIZE], args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'Z':
|
||||||
|
if (gdbstub->ops->set_bp != NULL) {
|
||||||
|
process_set_break_points(gdbstub, payload, args);
|
||||||
|
} else {
|
||||||
|
SEND_EPERM(gdbstub);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, "");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gdb_action_t gdbstub_handle_event(gdbstub_t *gdbstub,
|
||||||
|
gdb_event_t event,
|
||||||
|
void *args)
|
||||||
|
{
|
||||||
|
gdb_action_t act = ACT_NONE;
|
||||||
|
|
||||||
|
switch (event) {
|
||||||
|
case EVENT_CONT:
|
||||||
|
async_io_enable(gdbstub->priv);
|
||||||
|
act = gdbstub->ops->cont(args);
|
||||||
|
async_io_disable(gdbstub->priv);
|
||||||
|
break;
|
||||||
|
case EVENT_STEP:
|
||||||
|
act = gdbstub->ops->stepi(args);
|
||||||
|
break;
|
||||||
|
case EVENT_DETACH:
|
||||||
|
act = ACT_SHUTDOWN;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return act;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gdbstub_act_resume(gdbstub_t *gdbstub)
|
||||||
|
{
|
||||||
|
char packet_str[32];
|
||||||
|
sprintf(packet_str, "S%02x", GDB_SIGNAL_TRAP);
|
||||||
|
conn_send_pktstr(&gdbstub->priv->conn, packet_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gdbstub_run(gdbstub_t *gdbstub, void *args)
|
||||||
|
{
|
||||||
|
// Bring the user-provided argument in the gdbstub_t structure
|
||||||
|
gdbstub->priv->args = args;
|
||||||
|
|
||||||
|
/* Create a thread to receive interrupt when running the gdbstub op */
|
||||||
|
if (gdbstub->ops->on_interrupt != NULL && gdbstub->priv->tid == 0) {
|
||||||
|
async_io_disable(gdbstub->priv);
|
||||||
|
pthread_create(&gdbstub->priv->tid, NULL, (void *) socket_reader,
|
||||||
|
(void *) gdbstub);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
conn_recv_packet(&gdbstub->priv->conn);
|
||||||
|
packet_t *pkt = conn_pop_packet(&gdbstub->priv->conn);
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf("packet = %s\n", pkt->data);
|
||||||
|
#endif
|
||||||
|
gdb_event_t event = gdbstub_process_packet(gdbstub, pkt, args);
|
||||||
|
free(pkt);
|
||||||
|
|
||||||
|
gdb_action_t act = gdbstub_handle_event(gdbstub, event, args);
|
||||||
|
switch (act) {
|
||||||
|
case ACT_RESUME:
|
||||||
|
gdbstub_act_resume(gdbstub);
|
||||||
|
break;
|
||||||
|
case ACT_SHUTDOWN:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gdbstub_close(gdbstub_t *gdbstub)
|
||||||
|
{
|
||||||
|
/* Use thread ID to make sure the thread was created */
|
||||||
|
if (gdbstub->priv->tid != 0) {
|
||||||
|
__atomic_store_n(&thread_stop, true, __ATOMIC_RELAXED);
|
||||||
|
pthread_join(gdbstub->priv->tid, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn_close(&gdbstub->priv->conn);
|
||||||
|
free(gdbstub->priv);
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
#include "packet.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static void pktbuf_clear(pktbuf_t *pktbuf)
|
||||||
|
{
|
||||||
|
pktbuf->end_pos = -1;
|
||||||
|
pktbuf->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DEFAULT_CAP (10)
|
||||||
|
bool pktbuf_init(pktbuf_t *pktbuf)
|
||||||
|
{
|
||||||
|
pktbuf->cap = DEFAULT_CAP;
|
||||||
|
pktbuf->data = calloc(1, (1 << pktbuf->cap) * sizeof(uint8_t));
|
||||||
|
pktbuf_clear(pktbuf);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t pktbuf_fill_from_file(pktbuf_t *pktbuf, int fd)
|
||||||
|
{
|
||||||
|
assert((1 << pktbuf->cap) >= pktbuf->size);
|
||||||
|
|
||||||
|
/* enlarge the buffer to read from file when it is full */
|
||||||
|
if ((1 << pktbuf->cap) == pktbuf->size) {
|
||||||
|
pktbuf->cap++;
|
||||||
|
pktbuf->data =
|
||||||
|
realloc(pktbuf->data, (1 << pktbuf->cap) * sizeof(uint8_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
int left = (1 << pktbuf->cap) - pktbuf->size;
|
||||||
|
uint8_t *buf = pktbuf->data + pktbuf->size;
|
||||||
|
ssize_t nread = read(fd, buf, left);
|
||||||
|
|
||||||
|
if (nread > 0)
|
||||||
|
pktbuf->size += nread;
|
||||||
|
|
||||||
|
return nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pktbuf_is_complete(pktbuf_t *pktbuf)
|
||||||
|
{
|
||||||
|
int head = -1;
|
||||||
|
|
||||||
|
/* skip to the head of next packet */
|
||||||
|
for (int i = 0; i < pktbuf->size; i++) {
|
||||||
|
if (pktbuf->data[i] == '$') {
|
||||||
|
head = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (head < 0) {
|
||||||
|
pktbuf_clear(pktbuf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (head > 0) {
|
||||||
|
/* moving memory for a valid packet */
|
||||||
|
memmove(pktbuf->data, pktbuf->data + head, pktbuf->size - head);
|
||||||
|
pktbuf->size -= head;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check the end of the buffer */
|
||||||
|
uint8_t *end_pos_ptr = memchr(pktbuf->data, '#', pktbuf->size);
|
||||||
|
if (end_pos_ptr == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int end_pos = (end_pos_ptr - pktbuf->data) + CSUM_SIZE;
|
||||||
|
if (end_pos > pktbuf->size)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
pktbuf->end_pos = end_pos;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
packet_t *pktbuf_pop_packet(pktbuf_t *pktbuf)
|
||||||
|
{
|
||||||
|
if (pktbuf->end_pos == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int old_pkt_size = pktbuf->end_pos + 1;
|
||||||
|
packet_t *pkt = calloc(1, sizeof(packet_t) + old_pkt_size + 1);
|
||||||
|
memcpy(pkt->data, pktbuf->data, old_pkt_size);
|
||||||
|
pkt->end_pos = pktbuf->end_pos;
|
||||||
|
pkt->data[old_pkt_size] = 0;
|
||||||
|
|
||||||
|
memmove(pktbuf->data, pktbuf->data + old_pkt_size + 1,
|
||||||
|
pktbuf->size - old_pkt_size);
|
||||||
|
pktbuf->size -= old_pkt_size;
|
||||||
|
pktbuf->end_pos = -1;
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pktbuf_destroy(pktbuf_t *pktbuf)
|
||||||
|
{
|
||||||
|
free(pktbuf->data);
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
#include "regbuf.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
bool regbuf_init(regbuf_t *reg)
|
||||||
|
{
|
||||||
|
/* Default to 8 bytes register size. */
|
||||||
|
reg->sz = 8;
|
||||||
|
reg->buf = malloc(reg->sz);
|
||||||
|
|
||||||
|
if (!reg->buf)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *regbuf_get(regbuf_t *reg, size_t reg_sz)
|
||||||
|
{
|
||||||
|
if (reg_sz <= reg->sz)
|
||||||
|
return reg->buf;
|
||||||
|
|
||||||
|
free(reg->buf);
|
||||||
|
|
||||||
|
while (reg_sz > reg->sz) {
|
||||||
|
reg->sz <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
reg->buf = malloc(reg->sz);
|
||||||
|
return reg->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void regbuf_destroy(regbuf_t *reg)
|
||||||
|
{
|
||||||
|
free(reg->buf);
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include "utils/csum.h"
|
||||||
|
|
||||||
|
uint8_t compute_checksum(char *buf, size_t len)
|
||||||
|
{
|
||||||
|
uint8_t csum = 0;
|
||||||
|
for (size_t i = 0; i < len; ++i)
|
||||||
|
csum += buf[i];
|
||||||
|
return csum;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include "utils/translate.h"
|
||||||
|
|
||||||
|
static char hexchars[] = "0123456789abcdef";
|
||||||
|
|
||||||
|
void hex_to_str(uint8_t *num, char *str, int bytes)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < bytes; i++) {
|
||||||
|
uint8_t ch = *(num + i);
|
||||||
|
*(str + i * 2) = hexchars[ch >> 4];
|
||||||
|
*(str + i * 2 + 1) = hexchars[ch & 0xf];
|
||||||
|
}
|
||||||
|
str[bytes * 2] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t char_to_hex(char ch)
|
||||||
|
{
|
||||||
|
const uint8_t letter = ch & 0x40;
|
||||||
|
const uint8_t offset = (letter >> 3) | (letter >> 6);
|
||||||
|
return (ch + offset) & 0xf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void str_to_hex(char *str, uint8_t *num, int bytes)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < bytes; i++) {
|
||||||
|
uint8_t ch_high = char_to_hex(*(str + i * 2));
|
||||||
|
uint8_t ch_low = char_to_hex(*(str + i * 2 + 1));
|
||||||
|
|
||||||
|
*(num + i) = (ch_high << 4) | ch_low;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int unescape(char *msg, char *end)
|
||||||
|
{
|
||||||
|
char *w = msg;
|
||||||
|
char *r = msg;
|
||||||
|
while (r < end) {
|
||||||
|
if (*r == '}') {
|
||||||
|
*w = *(r + 1) ^ 0x20;
|
||||||
|
r += 2;
|
||||||
|
} else {
|
||||||
|
*w = *r;
|
||||||
|
r += 1;
|
||||||
|
}
|
||||||
|
w += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return w - msg;
|
||||||
|
}
|
Loading…
Reference in New Issue