Debugger support

I will send my patch soon to LLDB devs. As of instructions, I may create some blog post where I will put all details and will share it here.

5 Likes

Did anyone tried my debug branch yet?
I would love to have some feedback and if there any bugs or suggestions?

I am curious what values are shown for primitives (less than i64/u64) on stack in LLDB and GDB

I tried it. Both with gdb and lldb. I tried to see the value of a string, I could just see a pointer value, I don’t know if that’s expected.

Maybe you could include detailed instructions on how to see the improvements you made?

Did you use VS Code?

What debugger plugin for VS Code did you use?

In my case I was using CodeLLDB instead of NativeDebug as NativeDebug is not respecting value formatters.

To see the value of the string you will need my value formatter that I originally put into lldb_formatters/crystal_formatters.py, in the latest commit it was moved into etc/lldb/crystal_formatters.py as per @RX14 comments in PR.

It has to be loaded into LLDB. via command (in my case):

command script import /Users/sergey/Projects/crystal/crystal/etc/lldb/crystal_formatters.py

In LLDB command line interface (without any plugins) if you will type v command it should use these Crystal value formatters to show the values for Strings and Arrays.

1 Like

I used lldb from the command line. Same for gdb.

Actually, I also used VS Code later using CodeLLDB but I only could see pointer values for strings. Maybe I was missing the formatter.

Oh, I didn’t know that. I’ll do it soon.

(That’s why I think you should make a thorough and compact guide about how to put all the stuff you did together. Otherwise I have to go through this entire thread which is like 124 posts now.)

2 Likes

I will post it soon with all screenshots.

Other than that did you able to see values of reference based unions and value based variables?
It should show properly reference based variables and should have some garbage for value based variables for values less than 64 bits on 64-bit based computers.

1 Like

Ok. I fixed all stuff that was failing the tests and code is ready to be merged to the master.
Tutorial how to setup debugger is coming.

9 Likes

Thank you so much for working on this. I’ve been following you progress and what you do is absolutely amazing!

Great tooling support is very important for language growth. Debugger won’t replace REPL, but I do feel like I’ll need to use it more frequently than in dynamic language.

6 Likes

@vlazar thank you very much for your kind words!

I feel your pain for debugger support so this is why for me it was highest priority to solve as it unblocks other roadblocks on the Crystal road to wider adoption.

I started to build PEG.js Crystal grammar by modifying and ripping apart JavaScript grammar so hopefully I will make it workable soon and will modify VS Code Crystal plugin to support it naturally. It will unblock some other features that at this moment in the Scry but I think it is little heavy for VS so I am thinking about some lighter version of it on JS

3 Likes

With you current changes will this also work?

It would be so cool!

1 Like

I have to test it to be sure that it will work. But if @RX14 tried my branch and it works with her code then it definitely should work then.

1 Like

@asterite @straight-shoota @bcardiff @RX14 @ysbaddaden

Before my debug branch is merged I have some idea that I wanted to exercise with you guys.
I am thinking about ENV variable that will need to be set for now until we get official LLDB and GDB support to enable to use CPP_LANG_DEBUG_IDENTIFIER instead of CRYSTAL_LANG_DEBUG_IDENTIFIER. When support will be added to those debuggers we can stop setting that flag and it will use Crystal language debug identifier.

I am thinking about DEBUG_ID=cpp so if it is set it will use CPP_LANG_DEBUG_IDENTIFIER

or we can set it to crystal and use CPP_LANG_DEBUG_IDENTIFIER by default.

Your thoughts?

Let’s use a debug identifier that exists and works. Once we get an identifier for Crystal in lldb/gdb or wherever, we can change Crystal to use it. I don’t think we need this to be configurable.

2 Likes

Ok. Let do it this way for now then.

By some reason Alpine unit tests are failing with some memory access fail. Can someone re-trigger that Alpine build (can I do it, or it should be someone with extra permissions)?
I want to be sure that it is not my code issue.

@asterite @bcardiff @straight-shoota @RX14
Websockets test is flakey at it’s best!

It is passed before but failed now again with the only change: removed comment from the code.
I have no clue at this moment how to get green builds as it fails out of nowhere.
Is there any way to re-trigger the build?

It said that someone with write permissions can do it:

We should add some docs to https://crystal-lang.org/reference

I combined the info here and in https://github.com/amberframework/docs/blob/master/examples/crystal-debug.md and it was not the best.

This is amazing! I’ve built it and have it running with lldb and OSX Catalina. Does it work best with a specific version of lldb? The CrystalString_SummaryProvider from “crystal_formatters.py” runs into int() conversion issues for me.

Is it possible to inspect instance variables?

I use macOS Mojave still as Catalina is not that stable for me.
I use crystal_formatters.pl with no issue as it not failing int() conversion. Can you dump your exception message here?

Current debugger has issue with converting stack values that not address and has size less than native address size so on macOS it will have issues with int values that are smaller than 64 bits.

I made a small patch to fix it in LLDB but I didn’t submitted it yet to LLDB team. I placed info how to patch it at the beginning of this thread.

Yes it is possible to inspect instance variables as they are stored inside of self variable.

1 Like

@asterite @bcardiff @straight-shoota @RX14 @ysbaddaden
I just found that GDB having the same issue as unmodified LLDB:

For stack value (value object) lower than native address size it shows incorrect value as it is trying to get value from the stack of address size: here is the example:

require "http/server"

module Crweb
  VERSION = "0.1.0"
  port = 3000

  def self.startHTTPServer(serverPort)
    server = HTTP::Server.new do |context|
      context.response.content_type = "text/plain"
      context.response.print "!"
    end

    server.listen(serverPort)
  end

  puts "PID=#{Process.pid}"
  puts "Initial port: #{port}"

  puts "Listening on http://127.0.0.1:#{port}"

  startHTTPServer port
end

In def self.startHTTPServer(serverPort) server port lldb with my fix shows proper value of 3000 but original LLDB anf GDB will show you

(gdb) p serverPort
Cannot access memory at address 0x5563cae000000bb8

where 0x00000bb8 is correct value (3000) and 0x5563cae0 is part of the other value.

I will check what we can do to fix this issue on our side. The only solution at this moment I have is to cast all stack (value) objects that are smaller than address size to the address size.

As you can see in this case of llvm-dwarfdump:

0x00039c83:   DW_TAG_subprogram
                DW_AT_low_pc    (0x00000000000e8a90)
                DW_AT_high_pc   (0x00000000000e8ad4)
                DW_AT_frame_base        (DW_OP_reg7 RSP)
                DW_AT_linkage_name      ("startHTTPServer")
                DW_AT_name      ("startHTTPServer")
                DW_AT_decl_file ("/home/ubuntu/Projects/crystal/test/crweb.cr")
                DW_AT_decl_line (7)
                DW_AT_type      (0x0005d43f "int")

0x00039ca0:     DW_TAG_formal_parameter
                  DW_AT_location        (0x00041a91
                     [0x00000000000e8a90,  0x00000000000e8a98): DW_OP_breg5 RDI+0
                     [0x00000000000e8a98,  0x00000000000e8ad4): DW_OP_breg7 RSP+12, DW_OP_deref)
                  DW_AT_name    ("serverPort")
                  DW_AT_decl_file       ("/home/ubuntu/Projects/crystal/test/crweb.cr")
                  DW_AT_decl_line       (7)
                  DW_AT_type    (0x0000004a "Int32")

LLVM is always creates Deref from stack and that size is the address size: [0x00000000000e8a98, 0x00000000000e8ad4): DW_OP_breg7 RSP+12, DW_OP_deref). I have to figure out how to avoid it from generation.

1 Like