== Debugger Implementation

=== C Header File

https://github.com/riscv/riscv-debug-spec contains instructions for
generating a C header file that defines macros for every field in every
register/abstract command mentioned in this document.

=== External Debugger Implementation

This section details how an external debugger might use the described
debug interface to perform some common operations on RISC-V cores using
the JTAG DTM described in Section #sec:jtagdtm[[sec:jtagdtm]]. All these
examples assume a 32-bit core but it should be easy to adapt the
examples to 64- or 128-bit cores.

To keep the examples readable, they all assume that everything succeeds,
and that they complete faster than the debugger can perform the next
access. This will be the case in a typical JTAG setup. However, the
debugger must always check the sticky error status bits after performing
a sequence of actions. If it sees any that are set, then it should
attempt the same actions again, possibly while adding in some delay, or
explicit checks for status bits.

[[dmiaccess]]
==== Debug Module Interface Access

To read an arbitrary Debug Module register, select , and scan in a value
with set to 1, and set to the desired register address. In Update-DR the
operation will start, and in Capture-DR its results will be captured
into . If the operation didn’t complete in time, will be 3 and the value
in must be ignored. The busy condition must be cleared by writing in ,
and then the second scan scan must be performed again. This process must
be repeated until returns 0. In later operations the debugger should
allow for more time between Update-DR and Capture-DR.

To write an arbitrary Debug Bus register, select , and scan in a value
with set to 2, and and set to the desired register address and data
respectively. From then on everything happens exactly as with a read,
except that a write is performed instead of the read.

It should almost never be necessary to scan IR, avoiding a big part of
the inefficiency in typical JTAG use.

==== Checking for Halted Harts

A user will want to know as quickly as possible when a hart is halted
(e.g. due to a breakpoint). To efficiently determine which harts are
halted when there are many harts, the debugger uses the `haltsum`
registers. Assuming the maximum number of harts exist, first it checks .
For each bit set there, it writes , and checks . This process repeats
through and . Depending on how many harts exist, the process should
start at one of the lower `haltsum` registers.

[[deb:halt]]
==== Halting

To halt one or more harts, the debugger selects them, sets , and then
waits for to indicate the harts are halted. Then it can clear to 0, or
leave it high to catch a hart that resets while halted.

==== Running

First, the debugger should restore any registers that it has
overwritten. Then it can let the selected harts run by setting . Once is
set, the debugger knows the selected harts have resumed. Harts might
halt very quickly after resuming (e.g. by hitting a software breakpoint)
so the debugger cannot use /to check whether the hart resumed.

==== Single Step

Using the hardware single step feature is almost the same as regular
running. The debugger just sets in before letting the hart run. The hart
behaves exactly as in the running case, except that interrupts may be
disabled (depending on ) and it only fetches and executes a single
instruction before re-entering Debug Mode.

==== Accessing Registers

[[deb:abstractreg]]
===== Using Abstract Command

Read using abstract command:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |latexmath:[$=2$], , = 0x1008 |Read
|Read | |- |Returns value that was in
|===

Write using abstract command:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |new value |
|Write | |latexmath:[$=2$], , , = 0x300 |Write
|===

[[deb:regprogbuf]]
===== Using Program Buffer

Abstract commands are used to exchange data with GPRs. Using this
mechanism, other registers can be accessed by moving their value
into/out of GPRs.

Write using program buffer:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |`csrw s0, MSTATUS` |

|Write |`progbuf1` |`ebreak` |

|Write | |new value |

|Write | |latexmath:[$=2$], , , , = 0x1008 |Write , then execute program
buffer
|===

Read using program buffer:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |`fmv.x.s s0, f1` |
|Write |`progbuf1` |`ebreak` |
|Write | | |Execute program buffer
|Write | |, = 0x1008 |read
|Read | |- |Returns the value that was in
|===

==== Reading Memory

[[deb:mrsysbus]]
===== Using System Bus Access

With system bus access, addresses are physical system bus addresses.

Read a word from memory using system bus access:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |latexmath:[$=2$], |Setup
|Write | |address |
|Read | |- |Value read from memory
|===

Read block of memory using system bus access:

