├── .clang-format ├── .github └── workflows │ ├── build-check.yml │ └── format-check.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── emu ├── Makefile └── src │ └── emu.c ├── include ├── conn.h ├── gdb_signal.h ├── gdbstub.h ├── packet.h ├── regbuf.h └── utils │ ├── csum.h │ ├── log.h │ └── translate.h ├── scripts ├── install-git-hooks └── pre-commit.hook ├── src ├── conn.c ├── gdbstub.c ├── packet.c ├── regbuf.c └── utils │ ├── csum.c │ └── translate.c └── tests └── test.c /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | Language: Cpp 3 | MaxEmptyLinesToKeep: 3 4 | IndentCaseLabels: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortCaseLabelsOnASingleLine: false 7 | AllowShortLoopsOnASingleLine: false 8 | DerivePointerAlignment: false 9 | PointerAlignment: Right 10 | SpaceAfterCStyleCast: true 11 | TabWidth: 4 12 | UseTab: Never 13 | IndentWidth: 4 14 | BreakBeforeBraces: Linux 15 | AccessModifierOffset: -4 16 | -------------------------------------------------------------------------------- /.github/workflows/build-check.yml: -------------------------------------------------------------------------------- 1 | name: Build Check 2 | 3 | on: 4 | push: # Runs on push to any branch 5 | pull_request: # Runs on PRs targeting any branch 6 | 7 | jobs: 8 | build: 9 | name: Build Project 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Install build tools 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y build-essential 20 | 21 | - name: Build project using Makefile 22 | run: | 23 | echo "Building library" 24 | make 25 | echo "Building emu" 26 | make build-emu -------------------------------------------------------------------------------- /.github/workflows/format-check.yml: -------------------------------------------------------------------------------- 1 | name: Format Check 2 | 3 | on: 4 | push: # Runs on push to any branch 5 | pull_request: # Runs on PRs targeting any branch 6 | 7 | jobs: 8 | format: 9 | name: Check C/C++ Formatting 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Install clang-format 17 | run: | 18 | sudo apt-get update 19 | sudo apt-get install -y clang-format 20 | 21 | - name: Check C/C++ code formatting 22 | run: | 23 | echo "Checking formatting..." 24 | # Find C source and header files, then check formatting with clang-format 25 | find . -name '*.c' -o -name '*.h' | xargs clang-format --dry-run -Werror 26 | echo "Formatting check passed." -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 RinHizakura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Iinclude -Wall -Wextra -MMD #-Werror 2 | 3 | CURDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 4 | 5 | O ?= build 6 | ifeq ($(O), $(CURDIR)/build) 7 | OUT := $(CURDIR) 8 | else 9 | OUT := $(O) 10 | endif 11 | 12 | LIBGDBSTUB = $(OUT)/libgdbstub.a 13 | SHELL_HACK := $(shell mkdir -p $(OUT)) 14 | GIT_HOOKS := .git/hooks/applied 15 | 16 | LIBSRCS = $(shell find ./src -name '*.c') 17 | _LIB_OBJ = $(notdir $(LIBSRCS)) 18 | LIB_OBJ = $(_LIB_OBJ:%.c=$(OUT)/%.o) 19 | 20 | TEST_OBJ = $(OUT)/test.obj 21 | TEST_BIN = $(OUT)/test.bin 22 | 23 | vpath %.c $(sort $(dir $(LIBSRCS))) 24 | .PHONY: all debug test clean 25 | 26 | all: CFLAGS += -O3 27 | all: LDFLAGS += -O3 28 | all: $(LIBGDBSTUB) $(GIT_HOOKS) 29 | 30 | debug: CFLAGS += -O3 -g -DDEBUG 31 | debug: LDFLAGS += -O3 32 | debug: $(LIBGDBSTUB) 33 | 34 | test: $(TEST_BIN) 35 | 36 | $(GIT_HOOKS): 37 | @scripts/install-git-hooks 38 | @echo 39 | 40 | $(OUT)/%.o: %.c 41 | $(CC) -c $(CFLAGS) $< -o $@ 42 | 43 | $(LIBGDBSTUB): $(LIB_OBJ) 44 | $(AR) -rcs $@ $^ 45 | 46 | $(TEST_OBJ): tests/test.c 47 | riscv64-unknown-elf-gcc -march=rv64g -Wl,-Ttext=0x0 -nostdlib -g -o $@ $< 48 | 49 | $(TEST_BIN): $(TEST_OBJ) 50 | riscv64-unknown-elf-objcopy -O binary $< $@ 51 | 52 | 53 | build-emu: $(LIBGDBSTUB) 54 | $(MAKE) -C emu 55 | 56 | run-gdbstub: $(TEST_BIN) build-emu 57 | emu/build/emu $(TEST_BIN) 58 | 59 | GDBSTUB_COMM = 127.0.0.1:1234 60 | run-gdb: $(TEST_OBJ) 61 | riscv64-unknown-elf-gdb \ 62 | -ex "file $(TEST_OBJ)" \ 63 | -ex "set debug remote 1" \ 64 | -ex "target remote $(GDBSTUB_COMM)" \ 65 | 66 | clean: 67 | $(RM) $(LIB_OBJ) 68 | $(RM) $(LIBGDBSTUB) 69 | $(RM) $(TEST_BIN) 70 | $(RM) $(TEST_OBJ) 71 | $(RM) $(OUT)/*.d 72 | 73 | -include $(OUT)/*.d 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-gdbstub 2 | 3 | `mini-gdbstub` is an implementation of the 4 | [GDB Remote Serial Protocol](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html) 5 | that gives your emulators debugging capabilities. 6 | 7 | ## Usage 8 | 9 | The very first thing you should do is to build the statically-linked library `libgdbstub.a`. 10 | ``` 11 | make 12 | ``` 13 | 14 | To use the library in your project, you should include the file `gdbstub.h` first. 15 | Then, you have to initialize the pre-allocated structure `gdbstub_t` with `gdbstub_init`. 16 | 17 | ```c 18 | bool gdbstub_init(gdbstub_t *gdbstub, struct target_ops *ops, arch_info_t arch, char *s); 19 | ``` 20 | 21 | The parameters `s` is the easiest one to understand. It is a string of the socket 22 | which your emulator would like to bind as gdb server. 23 | 24 | The `struct target_ops` is made up of function pointers. Each member function represents an 25 | abstraction of your emulator's operation. The following lists the requirement 26 | that should be provided for each method: 27 | 28 | Method | Description 29 | ---------------|------------------ 30 | `cont` | Run the emulator until hitting breakpoint or exit. 31 | `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. 32 | `get_reg_bytes` | Get the register size in bytes for the register specified by `regno` as a return value. 33 | `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. 34 | `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. 35 | `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. 36 | `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. 37 | `set_bp` | Set type `type` breakpoint on the address specified by `addr`. Return true if we set the breakpoint successfully, otherwise return false. 38 | `del_bp` | Delete type `type` breakpoint on the address specified by `addr`. Return true if we delete the breakpoint successfully, otherwise return false. 39 | `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. 40 | `set_cpu` | Set the debug target CPU to `cpuid`. 41 | `get_cpu` | Get the current debug target CPU `cpuid` as return value. 42 | 43 | ```c 44 | struct target_ops { 45 | gdb_action_t (*cont)(void *args); 46 | gdb_action_t (*stepi)(void *args); 47 | size_t (*get_reg_bytes)(int regno); 48 | int (*read_reg)(void *args, int regno, void *value); 49 | int (*write_reg)(void *args, int regno, void* value); 50 | int (*read_mem)(void *args, size_t addr, size_t len, void *val); 51 | int (*write_mem)(void *args, size_t addr, size_t len, void *val); 52 | bool (*set_bp)(void *args, size_t addr, bp_type_t type); 53 | bool (*del_bp)(void *args, size_t addr, bp_type_t type); 54 | void (*on_interrupt)(void *args); 55 | 56 | void (*set_cpu)(void *args, int cpuid); 57 | int (*get_cpu)(void *args); 58 | }; 59 | ``` 60 | 61 | For `cont` and `stepi` which are used to process the execution of emulator, their return type 62 | should be `gdb_action_t`. After performing the relevant operation, you should return `ACT_RESUME` 63 | to continue debugging; otherwise, return `ACT_SHUTDOWN` to finish debugging. The library 64 | typically uses `ACT_NONE` to take no action. 65 | 66 | ```c 67 | typedef enum { 68 | ACT_NONE, 69 | ACT_RESUME, 70 | ACT_SHUTDOWN, 71 | } gdb_action_t; 72 | ``` 73 | 74 | For `set_bp` and `del_bp`, the type of breakpoint which should be set or deleted is described 75 | in the type `bp_type_t`. In fact, its value will always be `BP_SOFTWARE` currently. 76 | 77 | ```c 78 | typedef enum { 79 | BP_SOFTWARE = 0, 80 | } bp_type_t; 81 | ``` 82 | 83 | Another structure you have to declare is `arch_info_t`. You must explicitly specify about the 84 | following field within `arch_info_t` while integrating into your emulator: 85 | * `smp`: Number of target's CPU 86 | * `reg_num`: Number of target's registers 87 | 88 | The `target_desc` is an optional member which could be 89 | `TARGET_RV32`, `TARGET_RV64` if the emulator is RISC-V 32-bit or 64-bit instruction 90 | set architecture or `TARGET_X86_64` if the emulator is x86_64 instruction set architecture. Alternatively, it can be a custom target description document 91 | string used by gdb. If none of these apply, simply set it to NULL. 92 | 93 | * Although the value of `reg_num` may be determined by `target_desc`, those 94 | members are still required to be filled correctly. 95 | 96 | ```c 97 | typedef struct { 98 | char *target_desc; 99 | int smp; 100 | int reg_num; 101 | } arch_info_t; 102 | ``` 103 | 104 | After startup, we can use `gdbstub_run` to run the emulator as gdbstub. The `args` 105 | can be used to pass the argument to any function in `struct target_ops`. 106 | 107 | ```c 108 | bool gdbstub_run(gdbstub_t *gdbstub, void *args); 109 | ``` 110 | 111 | When exiting from `gdbstub_run`, `gdbstub_close` should be called to recycle the resource on 112 | the initialization. 113 | 114 | ```c 115 | void gdbstub_close(gdbstub_t *gdbstub); 116 | ``` 117 | 118 | Finally, you can build you project with the statically-linked library `libgdbstub.a` now! 119 | Additionally, it is advised that you check the reference emulator in the directory `emu,` which 120 | demonstrates how to integrate `mini-gdbstub` into your project. 121 | 122 | ## Reference 123 | ### Project 124 | * [bet4it/gdbserver](https://github.com/bet4it/gdbserver) 125 | * [devbored/minigdbstub](https://github.com/devbored/minigdbstub) 126 | ### Article 127 | * [Howto: GDB Remote Serial Protocol](https://www.embecosm.com/appnotes/ean4/embecosm-howto-rsp-server-ean4-issue-2.html) 128 | * [TLMBoy: Implementing the GDB Remote Serial Protocol](https://www.chciken.com/tlmboy/2022/04/03/gdb-z80.html) 129 | -------------------------------------------------------------------------------- /emu/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -I../include -Wall -Wextra 2 | LDFLAGS = 3 | 4 | CURDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) 5 | OUT ?= build 6 | BIN = $(OUT)/emu 7 | SHELL_HACK := $(shell mkdir -p $(OUT)) 8 | 9 | CSRCS = $(shell find ./src -name '*.c') 10 | _COBJ = $(notdir $(CSRCS)) 11 | COBJ = $(_COBJ:%.c=$(OUT)/%.o) 12 | 13 | LIBGDBSTUB = ../build/libgdbstub.a 14 | 15 | vpath %.c $(sort $(dir $(CSRCS))) 16 | 17 | all: clean $(BIN) 18 | 19 | $(OUT)/%.o: %.c 20 | $(CC) -c $(CFLAGS) $< -o $@ 21 | 22 | $(BIN): $(COBJ) $(LIBGDBSTUB) 23 | $(CC) $^ -o $@ $(LDFLAGS) 24 | 25 | clean: 26 | $(RM) $(COBJ) 27 | $(RM) $(BIN) 28 | -------------------------------------------------------------------------------- /emu/src/emu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "gdbstub.h" 9 | 10 | #define MEM_SIZE (1024) 11 | struct mem { 12 | uint8_t *mem; 13 | size_t code_size; 14 | }; 15 | 16 | struct emu { 17 | struct mem m; 18 | uint64_t x[32]; 19 | uint64_t pc; 20 | 21 | bool bp_is_set; 22 | uint64_t bp_addr; 23 | 24 | bool halt; 25 | 26 | gdbstub_t gdbstub; 27 | }; 28 | 29 | static inline void emu_halt(struct emu *emu) 30 | { 31 | __atomic_store_n(&emu->halt, true, __ATOMIC_RELAXED); 32 | } 33 | 34 | static inline void emu_start_run(struct emu *emu) 35 | { 36 | __atomic_store_n(&emu->halt, false, __ATOMIC_RELAXED); 37 | } 38 | 39 | static inline bool emu_is_halt(struct emu *emu) 40 | { 41 | return __atomic_load_n(&emu->halt, __ATOMIC_RELAXED); 42 | } 43 | 44 | #define asr_i32(value, amount) ((int32_t) (value) >> (amount)) 45 | static void emu_exec(struct emu *emu, uint32_t inst) 46 | { 47 | uint8_t opcode = inst & 0x7f; 48 | uint8_t rd = (inst >> 7) & 0x1f; 49 | uint8_t rs1 = (inst >> 15) & 0x1f; 50 | uint8_t rs2 = (inst >> 20) & 0x1f; 51 | uint8_t funct3 = (inst >> 12) & 0x7; 52 | uint8_t funct7 = (inst >> 25) & 0x7f; 53 | 54 | uint64_t imm; 55 | 56 | #ifdef DEBUG 57 | printf("[%4lx] opcode: %2x, funct3: %x, funct7: %2x\n", emu->pc - 4, opcode, 58 | funct3, funct7); 59 | #endif 60 | 61 | switch (opcode) { 62 | case 0x13: 63 | switch (funct3) { 64 | case 0x0: 65 | // addi 66 | imm = asr_i32(inst & 0xfff00000, 20); 67 | emu->x[rd] = emu->x[rd] + imm; 68 | return; 69 | default: 70 | break; 71 | } 72 | break; 73 | case 0x23: 74 | switch (funct3) { 75 | case 0x3: 76 | // sd 77 | imm = asr_i32(inst & 0xfe000000, 20) | ((inst >> 7) & 0x1f); 78 | memcpy((void *) emu->m.mem + emu->x[rs1] + imm, &emu->x[rs2], 8); 79 | return; 80 | default: 81 | break; 82 | } 83 | break; 84 | case 0x33: 85 | switch (funct3) { 86 | case 0x0: 87 | switch (funct7) { 88 | case 0x00: 89 | // add 90 | emu->x[rd] = emu->x[rs1] + emu->x[rs2]; 91 | return; 92 | default: 93 | break; 94 | } 95 | break; 96 | } 97 | break; 98 | default: 99 | break; 100 | } 101 | 102 | printf("Not implemented or invalid instruction@%lx\n", emu->pc - 4); 103 | printf("opcode:%x, funct3:%x, funct7:%x\n", opcode, funct3, funct7); 104 | } 105 | 106 | static void emu_init(struct emu *emu) 107 | { 108 | memset(emu, 0, sizeof(struct emu)); 109 | emu->pc = 0; 110 | emu->x[2] = MEM_SIZE; 111 | emu->bp_addr = -1; 112 | emu_halt(emu); 113 | } 114 | 115 | static int init_mem(struct mem *m, const char *filename) 116 | { 117 | if (!filename) { 118 | return -1; 119 | } 120 | 121 | FILE *fp = fopen(filename, "rb"); 122 | if (!fp) { 123 | return -1; 124 | } 125 | 126 | fseek(fp, 0, SEEK_END); 127 | size_t sz = ftell(fp) * sizeof(uint8_t); 128 | rewind(fp); 129 | 130 | m->mem = malloc(MEM_SIZE); 131 | if (!m->mem) { 132 | fclose(fp); 133 | return -1; 134 | } 135 | memset(m->mem, 0, MEM_SIZE); 136 | size_t read_size = fread(m->mem, sizeof(uint8_t), sz, fp); 137 | 138 | if (read_size != sz) { 139 | fclose(fp); 140 | return -1; 141 | } 142 | fclose(fp); 143 | m->code_size = read_size; 144 | return 0; 145 | } 146 | 147 | static void free_mem(struct mem *m) 148 | { 149 | free(m->mem); 150 | } 151 | 152 | static size_t emu_get_reg_bytes(int regno __attribute__((unused))) 153 | { 154 | return 8; 155 | } 156 | 157 | static int emu_read_reg(void *args, int regno, void *reg_value) 158 | { 159 | struct emu *emu = (struct emu *) args; 160 | if (regno > 32) { 161 | return EFAULT; 162 | } 163 | 164 | if (regno == 32) { 165 | memcpy(reg_value, &emu->pc, 4); 166 | } else { 167 | memcpy(reg_value, &emu->x[regno], 4); 168 | } 169 | return 0; 170 | } 171 | 172 | static int emu_write_reg(void *args, int regno, void *data) 173 | { 174 | struct emu *emu = (struct emu *) args; 175 | 176 | if (regno > 32) { 177 | return EFAULT; 178 | } 179 | 180 | if (regno == 32) { 181 | memcpy(&emu->pc, data, 4); 182 | } else { 183 | memcpy(&emu->x[regno], data, 4); 184 | } 185 | return 0; 186 | } 187 | 188 | static int emu_read_mem(void *args, size_t addr, size_t len, void *val) 189 | { 190 | struct emu *emu = (struct emu *) args; 191 | if (addr + len > MEM_SIZE) { 192 | return EFAULT; 193 | } 194 | memcpy(val, (void *) emu->m.mem + addr, len); 195 | return 0; 196 | } 197 | 198 | static int emu_write_mem(void *args, size_t addr, size_t len, void *val) 199 | { 200 | struct emu *emu = (struct emu *) args; 201 | if (addr + len > MEM_SIZE) { 202 | return EFAULT; 203 | } 204 | memcpy((void *) emu->m.mem + addr, val, len); 205 | return 0; 206 | } 207 | 208 | static gdb_action_t emu_cont(void *args) 209 | { 210 | struct emu *emu = (struct emu *) args; 211 | 212 | emu_start_run(emu); 213 | while (emu->pc < emu->m.code_size && emu->pc != emu->bp_addr && 214 | !emu_is_halt(emu)) { 215 | uint32_t inst; 216 | emu_read_mem(args, emu->pc, 4, &inst); 217 | emu->pc += 4; 218 | emu_exec(emu, inst); 219 | } 220 | 221 | return ACT_RESUME; 222 | } 223 | 224 | static gdb_action_t emu_stepi(void *args) 225 | { 226 | struct emu *emu = (struct emu *) args; 227 | 228 | emu_start_run(emu); 229 | if (emu->pc < emu->m.code_size) { 230 | uint32_t inst; 231 | emu_read_mem(args, emu->pc, 4, &inst); 232 | emu->pc += 4; 233 | emu_exec(emu, inst); 234 | } 235 | 236 | return ACT_RESUME; 237 | } 238 | 239 | static bool emu_set_bp(void *args, size_t addr, bp_type_t type) 240 | { 241 | struct emu *emu = (struct emu *) args; 242 | if (type != BP_SOFTWARE || emu->bp_is_set) 243 | return false; 244 | 245 | emu->bp_is_set = true; 246 | emu->bp_addr = addr; 247 | return true; 248 | } 249 | 250 | static bool emu_del_bp(void *args, size_t addr, bp_type_t type) 251 | { 252 | struct emu *emu = (struct emu *) args; 253 | 254 | // It's fine when there's no matching breakpoint, just doing nothing 255 | if (type != BP_SOFTWARE || !emu->bp_is_set || emu->bp_addr != addr) 256 | return true; 257 | 258 | emu->bp_is_set = false; 259 | emu->bp_addr = 0; 260 | return true; 261 | } 262 | 263 | static void emu_on_interrupt(void *args) 264 | { 265 | struct emu *emu = (struct emu *) args; 266 | emu_halt(emu); 267 | } 268 | 269 | struct target_ops emu_ops = { 270 | .get_reg_bytes = emu_get_reg_bytes, 271 | .read_reg = emu_read_reg, 272 | .write_reg = emu_write_reg, 273 | .read_mem = emu_read_mem, 274 | .write_mem = emu_write_mem, 275 | .cont = emu_cont, 276 | .stepi = emu_stepi, 277 | .set_bp = emu_set_bp, 278 | .del_bp = emu_del_bp, 279 | .on_interrupt = emu_on_interrupt, 280 | }; 281 | 282 | int main(int argc, char *argv[]) 283 | { 284 | if (argc != 2) { 285 | return -1; 286 | } 287 | 288 | struct emu emu; 289 | emu_init(&emu); 290 | 291 | if (init_mem(&emu.m, argv[1]) == -1) { 292 | return -1; 293 | } 294 | 295 | if (!gdbstub_init(&emu.gdbstub, &emu_ops, 296 | (arch_info_t){ 297 | .smp = 1, 298 | .reg_num = 33, 299 | .target_desc = TARGET_RV32, 300 | }, 301 | "127.0.0.1:1234")) { 302 | fprintf(stderr, "Fail to create socket.\n"); 303 | return -1; 304 | } 305 | 306 | if (!gdbstub_run(&emu.gdbstub, (void *) &emu)) { 307 | fprintf(stderr, "Fail to run in debug mode.\n"); 308 | return -1; 309 | } 310 | gdbstub_close(&emu.gdbstub); 311 | free_mem(&emu.m); 312 | 313 | return 0; 314 | } 315 | -------------------------------------------------------------------------------- /include/conn.h: -------------------------------------------------------------------------------- 1 | #ifndef CONN_H 2 | #define CONN_H 3 | 4 | #include 5 | #include 6 | #include "packet.h" 7 | 8 | #define MAX_SEND_PACKET_SIZE (0x1000) 9 | #define MAX_DATA_PAYLOAD (MAX_SEND_PACKET_SIZE - (2 + CSUM_SIZE + 2)) 10 | 11 | typedef struct { 12 | int listen_fd; 13 | int socket_fd; 14 | 15 | pktbuf_t pktbuf; 16 | } conn_t; 17 | 18 | bool conn_init(conn_t *conn, char *addr_str, int port); 19 | void conn_recv_packet(conn_t *conn); 20 | packet_t *conn_pop_packet(conn_t *conn); 21 | bool conn_try_recv_intr(conn_t *conn); 22 | void conn_send_str(conn_t *conn, char *str); 23 | void conn_send_pktstr(conn_t *conn, char *pktstr); 24 | void conn_close(conn_t *conn); 25 | #endif 26 | -------------------------------------------------------------------------------- /include/gdb_signal.h: -------------------------------------------------------------------------------- 1 | #ifndef GDB_SIGNAL_H 2 | #define GDB_SIGNAL_H 3 | 4 | #define GDB_SIGNAL_TRAP 5 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /include/gdbstub.h: -------------------------------------------------------------------------------- 1 | #ifndef GDBSTUB_H 2 | #define GDBSTUB_H 3 | 4 | #include 5 | #include 6 | 7 | #define TARGET_RV32 \ 8 | "riscv:rv32" 9 | #define TARGET_RV64 \ 10 | "riscv:rv64" 11 | #define TARGET_X86_64 \ 12 | "i386:x86-64" 14 | 15 | typedef enum { 16 | EVENT_NONE, 17 | EVENT_CONT, 18 | EVENT_DETACH, 19 | EVENT_STEP, 20 | } gdb_event_t; 21 | 22 | typedef enum { 23 | ACT_NONE, 24 | ACT_RESUME, 25 | ACT_SHUTDOWN, 26 | } gdb_action_t; 27 | 28 | typedef enum { 29 | BP_SOFTWARE = 0, 30 | } bp_type_t; 31 | 32 | struct target_ops { 33 | gdb_action_t (*cont)(void *args); 34 | gdb_action_t (*stepi)(void *args); 35 | size_t (*get_reg_bytes)(int regno); 36 | int (*read_reg)(void *args, int regno, void *value); 37 | int (*write_reg)(void *args, int regno, void *value); 38 | int (*read_mem)(void *args, size_t addr, size_t len, void *val); 39 | int (*write_mem)(void *args, size_t addr, size_t len, void *val); 40 | bool (*set_bp)(void *args, size_t addr, bp_type_t type); 41 | bool (*del_bp)(void *args, size_t addr, bp_type_t type); 42 | void (*on_interrupt)(void *args); 43 | 44 | void (*set_cpu)(void *args, int cpuid); 45 | int (*get_cpu)(void *args); 46 | }; 47 | 48 | typedef struct gdbstub_private gdbstub_private_t; 49 | 50 | typedef struct { 51 | char *target_desc; 52 | int smp; 53 | int reg_num; 54 | } arch_info_t; 55 | 56 | typedef struct { 57 | struct target_ops *ops; 58 | arch_info_t arch; 59 | gdbstub_private_t *priv; 60 | } gdbstub_t; 61 | 62 | bool gdbstub_init(gdbstub_t *gdbstub, 63 | struct target_ops *ops, 64 | arch_info_t arch, 65 | char *s); 66 | bool gdbstub_run(gdbstub_t *gdbstub, void *args); 67 | void gdbstub_close(gdbstub_t *gdbstub); 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /include/packet.h: -------------------------------------------------------------------------------- 1 | #ifndef PACKET_H 2 | #define PACKET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define STR_ACK "+" 9 | #define INTR_CHAR '\x03' 10 | 11 | #define CSUM_SIZE (2) 12 | 13 | typedef struct { 14 | int end_pos; 15 | uint8_t data[]; 16 | } packet_t; 17 | 18 | /* A naive packet buffer: maintain a big array to fill the packet */ 19 | typedef struct { 20 | int size; /* the size for all valid characters in data buffer */ 21 | int cap; /* the capacity (1 << cap) of the data buffer */ 22 | int end_pos; /* the end position of the first packet in data buffer */ 23 | uint8_t *data; 24 | } pktbuf_t; 25 | 26 | bool pktbuf_init(pktbuf_t *pktbuf); 27 | ssize_t pktbuf_fill_from_file(pktbuf_t *pktbuf, int fd); 28 | bool pktbuf_is_complete(pktbuf_t *pktbuf); 29 | packet_t *pktbuf_pop_packet(pktbuf_t *pktbuf); 30 | void pktbuf_destroy(pktbuf_t *pktbuf); 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /include/regbuf.h: -------------------------------------------------------------------------------- 1 | #ifndef REG_H 2 | #define REG_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | void *buf; 9 | size_t sz; 10 | } regbuf_t; 11 | 12 | bool regbuf_init(regbuf_t *reg); 13 | void *regbuf_get(regbuf_t *reg, size_t reg_sz); 14 | void regbuf_destroy(regbuf_t *reg); 15 | #endif 16 | -------------------------------------------------------------------------------- /include/utils/csum.h: -------------------------------------------------------------------------------- 1 | #ifndef CSUM_H 2 | #define CSUM_H 3 | 4 | #include 5 | #include 6 | 7 | uint8_t compute_checksum(char *buf, size_t len); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /include/utils/log.h: -------------------------------------------------------------------------------- 1 | #ifndef LOG_H 2 | #define LOG_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static inline void log_print(const char *format, ...) 11 | { 12 | va_list vargs; 13 | va_start(vargs, format); 14 | vfprintf(stderr, format, vargs); 15 | va_end(vargs); 16 | } 17 | 18 | #define warn(format, ...) \ 19 | log_print("ERRNO: %s\n" format, strerror(errno), ##__VA_ARGS__) 20 | #define info(format, ...) log_print(format, ##__VA_ARGS__) 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /include/utils/translate.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include 5 | 6 | void hex_to_str(uint8_t *num, char *str, int bytes); 7 | void str_to_hex(char *str, uint8_t *num, int bytes); 8 | int unescape(char *msg, char *end); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /scripts/install-git-hooks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! test -d .git; then 4 | echo "Execute scripts/install-git-hooks in the top-level directory." 5 | exit 1 6 | fi 7 | 8 | ln -sf ../../scripts/pre-commit.hook .git/hooks/pre-commit || exit 1 9 | chmod +x .git/hooks/pre-commit 10 | 11 | touch .git/hooks/applied || exit 1 12 | 13 | echo 14 | echo "Git hooks are installed successfully." 15 | 16 | -------------------------------------------------------------------------------- /scripts/pre-commit.hook: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RETURN=0 4 | CLANG_FORMAT=$(which clang-format) 5 | if [ $? -ne 0 ]; then 6 | echo "[!] clang-format not installed. Unable to check source file format policy." >&2 7 | exit 1 8 | fi 9 | 10 | DIFF=$(which colordiff) 11 | if [ $? -ne 0 ]; then 12 | DIFF=diff 13 | fi 14 | 15 | FILES=`git diff --cached --name-only --diff-filter=ACMR | grep -E "\.(c|cpp|h)$"` 16 | for FILE in $FILES; do 17 | nf=`git checkout-index --temp $FILE | cut -f 1` 18 | tempdir=`mktemp -d` || exit 1 19 | newfile=`mktemp ${tempdir}/${nf}.XXXXXX` || exit 1 20 | basename=`basename $FILE` 21 | 22 | source="${tempdir}/${basename}" 23 | mv $nf $source 24 | cp .clang-format $tempdir 25 | $CLANG_FORMAT $source > $newfile 2>> /dev/null 26 | $DIFF -u -p -B --label="modified $FILE" --label="expected coding style" \ 27 | "${source}" "${newfile}" 28 | r=$? 29 | rm -rf "${tempdir}" 30 | if [ $r != 0 ] ; then 31 | echo "[!] $FILE does not follow the consistent coding style." >&2 32 | RETURN=1 33 | fi 34 | if [ $RETURN -eq 1 ]; then 35 | echo "" >&2 36 | echo "Make sure you indent as the following:" >&2 37 | echo " clang-format -i $FILE" >&2 38 | echo 39 | fi 40 | done 41 | 42 | exit $RETURN 43 | -------------------------------------------------------------------------------- /src/conn.c: -------------------------------------------------------------------------------- 1 | #include "conn.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "utils/csum.h" 12 | #include "utils/log.h" 13 | 14 | static bool socket_poll(int socket_fd, int timeout, int events) 15 | { 16 | struct pollfd pfd = (struct pollfd){ 17 | .fd = socket_fd, 18 | .events = events, 19 | }; 20 | 21 | return (poll(&pfd, 1, timeout) > 0) && (pfd.revents & events); 22 | } 23 | 24 | static bool socket_readable(int socket_fd, int timeout) 25 | { 26 | return socket_poll(socket_fd, timeout, POLLIN); 27 | } 28 | 29 | static bool socket_writable(int socket_fd, int timeout) 30 | { 31 | return socket_poll(socket_fd, timeout, POLLOUT); 32 | } 33 | 34 | bool conn_init(conn_t *conn, char *addr_str, int port) 35 | { 36 | if (!pktbuf_init(&conn->pktbuf)) 37 | return false; 38 | 39 | struct in_addr addr_ip; 40 | if (inet_aton(addr_str, &addr_ip) != 0) { 41 | struct sockaddr_in addr; 42 | addr.sin_family = AF_INET; 43 | addr.sin_addr.s_addr = addr_ip.s_addr; 44 | addr.sin_port = htons(port); 45 | conn->listen_fd = socket(AF_INET, SOCK_STREAM, 0); 46 | if (conn->listen_fd < 0) 47 | return false; 48 | 49 | int optval = 1; 50 | if (setsockopt(conn->listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, 51 | sizeof(optval)) < 0) { 52 | warn("Set sockopt fail.\n"); 53 | goto fail; 54 | } 55 | 56 | if (bind(conn->listen_fd, (struct sockaddr *) &addr, sizeof(addr)) < 57 | 0) { 58 | warn("Bind fail.\n"); 59 | goto fail; 60 | } 61 | } else { 62 | struct sockaddr_un addr; 63 | addr.sun_family = AF_UNIX; 64 | strncpy(addr.sun_path, addr_str, sizeof(addr.sun_path) - 1); 65 | unlink(addr_str); 66 | conn->listen_fd = socket(AF_UNIX, SOCK_STREAM, 0); 67 | if (conn->listen_fd < 0) 68 | return false; 69 | 70 | if (bind(conn->listen_fd, (struct sockaddr *) &addr, sizeof(addr)) < 71 | 0) { 72 | warn("Bind fail.\n"); 73 | goto fail; 74 | } 75 | } 76 | 77 | if (listen(conn->listen_fd, 1) < 0) { 78 | warn("Listen fail.\n"); 79 | goto fail; 80 | } 81 | 82 | conn->socket_fd = accept(conn->listen_fd, NULL, NULL); 83 | if (conn->socket_fd < 0) { 84 | warn("Accept fail.\n"); 85 | goto fail; 86 | } 87 | 88 | return true; 89 | 90 | fail: 91 | close(conn->listen_fd); 92 | return false; 93 | } 94 | 95 | void conn_recv_packet(conn_t *conn) 96 | { 97 | while (!pktbuf_is_complete(&conn->pktbuf) && 98 | socket_readable(conn->socket_fd, -1)) { 99 | ssize_t nread = pktbuf_fill_from_file(&conn->pktbuf, conn->socket_fd); 100 | if (nread == -1) 101 | break; 102 | } 103 | 104 | conn_send_str(conn, STR_ACK); 105 | } 106 | 107 | packet_t *conn_pop_packet(conn_t *conn) 108 | { 109 | packet_t *pkt = pktbuf_pop_packet(&conn->pktbuf); 110 | 111 | return pkt; 112 | } 113 | 114 | bool conn_try_recv_intr(conn_t *conn) 115 | { 116 | char ch; 117 | 118 | if (!socket_readable(conn->socket_fd, 0)) 119 | return false; 120 | 121 | ssize_t nread = read(conn->socket_fd, &ch, 1); 122 | if (nread != 1) 123 | return false; 124 | 125 | /* FIXME: The character must be INTR_CHAR, otherwise the library 126 | * may work incorrectly. However, I'm not sure if this implementation 127 | * can always meet our expectation (concurrent is so hard QAQ). */ 128 | assert(ch == INTR_CHAR); 129 | return true; 130 | } 131 | 132 | void conn_send_str(conn_t *conn, char *str) 133 | { 134 | size_t len = strlen(str); 135 | 136 | while (len > 0 && socket_writable(conn->socket_fd, -1)) { 137 | ssize_t nwrite = write(conn->socket_fd, str, len); 138 | if (nwrite == -1) 139 | break; 140 | len -= nwrite; 141 | } 142 | } 143 | 144 | void conn_send_pktstr(conn_t *conn, char *pktstr) 145 | { 146 | char packet[MAX_SEND_PACKET_SIZE]; 147 | size_t len = strlen(pktstr); 148 | 149 | /* 2: '$' + '#' 150 | * 2: checksum digits(maximum) 151 | * 1: '\0' */ 152 | assert(len + 2 + CSUM_SIZE + 1 < MAX_SEND_PACKET_SIZE); 153 | 154 | packet[0] = '$'; 155 | memcpy(packet + 1, pktstr, len); 156 | packet[len + 1] = '#'; 157 | 158 | char csum_str[4]; 159 | uint8_t csum = compute_checksum(pktstr, len); 160 | size_t csum_len = snprintf(csum_str, sizeof(csum_str) - 1, "%02x", csum); 161 | assert(csum_len == CSUM_SIZE); 162 | memcpy(packet + len + 2, csum_str, csum_len); 163 | packet[len + 2 + csum_len] = '\0'; 164 | 165 | #ifdef DEBUG 166 | printf("send packet = %s,", packet); 167 | printf(" checksum = %d\n", csum); 168 | #endif 169 | conn_send_str(conn, packet); 170 | } 171 | 172 | void conn_close(conn_t *conn) 173 | { 174 | close(conn->socket_fd); 175 | close(conn->listen_fd); 176 | pktbuf_destroy(&conn->pktbuf); 177 | } 178 | -------------------------------------------------------------------------------- /src/gdbstub.c: -------------------------------------------------------------------------------- 1 | #include "gdbstub.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "conn.h" 7 | #include "gdb_signal.h" 8 | #include "regbuf.h" 9 | #include "utils/csum.h" 10 | #include "utils/translate.h" 11 | 12 | struct gdbstub_private { 13 | conn_t conn; 14 | regbuf_t regbuf; 15 | 16 | pthread_t tid; 17 | bool async_io_enable; 18 | void *args; 19 | }; 20 | 21 | static inline void async_io_enable(struct gdbstub_private *priv) 22 | { 23 | __atomic_store_n(&priv->async_io_enable, true, __ATOMIC_RELAXED); 24 | } 25 | 26 | static inline void async_io_disable(struct gdbstub_private *priv) 27 | { 28 | __atomic_store_n(&priv->async_io_enable, false, __ATOMIC_RELAXED); 29 | } 30 | 31 | static inline bool async_io_is_enable(struct gdbstub_private *priv) 32 | { 33 | return __atomic_load_n(&priv->async_io_enable, __ATOMIC_RELAXED); 34 | } 35 | 36 | static volatile bool thread_stop = false; 37 | static void *socket_reader(gdbstub_t *gdbstub) 38 | { 39 | void *args = gdbstub->priv->args; 40 | 41 | /* This thread will only works when running the gdbstub routine, 42 | * which won't procees on any packets. In this case, we read packet 43 | * in another thread to be able to interrupt the gdbstub. */ 44 | while (!__atomic_load_n(&thread_stop, __ATOMIC_RELAXED)) { 45 | if (async_io_is_enable(gdbstub->priv) && 46 | conn_try_recv_intr(&gdbstub->priv->conn)) { 47 | gdbstub->ops->on_interrupt(args); 48 | } 49 | } 50 | 51 | return NULL; 52 | } 53 | 54 | bool gdbstub_init(gdbstub_t *gdbstub, 55 | struct target_ops *ops, 56 | arch_info_t arch, 57 | char *s) 58 | { 59 | char *addr_str = NULL; 60 | 61 | if (s == NULL || ops == NULL) 62 | return false; 63 | 64 | memset(gdbstub, 0, sizeof(gdbstub_t)); 65 | gdbstub->ops = ops; 66 | gdbstub->arch = arch; 67 | gdbstub->priv = calloc(1, sizeof(struct gdbstub_private)); 68 | if (gdbstub->priv == NULL) 69 | return false; 70 | 71 | // This is a naive implementation to parse the string 72 | addr_str = strdup(s); 73 | char *port_str = strchr(addr_str, ':'); 74 | int port = 0; 75 | if (addr_str == NULL) 76 | goto addr_fail; 77 | 78 | if (port_str != NULL) { 79 | *port_str = '\0'; 80 | port_str += 1; 81 | 82 | if (sscanf(port_str, "%d", &port) <= 0) 83 | goto addr_fail; 84 | } 85 | 86 | if (!regbuf_init(&gdbstub->priv->regbuf)) 87 | goto addr_fail; 88 | 89 | if (!conn_init(&gdbstub->priv->conn, addr_str, port)) 90 | goto conn_fail; 91 | 92 | free(addr_str); 93 | return true; 94 | 95 | conn_fail: 96 | regbuf_destroy(&gdbstub->priv->regbuf); 97 | addr_fail: 98 | free(addr_str); 99 | return false; 100 | } 101 | 102 | #define SEND_ERR(gdbstub, err) conn_send_pktstr(&gdbstub->priv->conn, err) 103 | #define SEND_EPERM(gdbstub) SEND_ERR(gdbstub, "E01") 104 | #define SEND_EINVAL(gdbstub) SEND_ERR(gdbstub, "E22") 105 | 106 | static void process_reg_read(gdbstub_t *gdbstub, void *args) 107 | { 108 | char packet_str[MAX_SEND_PACKET_SIZE]; 109 | 110 | for (int i = 0; i < gdbstub->arch.reg_num; i++) { 111 | size_t reg_sz = gdbstub->ops->get_reg_bytes(i); 112 | void *reg_value = regbuf_get(&gdbstub->priv->regbuf, reg_sz); 113 | 114 | int ret = gdbstub->ops->read_reg(args, i, reg_value); 115 | #ifdef DEBUG 116 | char debug_hex[MAX_SEND_PACKET_SIZE]; 117 | hex_to_str((uint8_t *) reg_value, debug_hex, reg_sz); 118 | printf("reg read = regno %d data 0x%s (size %zu)\n", i, debug_hex, 119 | reg_sz); 120 | #endif 121 | if (!ret) { 122 | hex_to_str((uint8_t *) reg_value, &packet_str[i * reg_sz * 2], 123 | reg_sz); 124 | } else { 125 | sprintf(packet_str, "E%d", ret); 126 | break; 127 | } 128 | } 129 | 130 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 131 | } 132 | 133 | static void process_reg_read_one(gdbstub_t *gdbstub, char *payload, void *args) 134 | { 135 | char packet_str[MAX_SEND_PACKET_SIZE]; 136 | int regno; 137 | 138 | assert(sscanf(payload, "%x", ®no) == 1); 139 | size_t reg_sz = gdbstub->ops->get_reg_bytes(regno); 140 | void *reg_value = regbuf_get(&gdbstub->priv->regbuf, reg_sz); 141 | 142 | int ret = gdbstub->ops->read_reg(args, regno, reg_value); 143 | #ifdef DEBUG 144 | char debug_hex[MAX_SEND_PACKET_SIZE]; 145 | hex_to_str((uint8_t *) reg_value, debug_hex, reg_sz); 146 | printf("reg read = regno %d data 0x%s (size %zu)\n", regno, debug_hex, 147 | reg_sz); 148 | #endif 149 | if (!ret) { 150 | hex_to_str((uint8_t *) reg_value, packet_str, reg_sz); 151 | } else { 152 | sprintf(packet_str, "E%d", ret); 153 | } 154 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 155 | } 156 | 157 | static void process_reg_write(gdbstub_t *gdbstub, char *payload, void *args) 158 | { 159 | for (int i = 0; i < gdbstub->arch.reg_num; i++) { 160 | size_t reg_sz = gdbstub->ops->get_reg_bytes(i); 161 | void *reg_value = regbuf_get(&gdbstub->priv->regbuf, reg_sz); 162 | 163 | str_to_hex(&payload[i * reg_sz * 2], (uint8_t *) reg_value, reg_sz); 164 | #ifdef DEBUG 165 | char debug_hex[MAX_SEND_PACKET_SIZE]; 166 | hex_to_str((uint8_t *) reg_value, debug_hex, reg_sz); 167 | printf("reg write = regno %d data 0x%s (size %zu)\n", i, debug_hex, 168 | reg_sz); 169 | #endif 170 | int ret = gdbstub->ops->write_reg(args, i, reg_value); 171 | if (ret) { 172 | /* FIXME: Even if we fail to modify this register, some 173 | * registers could be writen before. This may not be 174 | * an expected behavior. */ 175 | char packet_str[MAX_SEND_PACKET_SIZE]; 176 | sprintf(packet_str, "E%d", ret); 177 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 178 | return; 179 | } 180 | } 181 | 182 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 183 | } 184 | 185 | static void process_reg_write_one(gdbstub_t *gdbstub, char *payload, void *args) 186 | { 187 | int regno; 188 | char *regno_str = payload; 189 | char *data_str = strchr(payload, '='); 190 | if (data_str) { 191 | *data_str = '\0'; 192 | data_str++; 193 | } 194 | 195 | assert(sscanf(regno_str, "%x", ®no) == 1); 196 | size_t reg_sz = gdbstub->ops->get_reg_bytes(regno); 197 | void *data = regbuf_get(&gdbstub->priv->regbuf, reg_sz); 198 | 199 | assert(strlen(data_str) == reg_sz * 2); 200 | 201 | str_to_hex(data_str, (uint8_t *) data, reg_sz); 202 | #ifdef DEBUG 203 | printf("reg write = regno %d data 0x%s (size %zu)\n", regno, data_str, 204 | reg_sz); 205 | #endif 206 | 207 | int ret = gdbstub->ops->write_reg(args, regno, data); 208 | 209 | if (!ret) { 210 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 211 | } else { 212 | char packet_str[MAX_SEND_PACKET_SIZE]; 213 | sprintf(packet_str, "E%d", ret); 214 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 215 | } 216 | } 217 | 218 | static void process_mem_read(gdbstub_t *gdbstub, char *payload, void *args) 219 | { 220 | size_t maddr, mlen; 221 | assert(sscanf(payload, "%lx,%lx", &maddr, &mlen) == 2); 222 | #ifdef DEBUG 223 | printf("mem read = addr %lx / len %lx\n", maddr, mlen); 224 | #endif 225 | char packet_str[MAX_SEND_PACKET_SIZE]; 226 | 227 | uint8_t *mval = malloc(mlen); 228 | int ret = gdbstub->ops->read_mem(args, maddr, mlen, mval); 229 | if (!ret) { 230 | hex_to_str(mval, packet_str, mlen); 231 | } else { 232 | sprintf(packet_str, "E%d", ret); 233 | } 234 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 235 | free(mval); 236 | } 237 | 238 | static void process_mem_write(gdbstub_t *gdbstub, char *payload, void *args) 239 | { 240 | size_t maddr, mlen; 241 | char *content = strchr(payload, ':'); 242 | if (content) { 243 | *content = '\0'; 244 | content++; 245 | } 246 | assert(sscanf(payload, "%lx,%lx", &maddr, &mlen) == 2); 247 | #ifdef DEBUG 248 | printf("mem write = addr %lx / len %lx\n", maddr, mlen); 249 | printf("mem write = content %s\n", content); 250 | #endif 251 | uint8_t *mval = malloc(mlen); 252 | str_to_hex(content, mval, mlen); 253 | int ret = gdbstub->ops->write_mem(args, maddr, mlen, mval); 254 | 255 | if (!ret) { 256 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 257 | } else { 258 | char packet_str[MAX_SEND_PACKET_SIZE]; 259 | sprintf(packet_str, "E%d", ret); 260 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 261 | } 262 | free(mval); 263 | } 264 | 265 | static void process_mem_xwrite(gdbstub_t *gdbstub, 266 | char *payload, 267 | uint8_t *packet_end, 268 | void *args) 269 | { 270 | size_t maddr, mlen; 271 | char *content = strchr(payload, ':'); 272 | if (content) { 273 | *content = '\0'; 274 | content++; 275 | } 276 | assert(sscanf(payload, "%lx,%lx", &maddr, &mlen) == 2); 277 | assert(unescape(content, (char *) packet_end) == (int) mlen); 278 | #ifdef DEBUG 279 | printf("mem xwrite = addr %lx / len %lx\n", maddr, mlen); 280 | for (size_t i = 0; i < mlen; i++) { 281 | printf("\tmem xwrite, byte %ld: %x\n", i, content[i]); 282 | } 283 | #endif 284 | 285 | gdbstub->ops->write_mem(args, maddr, mlen, content); 286 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 287 | } 288 | 289 | 290 | void process_xfer(gdbstub_t *gdbstub, char *s) 291 | { 292 | char *name = s; 293 | char *args = strchr(s, ':'); 294 | if (args) { 295 | *args = '\0'; 296 | args++; 297 | } 298 | #ifdef DEBUG 299 | printf("xfer = %s %s\n", name, args); 300 | #endif 301 | if (!strcmp(name, "features") && gdbstub->arch.target_desc != NULL) { 302 | /* Check the args */ 303 | char *action = strtok(args, ":"); 304 | assert(strcmp(action, "read") == 0); 305 | char *annex = strtok(NULL, ":"); 306 | assert(strcmp(annex, "target.xml") == 0); 307 | 308 | char buf[MAX_SEND_PACKET_SIZE]; 309 | int offset = 0, length = 0; 310 | sscanf(strtok(NULL, ":"), "%x,%x", &offset, &length); 311 | 312 | int total_len = strlen(gdbstub->arch.target_desc); 313 | int payload_length = 314 | MAX_DATA_PAYLOAD > length ? length : MAX_DATA_PAYLOAD; 315 | 316 | // Determine if the remaining data fits within the buffer 317 | buf[0] = (total_len - offset < payload_length) ? 'l' : 'm'; 318 | snprintf(buf + 1, payload_length, "%s", 319 | gdbstub->arch.target_desc + offset); 320 | 321 | conn_send_pktstr(&gdbstub->priv->conn, buf); 322 | } else { 323 | conn_send_pktstr(&gdbstub->priv->conn, ""); 324 | } 325 | } 326 | 327 | static void process_query(gdbstub_t *gdbstub, char *payload, void *args) 328 | { 329 | char packet_str[MAX_SEND_PACKET_SIZE]; 330 | char *name = payload; 331 | char *qargs = strchr(payload, ':'); 332 | if (qargs) { 333 | *qargs = '\0'; 334 | qargs++; 335 | } 336 | #ifdef DEBUG 337 | printf("query = %s %s\n", name, qargs); 338 | #endif 339 | 340 | if (!strcmp(name, "C")) { 341 | if (gdbstub->ops->get_cpu != NULL) { 342 | int cpuid = gdbstub->ops->get_cpu(args); 343 | sprintf(packet_str, "QC%04d", cpuid); 344 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 345 | } else 346 | conn_send_pktstr(&gdbstub->priv->conn, ""); 347 | } else if (!strcmp(name, "Supported")) { 348 | if (gdbstub->arch.target_desc != NULL) 349 | conn_send_pktstr(&gdbstub->priv->conn, 350 | "PacketSize=1024;qXfer:features:read+"); 351 | else 352 | conn_send_pktstr(&gdbstub->priv->conn, "PacketSize=1024"); 353 | } else if (!strcmp(name, "Attached")) { 354 | /* assume attached to an existing process */ 355 | conn_send_pktstr(&gdbstub->priv->conn, "1"); 356 | } else if (!strcmp(name, "Xfer")) { 357 | process_xfer(gdbstub, qargs); 358 | } else if (!strcmp(name, "Symbol")) { 359 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 360 | } else if (!strcmp(name, "fThreadInfo")) { 361 | /* Assume at least 1 CPU if user didn't specific 362 | * the CPU counts */ 363 | int smp = gdbstub->arch.smp ? gdbstub->arch.smp : 1; 364 | char *ptr; 365 | char cpuid_str[6]; 366 | 367 | /* Make assumption on the CPU counts, so 368 | * that we can use the buffer very simply. */ 369 | assert(smp < 10000); 370 | 371 | packet_str[0] = 'm'; 372 | ptr = packet_str + 1; 373 | for (int cpuid = 0; cpuid < smp; cpuid++) { 374 | sprintf(cpuid_str, "%04d,", cpuid); 375 | memcpy(ptr, cpuid_str, 5); 376 | ptr += 5; 377 | } 378 | *ptr = 0; 379 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 380 | } else if (!strcmp(name, "sThreadInfo")) { 381 | conn_send_pktstr(&gdbstub->priv->conn, "l"); 382 | } else { 383 | conn_send_pktstr(&gdbstub->priv->conn, ""); 384 | } 385 | } 386 | 387 | static inline gdb_event_t process_vcont(gdbstub_t *gdbstub, char *args) 388 | { 389 | gdb_event_t event = EVENT_NONE; 390 | 391 | switch (args[0]) { 392 | case 'c': 393 | if (gdbstub->ops->cont != NULL) 394 | event = EVENT_CONT; 395 | else 396 | SEND_EPERM(gdbstub); 397 | break; 398 | case 's': 399 | if (gdbstub->ops->stepi != NULL) 400 | event = EVENT_STEP; 401 | else 402 | SEND_EPERM(gdbstub); 403 | break; 404 | default: 405 | SEND_EPERM(gdbstub); 406 | break; 407 | } 408 | 409 | return event; 410 | } 411 | 412 | #define VCONT_DESC "vCont;%s%s" 413 | static inline void process_vcont_support(gdbstub_t *gdbstub) 414 | { 415 | char packet_str[MAX_SEND_PACKET_SIZE]; 416 | char *str_s = (gdbstub->ops->stepi == NULL) ? "" : "s;S;"; 417 | char *str_c = (gdbstub->ops->cont == NULL) ? "" : "c;C;"; 418 | sprintf(packet_str, VCONT_DESC, str_s, str_c); 419 | 420 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 421 | } 422 | 423 | static gdb_event_t process_vpacket(gdbstub_t *gdbstub, char *payload) 424 | { 425 | gdb_event_t event = EVENT_NONE; 426 | char *name = payload; 427 | char *args = strchr(payload, ';'); 428 | if (args) { 429 | *args = '\0'; 430 | args++; 431 | } 432 | #ifdef DEBUG 433 | printf("vpacket = %s %s\n", name, args); 434 | #endif 435 | 436 | if (!strcmp("Cont", name)) 437 | event = process_vcont(gdbstub, args); 438 | else if (!strcmp("Cont?", name)) 439 | process_vcont_support(gdbstub); 440 | else 441 | conn_send_pktstr(&gdbstub->priv->conn, ""); 442 | 443 | return event; 444 | } 445 | 446 | static void process_del_break_points(gdbstub_t *gdbstub, 447 | char *payload, 448 | void *args) 449 | { 450 | size_t type, addr, kind; 451 | assert(sscanf(payload, "%zx,%zx,%zx", &type, &addr, &kind) == 3); 452 | 453 | #ifdef DEBUG 454 | printf("remove breakpoints = %zx %zx %zx\n", type, addr, kind); 455 | #endif 456 | 457 | bool ret = gdbstub->ops->del_bp(args, addr, type); 458 | if (ret) 459 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 460 | else 461 | SEND_EINVAL(gdbstub); 462 | } 463 | 464 | static void process_set_break_points(gdbstub_t *gdbstub, 465 | char *payload, 466 | void *args) 467 | { 468 | size_t type, addr, kind; 469 | assert(sscanf(payload, "%zx,%zx,%zx", &type, &addr, &kind) == 3); 470 | 471 | #ifdef DEBUG 472 | printf("set breakpoints = %zx %zx %zx\n", type, addr, kind); 473 | #endif 474 | 475 | bool ret = gdbstub->ops->set_bp(args, addr, type); 476 | if (ret) 477 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 478 | else 479 | SEND_EINVAL(gdbstub); 480 | } 481 | 482 | static void process_set_cpu(gdbstub_t *gdbstub, char *payload, void *args) 483 | { 484 | int cpuid; 485 | /* We don't support deprecated Hc packet, GDB 486 | * should send only send vCont;c and vCont;s here. */ 487 | if (payload[0] == 'g') { 488 | assert(sscanf(payload, "g%d", &cpuid) == 1); 489 | gdbstub->ops->set_cpu(args, cpuid); 490 | } 491 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 492 | } 493 | 494 | static bool packet_csum_verify(packet_t *inpkt) 495 | { 496 | /* We add extra 1 for leading '$' and minus extra 1 for trailing '#' */ 497 | uint8_t csum_rslt = compute_checksum((char *) inpkt->data + 1, 498 | inpkt->end_pos - CSUM_SIZE - 1); 499 | uint8_t csum_expected; 500 | str_to_hex((char *) &inpkt->data[inpkt->end_pos - CSUM_SIZE + 1], 501 | &csum_expected, sizeof(uint8_t)); 502 | #ifdef DEBUG 503 | printf("csum rslt = %x / csum expected = %s / ", csum_rslt, 504 | &inpkt->data[inpkt->end_pos - CSUM_SIZE + 1]); 505 | printf("csum expected = %x \n", csum_expected); 506 | #endif 507 | return csum_rslt == csum_expected; 508 | } 509 | 510 | static gdb_event_t gdbstub_process_packet(gdbstub_t *gdbstub, 511 | packet_t *inpkt, 512 | void *args) 513 | { 514 | assert(inpkt->data[0] == '$'); 515 | assert(packet_csum_verify(inpkt)); 516 | 517 | /* After checking the checksum result, ignore those bytes */ 518 | inpkt->data[inpkt->end_pos - CSUM_SIZE] = 0; 519 | uint8_t request = inpkt->data[1]; 520 | char *payload = (char *) &inpkt->data[2]; 521 | gdb_event_t event = EVENT_NONE; 522 | 523 | switch (request) { 524 | case 'g': 525 | if (gdbstub->ops->read_reg != NULL) { 526 | process_reg_read(gdbstub, args); 527 | } else { 528 | SEND_EPERM(gdbstub); 529 | } 530 | break; 531 | case 'm': 532 | if (gdbstub->ops->read_mem != NULL) { 533 | process_mem_read(gdbstub, payload, args); 534 | } else { 535 | SEND_EPERM(gdbstub); 536 | } 537 | break; 538 | case 'p': 539 | if (gdbstub->ops->read_reg != NULL) { 540 | process_reg_read_one(gdbstub, payload, args); 541 | } else { 542 | SEND_EPERM(gdbstub); 543 | } 544 | break; 545 | case 'q': 546 | process_query(gdbstub, payload, args); 547 | break; 548 | case 'v': 549 | event = process_vpacket(gdbstub, payload); 550 | break; 551 | case 'z': 552 | if (gdbstub->ops->del_bp != NULL) { 553 | process_del_break_points(gdbstub, payload, args); 554 | } else { 555 | SEND_EPERM(gdbstub); 556 | } 557 | break; 558 | case '?': 559 | conn_send_pktstr(&gdbstub->priv->conn, "S05"); 560 | break; 561 | case 'D': 562 | event = EVENT_DETACH; 563 | break; 564 | case 'G': 565 | if (gdbstub->ops->write_reg != NULL) { 566 | process_reg_write(gdbstub, payload, args); 567 | } else { 568 | SEND_EPERM(gdbstub); 569 | } 570 | break; 571 | case 'H': 572 | if (gdbstub->ops->set_cpu != NULL) { 573 | process_set_cpu(gdbstub, payload, args); 574 | } else { 575 | SEND_EPERM(gdbstub); 576 | } 577 | break; 578 | case 'M': 579 | if (gdbstub->ops->write_mem != NULL) { 580 | process_mem_write(gdbstub, payload, args); 581 | } else { 582 | SEND_EPERM(gdbstub); 583 | } 584 | break; 585 | case 'P': 586 | if (gdbstub->ops->write_reg != NULL) { 587 | process_reg_write_one(gdbstub, payload, args); 588 | } else { 589 | SEND_EPERM(gdbstub); 590 | } 591 | break; 592 | case 'T': 593 | /* FIXME: Assume all CPUs are alive here, any exception case 594 | * that user may want to handle? */ 595 | conn_send_pktstr(&gdbstub->priv->conn, "OK"); 596 | break; 597 | case 'X': 598 | if (gdbstub->ops->write_mem != NULL) { 599 | /* It is important for xwrite to know the end position of packet, 600 | * because there're escape characters which block us interpreting 601 | * the packet as a string just like other packets do. */ 602 | process_mem_xwrite(gdbstub, payload, 603 | &inpkt->data[inpkt->end_pos - CSUM_SIZE], args); 604 | } else { 605 | SEND_EPERM(gdbstub); 606 | } 607 | break; 608 | case 'Z': 609 | if (gdbstub->ops->set_bp != NULL) { 610 | process_set_break_points(gdbstub, payload, args); 611 | } else { 612 | SEND_EPERM(gdbstub); 613 | } 614 | break; 615 | default: 616 | conn_send_pktstr(&gdbstub->priv->conn, ""); 617 | break; 618 | } 619 | 620 | return event; 621 | } 622 | 623 | static gdb_action_t gdbstub_handle_event(gdbstub_t *gdbstub, 624 | gdb_event_t event, 625 | void *args) 626 | { 627 | gdb_action_t act = ACT_NONE; 628 | 629 | switch (event) { 630 | case EVENT_CONT: 631 | async_io_enable(gdbstub->priv); 632 | act = gdbstub->ops->cont(args); 633 | async_io_disable(gdbstub->priv); 634 | break; 635 | case EVENT_STEP: 636 | act = gdbstub->ops->stepi(args); 637 | break; 638 | case EVENT_DETACH: 639 | act = ACT_SHUTDOWN; 640 | break; 641 | default: 642 | break; 643 | } 644 | 645 | return act; 646 | } 647 | 648 | static void gdbstub_act_resume(gdbstub_t *gdbstub) 649 | { 650 | char packet_str[32]; 651 | sprintf(packet_str, "S%02x", GDB_SIGNAL_TRAP); 652 | conn_send_pktstr(&gdbstub->priv->conn, packet_str); 653 | } 654 | 655 | bool gdbstub_run(gdbstub_t *gdbstub, void *args) 656 | { 657 | // Bring the user-provided argument in the gdbstub_t structure 658 | gdbstub->priv->args = args; 659 | 660 | /* Create a thread to receive interrupt when running the gdbstub op */ 661 | if (gdbstub->ops->on_interrupt != NULL && gdbstub->priv->tid == 0) { 662 | async_io_disable(gdbstub->priv); 663 | pthread_create(&gdbstub->priv->tid, NULL, (void *) socket_reader, 664 | (void *) gdbstub); 665 | } 666 | 667 | while (true) { 668 | conn_recv_packet(&gdbstub->priv->conn); 669 | packet_t *pkt = conn_pop_packet(&gdbstub->priv->conn); 670 | #ifdef DEBUG 671 | printf("packet = %s\n", pkt->data); 672 | #endif 673 | gdb_event_t event = gdbstub_process_packet(gdbstub, pkt, args); 674 | free(pkt); 675 | 676 | gdb_action_t act = gdbstub_handle_event(gdbstub, event, args); 677 | switch (act) { 678 | case ACT_RESUME: 679 | gdbstub_act_resume(gdbstub); 680 | break; 681 | case ACT_SHUTDOWN: 682 | return true; 683 | default: 684 | break; 685 | } 686 | } 687 | 688 | return false; 689 | } 690 | 691 | void gdbstub_close(gdbstub_t *gdbstub) 692 | { 693 | /* Use thread ID to make sure the thread was created */ 694 | if (gdbstub->priv->tid != 0) { 695 | __atomic_store_n(&thread_stop, true, __ATOMIC_RELAXED); 696 | pthread_join(gdbstub->priv->tid, NULL); 697 | } 698 | 699 | conn_close(&gdbstub->priv->conn); 700 | free(gdbstub->priv); 701 | } 702 | -------------------------------------------------------------------------------- /src/packet.c: -------------------------------------------------------------------------------- 1 | #include "packet.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static void pktbuf_clear(pktbuf_t *pktbuf) 7 | { 8 | pktbuf->end_pos = -1; 9 | pktbuf->size = 0; 10 | } 11 | 12 | #define DEFAULT_CAP (10) 13 | bool pktbuf_init(pktbuf_t *pktbuf) 14 | { 15 | pktbuf->cap = DEFAULT_CAP; 16 | pktbuf->data = calloc(1, (1 << pktbuf->cap) * sizeof(uint8_t)); 17 | pktbuf_clear(pktbuf); 18 | 19 | return true; 20 | } 21 | 22 | ssize_t pktbuf_fill_from_file(pktbuf_t *pktbuf, int fd) 23 | { 24 | assert((1 << pktbuf->cap) >= pktbuf->size); 25 | 26 | /* enlarge the buffer to read from file when it is full */ 27 | if ((1 << pktbuf->cap) == pktbuf->size) { 28 | pktbuf->cap++; 29 | pktbuf->data = 30 | realloc(pktbuf->data, (1 << pktbuf->cap) * sizeof(uint8_t)); 31 | } 32 | 33 | int left = (1 << pktbuf->cap) - pktbuf->size; 34 | uint8_t *buf = pktbuf->data + pktbuf->size; 35 | ssize_t nread = read(fd, buf, left); 36 | 37 | if (nread > 0) 38 | pktbuf->size += nread; 39 | 40 | return nread; 41 | } 42 | 43 | bool pktbuf_is_complete(pktbuf_t *pktbuf) 44 | { 45 | int head = -1; 46 | 47 | /* skip to the head of next packet */ 48 | for (int i = 0; i < pktbuf->size; i++) { 49 | if (pktbuf->data[i] == '$') { 50 | head = i; 51 | break; 52 | } 53 | } 54 | 55 | if (head < 0) { 56 | pktbuf_clear(pktbuf); 57 | return false; 58 | } 59 | 60 | if (head > 0) { 61 | /* moving memory for a valid packet */ 62 | memmove(pktbuf->data, pktbuf->data + head, pktbuf->size - head); 63 | pktbuf->size -= head; 64 | } 65 | 66 | /* check the end of the buffer */ 67 | uint8_t *end_pos_ptr = memchr(pktbuf->data, '#', pktbuf->size); 68 | if (end_pos_ptr == NULL) 69 | return false; 70 | 71 | int end_pos = (end_pos_ptr - pktbuf->data) + CSUM_SIZE; 72 | if (end_pos > pktbuf->size) 73 | return false; 74 | 75 | pktbuf->end_pos = end_pos; 76 | 77 | return true; 78 | } 79 | 80 | packet_t *pktbuf_pop_packet(pktbuf_t *pktbuf) 81 | { 82 | if (pktbuf->end_pos == -1) { 83 | return NULL; 84 | } 85 | 86 | int old_pkt_size = pktbuf->end_pos + 1; 87 | packet_t *pkt = calloc(1, sizeof(packet_t) + old_pkt_size + 1); 88 | memcpy(pkt->data, pktbuf->data, old_pkt_size); 89 | pkt->end_pos = pktbuf->end_pos; 90 | pkt->data[old_pkt_size] = 0; 91 | 92 | memmove(pktbuf->data, pktbuf->data + old_pkt_size + 1, 93 | pktbuf->size - old_pkt_size); 94 | pktbuf->size -= old_pkt_size; 95 | pktbuf->end_pos = -1; 96 | return pkt; 97 | } 98 | 99 | void pktbuf_destroy(pktbuf_t *pktbuf) 100 | { 101 | free(pktbuf->data); 102 | } 103 | -------------------------------------------------------------------------------- /src/regbuf.c: -------------------------------------------------------------------------------- 1 | #include "regbuf.h" 2 | 3 | #include 4 | 5 | bool regbuf_init(regbuf_t *reg) 6 | { 7 | /* Default to 8 bytes register size. */ 8 | reg->sz = 8; 9 | reg->buf = malloc(reg->sz); 10 | 11 | if (!reg->buf) 12 | return false; 13 | 14 | return true; 15 | } 16 | 17 | void *regbuf_get(regbuf_t *reg, size_t reg_sz) 18 | { 19 | if (reg_sz <= reg->sz) 20 | return reg->buf; 21 | 22 | free(reg->buf); 23 | 24 | while (reg_sz > reg->sz) { 25 | reg->sz <<= 1; 26 | } 27 | 28 | reg->buf = malloc(reg->sz); 29 | return reg->buf; 30 | } 31 | 32 | void regbuf_destroy(regbuf_t *reg) 33 | { 34 | free(reg->buf); 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/csum.c: -------------------------------------------------------------------------------- 1 | #include "utils/csum.h" 2 | 3 | uint8_t compute_checksum(char *buf, size_t len) 4 | { 5 | uint8_t csum = 0; 6 | for (size_t i = 0; i < len; ++i) 7 | csum += buf[i]; 8 | return csum; 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/translate.c: -------------------------------------------------------------------------------- 1 | #include "utils/translate.h" 2 | 3 | static char hexchars[] = "0123456789abcdef"; 4 | 5 | void hex_to_str(uint8_t *num, char *str, int bytes) 6 | { 7 | for (int i = 0; i < bytes; i++) { 8 | uint8_t ch = *(num + i); 9 | *(str + i * 2) = hexchars[ch >> 4]; 10 | *(str + i * 2 + 1) = hexchars[ch & 0xf]; 11 | } 12 | str[bytes * 2] = '\0'; 13 | } 14 | 15 | static uint8_t char_to_hex(char ch) 16 | { 17 | const uint8_t letter = ch & 0x40; 18 | const uint8_t offset = (letter >> 3) | (letter >> 6); 19 | return (ch + offset) & 0xf; 20 | } 21 | 22 | void str_to_hex(char *str, uint8_t *num, int bytes) 23 | { 24 | for (int i = 0; i < bytes; i++) { 25 | uint8_t ch_high = char_to_hex(*(str + i * 2)); 26 | uint8_t ch_low = char_to_hex(*(str + i * 2 + 1)); 27 | 28 | *(num + i) = (ch_high << 4) | ch_low; 29 | } 30 | } 31 | 32 | int unescape(char *msg, char *end) 33 | { 34 | char *w = msg; 35 | char *r = msg; 36 | while (r < end) { 37 | if (*r == '}') { 38 | *w = *(r + 1) ^ 0x20; 39 | r += 2; 40 | } else { 41 | *w = *r; 42 | r += 1; 43 | } 44 | w += 1; 45 | } 46 | 47 | return w - msg; 48 | } 49 | -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | int add(int a, int b); 2 | 3 | // main function is intentionally put on 0x0 4 | int main() 5 | { 6 | int c = add(3, 4); 7 | return 0; 8 | } 9 | 10 | int add(int a, int b) 11 | { 12 | return a + b; 13 | } 14 | --------------------------------------------------------------------------------