Debugger support

The error I get is:

(lldb) v
Traceback (most recent call last):
  File "./tmp/crystal-debug/etc/lldb/crystal_formatters.py", line 51, in CrystalString_SummaryProvider
    byteSize = int(value.child[0].value)
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'
(Foo *) self = 0x00000001000000a1
(String *) x = 0x0000000400000001

Looking at this more closely, the problem might be related to another problem. Here is my small test program:

class Foo
  def foo(x)
    puts x
  end
end
foo = Foo.new
foo.foo("test")

I’m compiling this ("./crystal-debug/bin/crystal build --debug foo.cr") and running it (“lldb foo”). Then I load the formatters, set a breakpoint at line #3 (the puts), and run. The error occurs when I list the local variables in the frame (“v”).

When I try to deference x directly, I see this:

(lldb) p *x
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory

So maybe this is actually the culprit…

Yes, the issue is with lldb resolver:

(Foo *) self = 0x00000001000000a1
(String *) x = 0x0000000400000001

the address is not correct as LLDB is trying to dereference self and then dereference it again. It is double dereferencing issue.

I think I may have some idea why it is happening and probably may solve it. Let me try it and I will let you know if this is successful.
If not, then you will have to use my LLDB patch that I described above but it will take you some time to build patched LLDB :frowning:

2 Likes

I am trying to find how I can create a pointer to the function argument in llvm.dbg.declare()

By some reason it is not creating extra pointer for function arguments as it does for local variables. Because of that this bug that @tsundsted is described is happening as debugger is trying to dereference the actual value but not the address of that value on the stack.

Ok, I cheated a little. I just dumped .ll code from llvm_ext.o code with full debug information
by llvm-dis llvm_ext.o and I found interesting thing (I was actually thinking to try it as a last resort): clang uses store command to load the arg pointer and only then it calls llvm.dbg.declare on that pointer:

; Function Attrs: noinline nounwind optnone ssp uwtable
define %struct.LLVMOpaqueMetadata* @LLVMExtDIBuilderCreateFile(%"class.llvm::DIBuilder"*, i8*, i8*) #0 !dbg !62605 {
  %4 = alloca %"class.llvm::DIBuilder"*, align 8
  %5 = alloca i8*, align 8
  %6 = alloca i8*, align 8
  %7 = alloca %"class.llvm::StringRef", align 8
  %8 = alloca %"class.llvm::StringRef", align 8
  %9 = alloca %"class.llvm::Optional.111", align 8
  %10 = alloca %"class.llvm::Optional.116", align 8
  store %"class.llvm::DIBuilder"* %0, %"class.llvm::DIBuilder"** %4, align 8
  call void @llvm.dbg.declare(metadata %"class.llvm::DIBuilder"** %4, metadata !62609, metadata !DIExpression()), !dbg !62610
  store i8* %1, i8** %5, align 8
  call void @llvm.dbg.declare(metadata i8** %5, metadata !62611, metadata !DIExpression()), !dbg !62612
  store i8* %2, i8** %6, align 8
  call void @llvm.dbg.declare(metadata i8** %6, metadata !62613, metadata !DIExpression()), !dbg !62614

So I have to implement the same way and it should solve our issue (I hope)

1 Like

That’s not cheating! That’s clever investigating!

I was joking as in that joke: “Tough programmers never look and use someones code, they build it think of it themselves!” :smiley:

Jokes aside I was able to implement this approach but I have some situation when it is trying to use variable that is assigned only later:

Module validation failed: Instruction does not dominate all uses!
  %signal1 = getelementptr inbounds %closure_1, %closure_1* %2, i32 0, i32 0, !dbg !17030
  store i32* %signal1, i32** %dbg.signal, !dbg !17032

so I am thinking what will be the best place to declare it. I will insert this data at the end of the last entry block just before codegen_return()

OK. Here is the status:

  1. I was able to make it work without my lldb patch by using example from clang IR dump.

  2. It is still flakey as it sometimes not showing info and then after few steps into function it starts to show information:

Look at port value in the example just above.

So I am trying to figure out how to make it stable.

3 Likes

@asterite @bcardiff @straight-shoota @RX14:

By digging in debug IR file for clang I found that it uses function attributes NoInline, OptNone, SSP, NoUnwind

When I set the same (covered by @debug.variables? check) I am getting IR validation errors like:

PHI nodes not grouped at top of basic block!
  %4 = phi %"Channel::Receiver(Nil)"* [ null, %then ], [ %2, %else ], !dbg !58714
label %exit
PHI nodes not grouped at top of basic block!
  %4 = phi %"Channel::Sender(Nil)"* [ null, %then ], [ %2, %else ], !dbg !59105
label %exit
LandingPadInst not the first non-PHI instruction in the block.
  %2 = landingpad %landing_pad
          cleanup, !dbg !59712

Trying to figure out what is wrong and how to solve this issue.

Phi’s can only happen at the very beginning of the blocks. No other instructions can appear between the start of the block and the last phi than other phis.

How does that block starts?

1 Like

I see. I may have llvm.dbg.declare at the beginning of block because of some cases if I will keep them in alloca they may dominate some values (2 cases in crweb.cr example, mostly on signal vars as it happens after GEP)
To solve it I had to add it as it allocates on the fly in the current block and not alloca.
I keep only local variables in alloca block.

Will try to figure out if I can declare them before phi not on the top of the blocks.

And yes, that was exactly the case as you said:

exit:                                             ; preds = %else, %then
  call void @llvm.dbg.declare(metadata %"Channel::Sender(Nil)"** %t, metadata !59101, metadata !DIExpression()), !dbg !59106
  %4 = phi %"Channel::Sender(Nil)"* [ null, %then ], [ %2, %else ], !dbg !59105
  ret %"Channel::Sender(Nil)"* %4, !dbg !59105

I think I know how to solve it though.
Will try it.

I was able to compile it cleanly. Will test it and will try to refactor the code.

Just a quick note: I enjoy following this thread even though you might as well be talking about the innards of steam trains.
Please feel free to keep updating with in-progress stuff, maybe I’ll still learn something about steam engines :smile:

2 Likes

@mavu Steel trains and steam engines are all history now. We are talking about innards of the rocket engines as we are all rocket scientists here, right? :laughing:

You probably saw the “Back to the Future” when Doc flew the steel train with the antigrav engine.
This is what we want to achieve here :slight_smile:

This is my stigma of being a chatterbox :slight_smile: as I love to share gathered information that may be interested to other people.

4 Likes

Ok. I just committed my changes that should work in LLDB and GDB without my patch.
If someone feels adventurous please try it and let me know if it works okay for you. try to use both LLDB and GDB.

Refactoring work is still in progress but it works at least.

4 Likes

I am just curious if someone already tried my last changes? I need feedback on how it works for you under original lldb and gdb.

https://github.com/skuznetsov/crystal/tree/debug

1 Like

Link to setup steps for testing the debugger?

@drhuffman12 You can follow instructions in this post that I posted before: Debugger support

Please disregard LLDB bug part as it is not relevant anymore (that was my overlook as LLDB is working just okay, the same way as GDB) so these current changes are making Crystal debug support to work normally with LLDB and GDB without any patching.

Full walkthrough is coming soon.

2 Likes

I’ve tested with lldb and CodeLLDB inside VS Code.

  1. Local variables Int,Float,Hash are showing.

  2. Strings only the first char and then properties easy : {bytesize:8, length:8, c:'e'}
    Was defined as

easy = “easy_str”

  1. Instance variables are not showing
  2. Class variables are not showing

1 Like

Strings will be shown (inside of lldb only for now) if you will load the format helper that I made and it stored in crystal’s source code folder etc/lldb/crystal_formatters.py
It will show strings in CodeLLDB and strings and arrays in raw LLDB command line when you will type v command.

Class variables are not shown yet.
Instance variables are stored in self object but by some reason it is not shown in your example. I will have to figure it out. I will type your example and see what is wrong.

Yup Strings work with the formatter

(lldb) frame variable

(SDB::DebugTest *) self = 0x00000000000000a4

(String *) easy = 0x00000001000ac2b0 "easy_str"

(Hash(String, Int32) *) __temp_440 = 0x00000001018c4600

(Hash(String, Int32) *) lhash = 0x00000001018c4600

(int) numberi = 1

(double) numberf = 1

Your branch is also much better with linenumbers and with step in operations, it’s pretty usable now! Excellent work.

3 Likes