Skip to content
 
 

Latest commit

 

History

192 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mini-gdbstub

mini-gdbstub is an implementation of the GDB Remote Serial Protocol 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.

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 of target-reported size kind on the address specified by addr. Return true if we set the breakpoint successfully, otherwise return false.
del_bp Delete type type breakpoint of target-reported size kind 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.
get_stop_signal Return the GDB signal number for the current target stop.
set_resume_signal Receive the signal from a C/S resume action, or zero for a plain c/s.
get_libraries_xml Return the current qXfer:libraries:read XML document.
get_libraries_generation Return a value that changes whenever the library list changes.
get_offsets Return the target's qOffsets response.
get_os_version Return the target OS version for qHostInfo, or NULL if unknown.
get_os_build Return the target OS build string for qHostInfo, or NULL if unknown.
get_shlib_info_addr Return the target address of Darwin's dyld_all_image_infos, or zero if unavailable.
get_process_info_extra Return optional semicolon-terminated fields appended to the qProcessInfo response.
get_loaded_libraries_json Return the JSON response for LLDB's Darwin jGetLoadedDynamicLibrariesInfos request.
get_thread_ids Optionally return dynamic RSP thread IDs. The return value is the total count; write at most capacity IDs when ids is non-NULL.
get_current_thread Optionally return the thread ID responsible for the current stop.
is_thread_alive Optionally report whether a thread ID is still live.
get_thread_stop_signal Optionally return a per-thread GDB signal number for qThreadStopInfo; use zero for a stopped thread without a signal.
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, size_t kind, bp_type_t type);
    bool (*del_bp)(void *args, size_t addr, size_t kind, bp_type_t type);
    void (*on_interrupt)(void *args);
    const char *(*get_libraries_xml)(void *args);
    const char *(*get_offsets)(void *args);

    void (*set_cpu)(void *args, int cpuid);
    int (*get_cpu)(void *args);

    int (*get_stop_signal)(void *args);
    void (*set_resume_signal)(void *args, int signal);
    size_t (*get_libraries_generation)(void *args);
    const char *(*get_os_version)(void *args);
    const char *(*get_os_build)(void *args);
    size_t (*get_shlib_info_addr)(void *args);
    const char *(*get_process_info_extra)(void *args);
    const char *(*get_loaded_libraries_json)(void *args,
                                             const char *request);
    size_t (*get_thread_ids)(void *args,
                             gdb_thread_id_t *ids,
                             size_t capacity);
    gdb_thread_id_t (*get_current_thread)(void *args);
    bool (*is_thread_alive)(void *args, gdb_thread_id_t thread_id);
    int (*get_thread_stop_signal)(void *args,
                                  gdb_thread_id_t thread_id);
};

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.

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.

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.
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.

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.

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

Article

About

An implementation of the GDB Remote Serial Protocol to help you adding debug mode on emulator

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages