What is this?

I want to implement a fake gdb-server that understands the GDB Remote Debug Protocol. This fake server can have either GDB or LLDB attached to it, allowing step in execution, examine registers, examine memory contents, set break points. I don’t know if I’ll be able to finish it or how long it’s going to take. But I’m going to publish my code as I make progress.

Motivation

I was writing a Play Station emulator last year for fun. In order to make the debug process better than just a bunch of logs, I implemented a gdb-server stub so that I could attach gdb to it. It was a successful attempt where I could attach a gdb from command line to my emulator. But it’s really not that fun to wrestle with the gdb CUI (or TUI if you prefer).

So I decided that I needed a graphical debugger interface. Visual Studio Code the best choice for me because I was writing my emulator on it with Rust. My first attempt is to use the Native Debug extension to launch a local gdb that configured to target MIPS processors.

This is where I got greedy; I wanted to make it work for LLDB too. In theory, LLDB does support gdb remote debug protocol, so it should just work out of box, but that just wasn’t the case. LLDB has its own extensions in the protocol that needed to be supported. I was able to make LLDB attach to my emulator and step-in to the instructions. But I couldn’t make it show the registers even though I implemented qHostInfo, qRegisterInfo and whatnot. That’s why I decided that I should implement a fake gdb-server from scratch to better understand the protocol.

1.1 Project setup

I’ve picked Node.js for this project because it seemed simple and worth learning. My work code is mostly done in Unity C# (and coding isn’t even my main responsibility as a PM Director..), so I imagine a lot of my work on this project is going to be a lot of Googling and making seemingly obvious mistakes in how to use JavaScript and Node.js.

After a lot of Googling about Node.js, npm and related topics, I set up my initial project on github. Besides the boilerplate package.json and so on, the only “meaningful” part of the code is the index.js which prints a “Hello World”.

1.2 Start up server

The next thing I wanted to do after setting up a project is to start a server. The following is mostly a boilerplate code snippet that simply starts a server listening to the incoming data on port 2424 and log it to the console (or terminal). I picked 2424 for no particular reasons.

Then start the server using node command (or npm run start if package.json is properly set up).

$ node src/index.js
Started a server at 2424

Now open another terminal to start gdb.

$ gdb
(gdb) target remote localhost:2424

As soon as I enter the command, my other terminal starts spewing logs

Connection accepted: 127.0.0.1:60624
Incoming data:+$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a
Incoming data:$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a
...

At this point, I can ctrl-c on the terminal that is running GDB to stop it. What’s happening here is that the GDB kept sending the same message because I’m not replying back.

1.3 Write back

So seems like qSupported is the packet that we need to support.

‘qSupported [:gdbfeature [;gdbfeature]… ]’

Tell the remote stub about features supported by GDB, and query the stub for features it supports. This packet allows GDB and the remote stub to take advantage of each others’ features.

But besides the qSupported query and its arguments, there is also a +$ at the beginning and a #6a at the end of the query. The answer is in the overview section of the protocol document.

When either the host or the target machine receives a packet, the first response expected is an acknowledgment: either ‘+’ (to indicate the package was received correctly) or ‘-’ (to request retransmission):

-> $packet-data#checksum
<- +

So the first + is just GDB saying “hi” to us.

With this, now we know what to tell GDB; We don’t support anything.

So we’re gonna reply with +$#00 which is an ack and an empty string with a zero checksum.

The line 3const reply = "+$#00" is what we reply to GDB. It’s basically saying “NO” to everything that GDB should ask. “What do we say to the god of debugger?” “Not today”.

With this line added, let’s see what GDB is gonna do.

$ gdb
(gdb) target remote localhost:2424
Remote debugging using localhost:2424
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
warning: Invalid remote reply:

Looks like it made some progress! Let’s see what the server should log.

$ node src/index.js
Started a server at 2424
Connection accepted: 127.0.0.1:61540
<-:+$qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a
->:+$#00
<-:+$vMustReplyEmpty#3a
->:+$#00
<-:+
->:+$#00
<-:$Hg0#df+$qTStatus#49
->:+$#00
<-:+
->:+$#00
<-:$?#3f+$qfThreadInfo#bb
->:+$#00
<-:+$qL1200000000000000000#50
->:+$#00
<-:+$Hc-1#09
->:+$#00
<-:+
->:+$#00
<-:$qC#b4+
->:+$#00
<-:$qAttached#8f
->:+$#00
<-:+
->:+$#00

We can also turn on the log in GDB by typing the command debug remote 1 in GDB.

$ gdb
(gdb) set debug remote 1
(gdb) target remote localhost:2424
Remote debugging using localhost:2424
Sending packet: $qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+;xmlRegisters=i386#6a...Ack
Packet received:
Packet qSupported (supported-packets) is NOT supported
Sending packet: $vMustReplyEmpty#3a...Ack
Packet received:
Sending packet: $Hg0#df...Ack
Packet received:
Sending packet: $qTStatus#49...Ack
Packet received:
Packet qTStatus (trace-status) is NOT supported
Sending packet: $?#3f...Ack
Packet received:
Sending packet: $qfThreadInfo#bb...Ack
Packet received:
Sending packet: $qL1200000000000000000#50...Ack
Packet received:
Sending packet: $Hc-1#09...Ack
Packet received:
Sending packet: $qC#b4...Ack
Packet received:
Sending packet: $qAttached#8f...Ack
Packet received:
Packet qAttached (query-attached) is NOT supported
warning: No executable has been specified and target does not support
determining executable automatically. Try using the "file" command.
warning: Invalid remote reply:

It seems like GDB is pissed because we kept saying NO to it. Unfortunately that’d be it for today. I’ll try to be nice with GDB to mend fences in my next posts.

References:

R3000: https://en.wikipedia.org/wiki/R3000

GDB Remote Debug Protocol: https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html

LLDB-GDB-Remote: https://github.com/llvm/llvm-project/blob/master/lldb/docs/lldb-gdb-remote.txt

--

--