Scheme/Guile Debugging

As the preferred GNU extension language, I’m a bit surprised that no YouTube instruction videos exist which demonstrate basic guile debugging. The manual exists, of course. But, there is very sparse mention of debugging examples, at least on my google searches.
I hope that after learning enough guile scheme, I am able to make better sense/use of the official manual’s debugging section.
I mean, the debugger is obviously being used – with all the GNU work being done. But crickets as far as google is concerned.

2 Likes

I had a very similar experience when I took the class. The GNU manuals are often comprehensive but seem to avoid being practically useful almost as a matter of principle or as some form off initiation into the guild. :wink:

Scheme The GNU ecosystem could use something like the new Emacs examples-based docs subsystem. Actually, Emacs could use some socialization that that subsystem now exists as well because, for the life of me, I cannot find the name of it neither via search engine nor via LLM. :\

For sure!
To add: I’ve gotten as far as using ,bt & ,up / ,down & ,bp & ,locals &etc. But it is very confusing, almost like navigating in the dark, but being told you can turn the lights on at any time, all you have to do is provide the lightbulb implementation.

Possible alternatives: there is a gdb - guile option.
Otherwise, the debugging experience of Clojure or Haskell seems like the way to go, unfortunately :confused:.

What specific trouble did you run into with debugging? I’m planning to add some material on using the debugger to the Guile course (which you’ll have access to), so it’d be good to learn from your experience!

Thanks David!
When I get home from classes later on tonight,
I’ll post some screenshots (pictures worth 1000 words). :grinning:

1 Like

Some attempts at debugging:
debug-session.zip (5.2 MB)

I hope that after learning enough guile scheme, I am able to make better sense/use of the official manual’s debugging section.

Be aware of implementation related pitfalls, especially when using debugging functionality in a guix repl for guile, that may not be obvious on first sight.

guix repl behaves differently compared to a plain guile REPL in terms of available debugging tooling. trace is stripped/non-functional in a guix repl (due to performance optimizations if I recall the reason correctly, although only being 40% sure; was discovered during a guix days session on profiling and filed as a bug Difference between trace behavior in guix repl and guile) but works in a normal guile REPL:

(guixps13 (~)) λ guix repl
scheme@(guix-user)> (use-modules (guix scripts build))
scheme@(guix-user)> ,trace (guix-build "hello")
/gnu/store/6fbh8phmp3izay6c0dpggpxhcjn4xlm5-hello-2.12.1

vs. a normal guile repl that gives away:

