Add semi-hosting example
This commit adds an example showing how to implement a semi-hosting
environment using libgdbs. This example is for RISC-V and requires
recent Pharo-ArchC [1] for assembling / disassembling of RISC-V code.
[1]: https://github.com/shingarov/Pharo-ArchC
--- a/GDBEvent.st Thu Oct 14 00:21:40 2021 +0200
+++ b/GDBEvent.st Mon Nov 15 16:16:04 2021 +0000
@@ -162,6 +162,12 @@
"Created: / 01-06-2014 / 23:38:07 / Jan Vrany <jan.vrany@fit.cvut.cz>"
!
+isStopEvent
+ ^ false
+
+ "Created: / 10-09-2021 / 13:56:02 / Jan Vrany <jan.vrany@labware.com>"
+!
+
isTargetOutputEvent
^ false
--- a/GDBStoppedEvent.st Thu Oct 14 00:21:40 2021 +0200
+++ b/GDBStoppedEvent.st Mon Nov 15 16:16:04 2021 +0000
@@ -134,6 +134,14 @@
"Created: / 10-03-2021 / 14:21:11 / Jan Vrany <jan.vrany@labware.com>"
! !
+!GDBStoppedEvent methodsFor:'testing'!
+
+isStopEvent
+ ^ true
+
+ "Created: / 10-09-2021 / 13:56:16 / Jan Vrany <jan.vrany@labware.com>"
+! !
+
!GDBStoppedEvent class methodsFor:'documentation'!
version_HG
--- a/tests/GDBDebuggerExamples.st Thu Oct 14 00:21:40 2021 +0200
+++ b/tests/GDBDebuggerExamples.st Mon Nov 15 16:16:04 2021 +0000
@@ -147,6 +147,181 @@
"Modified: / 27-03-2021 / 07:01:57 / Jan Vrany <jan.vrany@labware.com>"
!
+example02_semihosting_riscv
+ "
+ This example demonstrates how to use libgdbs to run semi-hosted
+ code on RISC-V.
+
+ Please note, that this code uses MachineArithmetic [1] and (Pharo-)ArchC [2]
+ to assemble code.
+
+ At any point, you may open a VDB on debugger instance by evaluating
+
+ VDBDebuggerApplication openFor: gdb
+
+ [1]: https://github.com/shingarov/MachineArithmetic
+ [2]: https://github.com/shingarov/Pharo-ArchC
+ "
+
+ | fatshell nzone code exit pdl done stopOrExit |
+
+ "First, we need a 'fatshell'. On Debian, you may compile it by
+
+ cd .../jv/libgdbs/tests/c
+ make CC=riscv64-linux-gnu-gcc BUILD_TARGET=riscv64-unknown-linux-gnu
+
+ To see fatshell source, inspect:
+
+ ((Smalltalk getPackageDirectoryForPackage:'jv:libgdbs/tests') / 'c' / 'fatshell.c') asFilename contents asString
+
+ "
+ fatshell := (Smalltalk getPackageDirectoryForPackage:'jv:libgdbs/tests') / 'c' / 'riscv64-unknown-linux-gnu' / 'fatshell'.
+
+ self skipIf: fatshell exists not
+ description: 'fatshell for RISC-V does not exists'.
+ self skipIf: (OperatingSystem pathOfCommand: 'qemu-riscv64-static') isNil
+ description: 'QEMU not found'.
+
+ pdl := AcProcessorDescriptions riscv.
+
+ "First, start QEMU: "
+ TerminalView
+ openOnCommand:'qemu-riscv64-static -g 1234 ', fatshell pathName, ' 1 2 3 4'
+ onExit:[:vt | vt topView close ].
+
+ "...then create a new GDB and connect to QEMU: "
+ gdb := GDBDebugger new.
+ gdb executable: fatshell.
+ gdb targetConnect: 'remote' parameters: #('localhost:1234').
+
+ "Now let's determine where the nzone is: "
+ nzone := ((gdb evaluate: '(void*)&nzone') valueFormatted: GDBOutputFormat signedDecimal) asInteger.
+
+ "By default, the nzone is empty, so we generate and inject a function that
+ just calls to the semi-hosting environment and return. This code would normally
+ be already there or might get generated by worked-on JIT for stuff that is
+ not yet supported by that very JIT.
+
+ The semihosting call is encoded as (see [1]):
+ slli zero, zero, 0x1f # Entry NOP
+ ebreak # Break to debugger
+ srai zero, zero, 7 # NOP encoding the semihosting call number 7
+
+ [1]: Volume I: RISC-V Unprivileged ISA V20191213, page 28.
+ "
+ code := ByteArray streamContents: [:code |
+ pdl assemble: #( " Start of the function"
+ 'slli zero, zero, 0x1f' " Entry NOP "
+ 'ebreak' " Break to debugger "
+ 'srai zero, zero, 7' " NOP encoding the semihosting call number 7 "
+ 'jalr zero, ra, 0' " Return"
+ " End of the function")
+ on: code.
+ ].
+ gdb selectedInferior memoryAt: nzone put: code.
+
+ "Now enter a (synchronous semi-hosting loop)"
+ done := false.
+ [ done ] whileFalse: [
+ "Run the inferior and wait until it either finishes
+ or stop on some other condition. Here we use simple ULD / SmallRSP -like API"
+
+ stopOrExit := gdb c.
+ stopOrExit isStopEvent ifTrue: [
+ "Inferior stopped, perhaps because of doing call to semihosting environment,
+ (that is, to us).
+
+ First, we need to check whether this is a because of a semi-hosting call or
+ because of some other reason (such as segfault, receiving SIGTERM or whatever).
+
+ We do this by checking that we stopped on an ebreak in slli / ebreak / srai sequence.
+ Again, here we're using simple ULD / SmallRSP -like API.
+ "
+ | semihostingCall pc code insn1 insn2 insn3 |
+
+ semihostingCall := false.
+ pc := gdb getRegister: 'pc'.
+ code := gdb memoryAt: pc - 4 count: 4*3.
+ insn1 := pdl decode: (code unsignedInt32At: 1 MSB: false).
+ "Yes, comparing instruction disassembly is a stupid way of checking
+ whether the instruction is what we expect, but it is simple and
+ concise. Real implementatin may want to compare bytes directly."
+ insn1 printString = 'slli: slli zero, zero, 0x1F' ifTrue: [
+ insn2 := pdl decode: (code unsignedInt32At: 5 MSB: false).
+ insn2 printString = 'ebreak: ebreak' ifTrue: [
+ insn3 := pdl decode: (code unsignedInt32At: 9 MSB: false).
+ (insn3 printString startsWith: 'srai: srai zero, zero, ') ifTrue: [
+ semihostingCall := true.
+ ].
+ ].
+ ].
+ semihostingCall ifTrue: [
+ | callNo |
+
+ "First, we extract the semihosting call number from instruction
+ and then, depending on which call was made, we perform some magic.
+
+ In this example only call no. 7 is supported (an arbitrary choice).
+ Let's say call no. 7 means to return return factorial of first
+ argument."
+
+ callNo := insn3 fieldValue: 'shamt'.
+ callNo == 7 ifTrue: [
+ | x |
+ "First, extract first argument from first integer argument register (a0)..."
+ x := gdb getRegister: 'a0'.
+
+ "...then compute the factorial..."
+ x := x factorial.
+
+ "...and return it in first integer return register (again, a0)."
+ gdb setRegister: 'a0' to: x.
+
+ "Finally, advance PC past ebreak..."
+ gdb setRegister: 'pc' to: pc + 4.
+
+ "...and we're done. Now we just go back to the beggining,
+ continuing until there's another semihosting calls to handle
+ (or the debugee finishes or stops for some other reason)."
+ ] ifFalse: [
+ "Unsupported call.
+
+ You may open a debugger and start debugging interactively by evaluating
+
+ VDBDebuggerApplication openFor: gdb
+ "
+ self halt.
+ ].
+ ] ifFalse: [
+ "The debuggee stopped for some other reason (like, SIGSEGV,
+ regular breakpoint etc)
+
+ You may open a debugger and start debugging interactively by evaluating
+
+ VDBDebuggerApplication openFor: gdb
+
+ "
+ self halt.
+ ].
+ ] ifFalse: [
+ "Inferior exited, we're done."
+ done := true.
+ ].
+ ].
+
+ "/ Here just check that the exit code is 120, that is 5!!
+ self assert: stopOrExit exitCode = 120.
+
+ "Finally, run the inferior"
+ exit := gdb send: 'quit' andWait: false.
+
+ "This is here so you can place a breakpoint on this line :-)"
+ exit yourself.
+
+ "Created: / 09-09-2021 / 10:49:56 / Jan Vrany <jan.vrany@labware.com>"
+ "Modified: / 10-09-2021 / 22:24:05 / Jan Vrany <jan.vrany@labware.com>"
+!
+
example_README
"
This is an example from libgdb's README.md.
--- a/tests/c/fatshell.c Thu Oct 14 00:21:40 2021 +0200
+++ b/tests/c/fatshell.c Mon Nov 15 16:16:04 2021 +0000
@@ -1,4 +1,5 @@
#include <stdio.h>
+#include <stdlib.h>
#define NZONE_SIZE 1024*1024
@@ -17,5 +18,5 @@
static entry_func entry = (entry_func)(&nzone);
int main(int argc, char **argv) {
- return entry(argc, argv);
+ exit(entry(argc, argv));
}
\ No newline at end of file
--- a/tests/jv_libgdbs_tests.st Thu Oct 14 00:21:40 2021 +0200
+++ b/tests/jv_libgdbs_tests.st Mon Nov 15 16:16:04 2021 +0000
@@ -60,6 +60,7 @@
my classes is considered to be a prerequisite package."
^ #(
+ #'ArchC-Core' "AcProcessorDescriptions - referenced by GDBDebuggerExamples>>example02_semihosting_riscv"
)
!