Add semi-hosting example
authorJan Vrany <jan.vrany@labware.com>
Mon, 15 Nov 2021 16:16:04 +0000
changeset 242 ab6978ab27f5
parent 241 286aa6020b9e
child 243 aaaf3757899b
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
GDBEvent.st
GDBStoppedEvent.st
tests/GDBDebuggerExamples.st
tests/c/fatshell.c
tests/jv_libgdbs_tests.st
--- 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"
     )
 !