mupen64plus / mupen64plus-core

Core module of the Mupen64Plus project
1.32k stars 258 forks source link

gdb stub #1038

Closed zZeck closed 1 year ago

zZeck commented 1 year ago

This adds a GDB stub to mupen, currently hard coded to listen on port 5555. It supports stepping, breaking with ctrl+C, read, write, exec breakpoints, reading and writing the main registers and reading and writing memory.

The motivation was, this makes it possible to load an .elf file with symbols from one of the N64 game decompilation projects into gdb, connect to mupen and then debug with symbols. You can even get a backtrace. You could also connect IDEs that support the gdb protocol to it, and step through source in a familiar environment.

I think this will be very useful for modders, reverse engineerers and homebrewers. (I made it to help with my own reversing project). I don't know how compliant I am with the mupen's standards. I used some C++ features I didn't see elsewhere in the code, out of convenience for initial development, but can remove them if needed. If the project is interested in taking this feature, I'm more than willing to make any changes (or additions!).

I still need to add support in the vcxproj file. I also suspect it will be desired that the port is configurable by like --gdbstub or something on the command line, but I have not done that yet. Advice about the preferred way would be appreciated.

I had to fix a few other problems along the way.

binutils version detection bug: This is caused by shell echo $(LIBOPCODES_VERSION) | cut -f3 -d. binutils does not print a trailing .0 on point version 0 eg it is 2.4 not 2.4.0. That code produces an empty string, which later causes the LIBBFD_GE_2_39 check to fail with a syntax problem. /bin/sh: line 1: [:)' expected, found 1` I replace this blank with a 0 using sed.

debugger tlb refill exception I immediately ran into this same problem, since I am using the existing debugger api. Made alternate method that does not trigger tlb exception.

Rosalie241 commented 1 year ago

@Gillou68310 has already made a gdbstub implementation for the console front-end (see [1]), which makes more sense than including it in mupen64plus-core in my opinion, @m4xw has a modified implementation of that in mupen64plus-nx from retroarch (see [2]) and I have a modified implementation in a branch for RMG (see [3]).

[1] https://github.com/Gillou68310/mupen64plus-ui-console/tree/gdbstub [2] https://github.com/libretro/mupen64plus-libretro-nx/tree/gdbstub [3] https://github.com/Rosalie241/RMG/tree/gdbstub

zZeck commented 1 year ago

I will check those out. Are any of those going to go into the main ui branch? I looked around for something like this but struck out in my google and github searching.

m4xw commented 1 year ago

At some point, ye. Gillous ver is the reference while my ver adds a bunch of other edge case handling specifically to optimize for IDA and some other tools

zZeck commented 1 year ago

Very nice! All I could ever find when I was searching was people talking about PJ64's debugger, using GDB with real hardware, and mupen's built in debugger.

Uh, any interest in a PR with just the binutils version check fix and the tlb exception fix?

m4xw commented 1 year ago

TLB exception fix is implemented on NX via my own mechanism and as far i remember Gillou made some debugger api change to not cause them to throw tlb exceptions when poking memory (not sure if that made its way in). Not sure what the binutils ver fix is about

m4xw commented 1 year ago

The issue you quoted regarding tlb was the first time we started work on it :P

zZeck commented 1 year ago

binutils thing is just the scripts in the makefile work fine for versions of form X.XX.X but not for versions X.XX (at least this is what is happening for me)

To illustrate, this means $(LIBOPCODES_POINT) is 1 $ echo "2.40.1" | head -n1 | rev | cut -d ' ' -f1 | rev | cut -f3 -d. 1 this means $(LIBOPCODES_POINT) is blank $ echo "2.40" | head -n1 | rev | cut -d ' ' -f1 | rev | cut -f3 -d. ` when$(LIBOPCODES_POINT)is used later [ $(LIBOPCODES_MAJOR) -gt 2 -o ( $(LIBOPCODES_MAJOR) -eq 2 -a $(LIBOPCODES_MINOR) -ge 28 ) -o ( $(LIBOPCODES_MAJOR) -eq 2 -a $(LIBOPCODES_MINOR) -eq 28 -a $(LIBOPCODES_POINT) -ge 1 ) ]`

Having a number in $(LIBOPCODES_POINT) at that location works fine for me. Having a blank string causes the error /bin/sh: line 1: [: )' expected, found 1

it's basically this which errors [ 2 -gt 2 -o \( 2 -eq 2 -a 38 -ge 28 \) -o \( 2 -eq 2 -a 38 -eq 28 -a -ge 1 \) ] vs this that works [ 2 -gt 2 -o \( 2 -eq 2 -a 38 -ge 28 \) -o \( 2 -eq 2 -a 38 -eq 28 -a 0 -ge 1 \) ]

Leads to version detection being wrong, which later leads to compilation problems after the shell error flies past. I was getting the behavior described here about print_insn_i386 undeclared.

m4xw commented 1 year ago

Yea thats probably fine to go into a different PR

zZeck commented 1 year ago

I will make that PR later today probably. Thanks for the fast replies everyone and for tipping me off to those other stub implementations. Closing this.

m4xw commented 1 year ago

Hope you didnt spend too much time. We welcome u to contribute to the ongoing implementations. Feel free to ask me if you have any questions