[cols=">,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |latexmath:[$=2$], , , |Turn on autoread and autoincrement
|Write | |address |Writing address triggers read and increment
|Read | |- |Value read from memory
|Read | |- |Next value read from memory
|... |... |... |...
|Write | |0 |Disable autoread
|Read | |- |Get last value read from memory.
|===

[[deb:mrprogbuf]]
===== Using Program Buffer

Through the Program Buffer, the hart performs the memory accesses.
Addresses are physical or virtual (depending on and other system
configuration).

Read a word from memory using program buffer:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |`lw s0, 0(s0)` |
|Write |`progbuf1` |`ebreak` |
|Write | |address |
|Write | |, , , = 0x1008 |Write , then execute program buffer
|Write | |= 0x1008 |Read
|Read | |- |Value read from memory
|===

Read block of memory using program buffer:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |`lw s1, 0(s0)` |
|Write |`progbuf1` |`addi s0, s0, 4` |
|Write |`progbuf2` |`ebreak` |
|Write | |address |
|Write | |, , , = 0x1008 |Write , then execute program buffer
|Write | |, = 0x1009 |Read , then execute program buffer
|Write | | |Set
|Read | |- |Get value read from memory, then execute program buffer
|Read | |- |Get next value read from memory, then execute program buffer
|... |... |... |...
|Write | |0 |Clear
|Read | |- |Get last value read from memory.
|===

[[deb:mrabstract]]
===== Using Abstract Memory Access

Abstract memory accesses act as if they are performed by the hart,
although the actual implementation may differ.

Read a word from memory using abstract memory access:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |address |
|Write | |cmdtype=2, |
|Read | |- |Value read from memory
|===

Read block of memory using abstract memory access:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |1 |Re-execute the command when is accessed
|Write | |address |
|Write | |cmdtype=2, , |
|Read | |- |Read value, and trigger reading of next address
|... |... |... |...
|Write | |0 |Disable auto-exec
|Read | |- |Get last value read from memory.
|===

[[writemem]]
==== Writing Memory

[[deb:mrsysbus]]
===== Using System Bus Access

With system bus access, addresses are physical system bus addresses.

Write a word to memory using system bus access:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |latexmath:[$=2$] |Configure access size
|Write | |address |
|Write | |value |
|===

Write a block of memory using system bus access:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |latexmath:[$=2$], |Turn on autoincrement
|Write | |address |
|Write | |value0 |
|Write | |value1 |
|... |... |... |...
|Write | |valueN |
|===

[[deb:mrprogbuf]]
===== Using Program Buffer

Through the Program Buffer, the hart performs the memory accesses.
Addresses are physical or virtual (depending on and other system
configuration).

Write a word to memory using program buffer:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |`sw s1, 0(s0)` |
|Write |`progbuf1` |`ebreak` |
|Write | |address |
|Write | |, , = 0x1008 |Write
|Write | |value |
|Write | |, , , = 0x1009 |Write , then execute program buffer
|===

Write block of memory using program buffer:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |`sw s1, 0(s0)` |
|Write |`progbuf1` |`addi s0, s0, 4` |
|Write |`progbuf2` |`ebreak` |
|Write | |address |
|Write | |, , = 0x1008 |Write
|Write | |value0 |
|Write | |, , , = 0x1009 |Write , then execute program buffer
|Write | | |Set
|Write | |value1 |
|... |... |... |...
|Write | |valueN |
|Write | |0 |Clear
|===

[[deb:mwabstract]]
===== Using Abstract Memory Access

Abstract memory accesses act as if they are performed by the hart,
although the actual implementation may differ.

Write a word to memory using abstract memory access:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |address |
|Write | |value |
|Write | |cmdtype=2, , write=1 |
|===

Write a block of memory using abstract memory access:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |address |
|Write | |value0 |
|Write | |cmdtype=2, , write=1, |
|Write | |1 |Re-execute the command when is accessed
|Write | |value1 |
|Write | |value2 |
|... |... |... |...
|Write | |valueN |
|Write | |0 |Disable auto-exec
|===

==== Triggers

A debugger can use hardware triggers to halt a hart when a certain event
occurs. Below are some examples, but as there is no requirement on the
number of features of the triggers implemented by a hart, these examples
might not be applicable to all implementations. When a debugger wants to
set a trigger, it writes the desired configuration, and then reads back
to see if that configuration is supported. All examples assume XLEN=32.

Enter Debug Mode when the instruction at 0x80001234 is executed, to be
used as an instruction breakpoint in ROM:

|r|r|L| & 0x6980105c & type=6, dmode=1, action=1, select=0, match=0,
m=1, s=1, u=1, vs=1, vu=1, execute=1 +
& 0x80001234 & address +

Enter Debug Mode when performing a load at address 0x80007f80 in M-mode
or S-mode or U-mode:

|r|r|L| & 0x68001059 & type=6, dmode=1, action=1, select=0, match=0,
m=1, s=1, u=1, load=1 +
& 0x80007f80 & address +

Enter Debug Mode when storing to an address between 0x80007c80 and
0x80007cef (inclusive) in VS-mode or VU-mode when hgatp.VMID=1:

|r|r|L| & 0x69801902 & type=6, dmode=1, action=1, chain=1, select=0,
match=2, vs=1, vu=1, store=1 +
& 0x80007c80 & start address (inclusive) +
& 0x03000000 & mhselect=6, mhvalue=0 +
& 0x69801182 & type=6, dmode=1, action=1, select=0, match=3, vs=1, vu=1,
store=1 +
& 0x80007cf0 & end address (exclusive) +
& 0x03000000 & mhselect=6, mhvalue=0 +

Enter Debug Mode when storing to an address between 0x81230000 and
0x8123ffff (inclusive):

|r|r|L| & 0x698010da & type=6, dmode=1, action=1, select=0, match=1,
m=1, s=1, u=1, vs=1, vu=1, store=1 +
& 0x81237fff & 16 upper bits to match exactly, then 0, then all ones. +

Enter Debug Mode when loading from an address between 0x86753090 and
0x8675309f or between 0x96753090 and 0x9675309f (inclusive):

|r|r|L| & 0x69801a59 & type=6, dmode=1, action=1, chain=1, match=4, m=1,
s=1, u=1, vs=1, vu=1, load=1 +
& 0xfff03090 & Mask for low half, then match for low half +
& 0x698012d9 & type=6, dmode=1, action=1, match=5, m=1, s=1, u=1, vs=1,
vu=1, load=1 +
& 0xefff8675 & Mask for high half, then match for high half +

==== Handling Exceptions

Generally the debugger can avoid exceptions by being careful with the
programs it writes. Sometimes they are unavoidable though, e.g. if the
user asks to access memory or a CSR that is not implemented. A typical
debugger will not know enough about the hardware platform to know what’s
going to happen, and must attempt the access to determine the outcome.

When an exception occurs while executing the Program Buffer, becomes
set. The debugger can check this field to see whether a program
encountered an exception. If there was an exception, it’s left to the
debugger to know what must have caused it.

[[quickaccess]]
==== Quick Access

There are a variety of instructions to transfer data between GPRs and
the ` data` registers. They are either loads/stores or CSR reads/writes.
The specific addresses also vary. This is all specified in . The
examples here use the pseudo-op `transfer dest, src` to represent all
these options.

Halt the hart for a minimum amount of time to perform a single memory
write:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |`transfer arg2, s0` |Save
|Write |`progbuf1` |`transfer s0, arg0` |Read first argument (address)
|Write |`progbuf2` |`transfer arg0, s1` |Save
|Write |`progbuf3` |`transfer s1, arg1` |Read second argument (data)
|Write |`progbuf4` |`sw s1, 0(s0)` |
|Write |`progbuf5` |`transfer s1, arg0` |Restore
|Write |`progbuf6` |`transfer s0, arg2` |Restore
|Write |`progbuf7` |`ebreak` |
|Write | |address |
|Write |`data1` |data |
|Write | |0x10000000 |Perform quick access
|===

This shows an example of setting the bit in to enable a hardware
breakpoint in M-mode. Similar quick access instructions could have been
used previously to configure the trigger that is being enabled here:

[cols="^,>,<,<",options="header",]
|===
|Op |Address |Value |Comment
|Write | |`transfer arg0, s0` |Save
|Write |`progbuf1` |`li s0, (1 << 6)` |Form the mask for bit
|Write |`progbuf2` |`csrrs x0, , s0` |Apply the mask to
|Write |`progbuf3` |`transfer s0, arg2` |Restore
|Write |`progbuf4` |`ebreak` |
|Write | |0x10000000 |Perform quick access
|===

=== Native Debugger Implementation

The spec contains a few features to aid in writing a native debugger.
This section describes how some common tasks might be achieved.

[[nativestep]]
==== Single Step

Single step is straightforward if the OS or a debug stub runs in M-Mode
while the program being debugged runs in a less privileged mode. When a
step is required, the OS or debug stub writes , , before returning
control to the lower user program with an `mret` instruction.

Stepping code running in the same privilege mode as the debugger is more
complicated, depending on what other debug features are implemented.

If hardware implements and , then stepping through non-trap code which
doesn’t allow for nested interrupts is also straightforward.

If hardware automatically prevents triggers from matching when entering
a trap handler as described in
Section #sec:nativetrigger[[sec:nativetrigger]], then a carefully
written trap handler can ensure that interrupts are disabled whenever
the icount trigger must not match.

If neither of these features exist, then single step is doable, but
tricky to get right. To single step, the debug stub would execute
something like:

....
    li    t0, {icount-count}=4, {icount-action}=0, {icount-m}=1
    csrw  tdata1, t0    /* Write the trigger. */
    lw    t0, 8(sp)     /* Restore t0, count decrements to 3 */
    lw    sp, 0(sp)     /* Restore sp, count decrements to 2 */
    mret                /* Return to program being debugged. count decrements to 1 */
....

There is an additional problem with using to single step. An instruction
may cause an exception into a more privileged mode where the trigger is
not enabled. The exception handler might address the cause of the
exception, and then restart the instruction. Examples of this include
page faults, FPU instructions when the FPU is not yet enabled, and
interrupts. When a user is single stepping through such code, they will
have to step twice to get past the restarted instruction. The first time
the exception handler runs, and the second time the instruction actually
executes. That is confusing and usually undesirable.

To help users out, debuggers should detect when a single step restarted
an instruction, and then step again. This way the users see the expected
behavior of stepping over the instruction. Ideally the debugger would
notify the user that an exception handler executed the first time.

The debugger should perform this extra step when the PC doesn’t change
during a regular step.

It is safe to perform an extra step when the PC changes, because every
RISC-V instruction either changes the PC or has side effects when
repeated, but never both.

To avoid an infinite loop if the exception handler does not address the
cause of the exception, the debugger must execute no more than a single
extra step.