scheme@(guile-user)> ,trace (guix-build "hello")
trace: |  (guix-build "hello")
trace: |  |  (_ (guix scripts) #:ensure #f)
trace: |  |  (_ #<procedure 7fd419889fc0 at ice-9/boot-9.scm:3241:7 ()>)
trace: |  |  |  (lock-mutex #<mutex 7fd421300fc0>)
trace: |  |  |  #t
trace: |  |  |  (_)
trace: |  |  |  |  (nested-ref-module #<module () 7fd42128ad20> (guix scripts))
trace: |  |  |  |  |  (module-ref-submodule #<module () 7fd42128ad20> guix)
trace: |  |  |  |  |  |  (_ #<module () 7fd42128ad20>)
trace: |  |  |  |  |  |  #<hash-table 7fd4212ad260 218/443>
trace: |  |  |  |  |  |  (hashq-ref #<hash-table 7fd4212ad260 218/443> guix)
trace: |  |  |  |  |  |  #<directory (guix) 7fd421357280>
trace: |  |  |  |  |  #<directory (guix) 7fd421357280>
trace: |  |  |  |  |  (module-ref-submodule #<directory (guix) 7fd421357280> scripts)
trace: |  |  |  |  |  |  (_ #<directory (guix) 7fd421357280>)
trace: |  |  |  |  |  |  #<hash-table 7fd421378ac0 35/61>
trace: |  |  |  |  |  |  (hashq-ref #<hash-table 7fd421378ac0 35/61> scripts)
trace: |  |  |  |  |  |  #<directory (guix scripts) 7fd421357320>
trace: |  |  |  |  |  #<directory (guix scripts) 7fd421357320>
trace: |  |  |  |  #<directory (guix scripts) 7fd421357320>
trace: |  |  |  |  (_ #<directory (guix scripts) 7fd421357320>)
trace: |  |  |  |  #<interface (guix scripts) 7fd41964b500>
trace: |  |  |  #<directory (guix scripts) 7fd421357320>
trace: |  |  |  (unlock-mutex #<mutex 7fd421300fc0>)

other functionality like time however isn’t affected:

scheme@(guile-user)> ,time (guix-build "hello")
/gnu/store/6fbh8phmp3izay6c0dpggpxhcjn4xlm5-hello-2.12.1
;; 1.346088s real time, 1.091178s run time.  0.409003s spent in GC.

profile also does work in both REPL:

scheme@(guile-user)> ,profile (guix-build "hello")
/gnu/store/6fbh8phmp3izay6c0dpggpxhcjn4xlm5-hello-2.12.1
%     cumulative   self             
time   seconds     seconds  procedure
 37.50      0.09      0.09  write
 12.50      0.03      0.03  ice-9/popen.scm:183:0:reap-pipes
 12.50      0.03      0.03  guix/derivations.scm:157:0:%derivation-input?-procedure
 12.50      0.03      0.03  get-bytevector-n
 12.50      0.03      0.03  string-index
 12.50      0.03      0.03  guix/base32.scm:164:31
  0.00      1.94      0.00  guix/store.scm:1996:8
  0.00      1.85      0.00  guix/packages.scm:1971:11
  0.00      1.61      0.00  guix/packages.scm:1648:7
  0.00      1.61      0.00  guix/packages.scm:1892:6
  0.00      1.61      0.00  guix/monads.scm:487:9
  0.00      0.96      0.00  ice-9/boot-9.scm:1689:4:with-exception-handler
  0.00      0.51      0.00  guix/store.scm:1287:0:call-with-build-handler
  0.00      0.30      0.00  guix/store.scm:2154:0:run-with-store
  0.00      0.27      0.00  srfi/srfi-1.scm:584:5:map1
  0.00      0.27      0.00  guix/store.scm:1357:0:map/accumulate-builds
  0.00      0.24      0.00  guix/scripts/build.scm:711:17
  0.00      0.24      0.00  guix/ui.scm:484:4
  0.00      0.24      0.00  guix/scripts/build.scm:709:2
  0.00      0.24      0.00  guix/scripts/build.scm:714:43
  0.00      0.24      0.00  srfi/srfi-1.scm:672:0:append-map
  0.00      0.24      0.00  guix/store.scm:656:4:thunk
  0.00      0.24      0.00  guix/scripts/build.scm:749:8
  0.00      0.24      0.00  guix/status.scm:835:0:call-with-status-report
  0.00      0.21      0.00  guix/gexp.scm:1180:2
  0.00      0.12      0.00  guix/store.scm:2039:28
  0.00      0.09      0.00  guix/derivations.scm:785:0:derivation
  0.00      0.09      0.00  ice-9/ports.scm:547:0:call-with-output-string
  0.00      0.09      0.00  guix/gexp.scm:1046:2
  0.00      0.06      0.00  guix/derivations.scm:757:2:derivation-hash
  0.00      0.03      0.00  guix/store.scm:2242:0:output-path
  0.00      0.03      0.00  guix/gexp.scm:892:4
  0.00      0.03      0.00  guix/base32.scm:162:2
  0.00      0.03      0.00  guix/grafts.scm:289:7
  0.00      0.03      0.00  guix/gexp.scm:980:11
  0.00      0.03      0.00  guix/derivations.scm:629:2:write-string-list
  0.00      0.03      0.00  guix/derivations.scm:630:4
  0.00      0.03      0.00  guix/grafts.scm:314:0:graft-derivation
  0.00      0.03      0.00  guix/build-system/trivial.scm:54:2
  0.00      0.03      0.00  guix/store.scm:2052:2
  0.00      0.03      0.00  guix/derivations.scm:593:0:escaped-string
  0.00      0.03      0.00  guix/gexp.scm:299:22
  0.00      0.03      0.00  guix/derivations.scm:616:0:write-derivation
  0.00      0.03      0.00  filter
  0.00      0.03      0.00  guix/store.scm:2083:2
  0.00      0.03      0.00  srfi/srfi-1.scm:628:2:for-each
  0.00      0.03      0.00  %after-gc-thunk
  0.00      0.03      0.00  guix/store.scm:1078:4
  0.00      0.03      0.00  guix/grafts.scm:284:2
  0.00      0.03      0.00  anon #x4d0d10
  0.00      0.03      0.00  apply-smob/0
  0.00      0.03      0.00  guix/store.scm:692:0:process-stderr
  0.00      0.03      0.00  guix/gexp.scm:897:13
  0.00      0.03      0.00  guix/serialization.scm:100:0:read-int
  0.00      0.03      0.00  guix/derivations.scm:834:29
  0.00      0.03      0.00  guix/base32.scm:148:0:bytevector-quintet-fold-right
---
Sample count: 8
Total time: 0.239235891 seconds (0.125144584 seconds in GC)

I figured this would be worth mentioning as I can imagine that folks on here may stumble upon this difference in behaviour as well.

2 Likes

I’ve had the same experience. Reading the manual did not help. It goes on about implementation details of the guile vm. I’m still not sure if gdb can be used for debugging, the manual says “GDB support” and “sometimes you may need to debug at the C level”, whatever that means.

the manual says “GDB support”

What’s interesting here is, that the GDB support goes both ways. You can utilize GDB to debug guile, but also use guile to extend GDB since v7.8 with guile by utilizing it for scripting or by starting a guile REPL from inside GDB. But be aware that most distributions that aren’t Guix disable guile support for GDB (at least Debian and Fedora do so).

the manual says “GDB support” and “sometimes you may need to debug at the C level”, whatever that means.

The manual is pretty sparse in these regards/I rarely use GDB support as in for debugging guile, not as in for extending GDB, as guiles breakpoint handling is enough for me in most situations.

In short, you could always:

(define (myunusefulfunction n)
  (display n)
  (myunusefulfunction (1+ n)))

set a breakpoint and get a backtrace:

scheme@(guile-user)> ,break myunusefulfunction 
Trap 0: Breakpoint at #<procedure myunusefulfunction (n)>.
scheme@(guile-user)> (myunusefulfunction 1)
Trap 0: Breakpoint at #<procedure myunusefulfunction (n)>
Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In current input:
      1:0  0 (myunusefulfunction 1)
scheme@(guile-user) [1]> ,q
1Trap 0: Breakpoint at #<procedure myunusefulfunction (n)>
Entering a new prompt.  Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In current input:
      1:0  0 (myunusefulfunction 2)

and all the other things one would expect from a debugger as well, e.g.:

scheme@(guile-user) [1]> ,up
Already at outermost frame.
scheme@(guile-user) [1]> ,down
Already at innermost frame.
scheme@(guile-user) [1]> ,frame
In current input:
      1:0  0 (myunusefulfunction 13)

frame navigation, accessing local variables with ,locals and so on.

Thank you!, this is very useful. I will look at more of those repl commands, I’ve been using them only occasionally (since geiser displays evaluations in the minibuffer) , I guess it is better to spend more time in the repl and master those commands.

If I understood correctly, you set a breakpoint in a “suspect” function or point of failure and examine the backtrace from then backwards, narrowing down to the bug, is this how it is done? .

I still think it would be useful to have the possibility of using gdb to step through the code. In Python it is very easy to set a breakpoint and step through the code using pdb, Guile’s repl workflow is so good that it is not often required, but it would be great to have the best of both worlds.