In the previous post, we were able to convince GDB that it is talking to a GDB Server and it retrieved register values from out GDB Server Stub. In this post, I’d like to implement a few remaining required GDB commands so that we can step execute and examine the content of the “memory”.

3.1: Remaining required commands

The following commands are the bare minimum commands that we need to support according to the spec’s overview section.

  • “?” — Halt reason. We already implemented this in the previous post.
  • “g” — Read from and write to registers. We already implemented it.
  • “G XX…” — Write to registers. XX are the values in hex.
  • “m addr,length” — Read length bytes from memory at addr. To be more precise, length’s unit is architecture dependent and can be less or more than a byte. But we don’t have to worry about it until when we have to deal with some quirky architecture.
  • “M addr,length,XX…” — Write to memory. XX are the values in hex.
  • “s [addr]” and “ [addr]” — Step and continue execution at the addr. If the addr is omitted, at the current address.

3.1.1 Register read and write

Let’s implement this one by one starting with “G”. I’ve changed the code for “g” command as well here so that the register values can be read and written.

Supporting reading and writing registers isn’t difficult, a lot of the code above is there to emulate MIPS. One thing that we should be careful about is where to set the boundary of responsibilities. The code above is careful set up so that the handleRead(Write)Register returns and takes array of bytes as its input. Not string, not integers, but bytes. It’s because I wanted to make sure that the GDB Server Stub is architecture agnostic so that we can easily swap the handling functions later on to support any kind of target architecture.

Let’s attach a GDB to it to test this out.

Very well. We can see that the set command with pairs of registers and their value is properly executed. Note that at is an alias for $r1, v0 for $r2 and s8 for $r30.

3.1.2 Memory read and write

Let’s move on to “m” and “M” for memory read and write. We’ll first create an 1kb array of bytes as our memory at 0xbfc00000. Then we’ll make functions to read from and write to it.

Simple enough, isn’t it? Let’s attach GDB to see how it works.

Looks like we’ve successfully loaded the memory contents to GDB! x/5i $pc means examine 5 words as instructions at $pc. We’ve loaded the memory with this simple program that calculates 1000 + 2000.

Let change this to 1000 — 2000 by setting the memory content at 0xbfc0008 to sub t2,t0,t1.

We can see that 0xbfc00008 is now sub t2,t0,t1 as we expected. Just to be extra clear, I added a command x/5x $pc that examines 5 words hex data at $pc to verify that the address 0xbfc00008 is indeed 0x010a5022 as set by the command.

3.1.3 Step and continue execution

The last two required commands that we need to implement are step and continue execution.

Because we don’t really have a program that gets executed, we’re just moving the $pc by one word (4 bytes) for both “s” and “c”. Then we reply with “S05” indicates that the program has halted by SIGTRAP.

Let’s test this out with GDB.

“si” or ”stepi” command on GDB (not GDB Remote Debug Protocol) executes one instruction and stops. It’s different from “s” or “step” command which executes one line of the source code. Source stepping is implemented by the GDB Remote Debug Protocol command “s addr” behind scene. So we don’t need to worry about the difference as far as the server is concerned.

“c” command works as same as “s” command except that it continues executes until the program hits a breakpoint. In our GDB server, we’re going to just execute one instruction for now.

To be continued…

--

--