helix-editor / helix

A post-modern modal text editor.
https://helix-editor.com
Mozilla Public License 2.0
34.03k stars 2.52k forks source link

Debugger: Show local variables as values, they are currently reported as memory addresses #7007

Open David-Else opened 1 year ago

David-Else commented 1 year ago

I understand that the debugger is experimental, but surely it is useless at the moment if we can't see the value of the variables?

Everything else seems to work, this one fix would make the debugger very useful. Why has it been left in this state, are the memory addresses useful to some people?

mem

HealsCodes commented 1 year ago

It looks like this is down to using lldb-vscode / codelldb without enabling the rust pretty printer flags. If you debug rust on the command-line there is a wrapper called rust-lldb which executes a number of pre-launch commands to setup rust pretty printing for variables. I don't know how to pass XXX to lldb-vscode but it has support to run setup commands as well.

These would be the commands in question:

(lldb) command script import "/Users/USER/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/etc/lldb_lookup.py"
(lldb) command source -s 0 '/Users/USER/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/etc/lldb_commands'

"lldb_commands" executing the following:

(lldb) type synthetic add -l lldb_lookup.synthetic_lookup -x ".*" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(alloc::([a-z_]+::)+)String$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^&(mut )?str$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^&(mut )?\\[.+\\]$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(std::ffi::([a-z_]+::)+)OsString$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(alloc::([a-z_]+::)+)Vec<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(alloc::([a-z_]+::)+)VecDeque<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(alloc::([a-z_]+::)+)BTreeSet<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(alloc::([a-z_]+::)+)BTreeMap<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(std::collections::([a-z_]+::)+)HashMap<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(std::collections::([a-z_]+::)+)HashSet<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(alloc::([a-z_]+::)+)Rc<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(alloc::([a-z_]+::)+)Arc<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(core::([a-z_]+::)+)Cell<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(core::([a-z_]+::)+)Ref<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(core::([a-z_]+::)+)RefMut<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^(core::([a-z_]+::)+)RefCell<.+>$" --category Rust
(lldb) type summary add -F lldb_lookup.summary_lookup  -e -x -h "^core::num::([a-z_]+::)*NonZero.+$" --category Rust
(lldb) type category enable Rust

And here's a comparison - stock lldb vars:

lldb without rust pretty printing

And lldb after executing the two setup commands:

lldb with rust pretty printing
quantonganh commented 1 year ago

It looks like this is down to using lldb-vscode / codelldb without enabling the rust pretty printer flags. If you debug rust on the command-line there is a wrapper called rust-lldb which executes a number of pre-launch commands to setup rust pretty printing for variables. I don't know how to pass XXX to lldb-vscode but it has support to run setup commands as well.

We can ask lldb-vscode to execute these commands by using initCommands.

Add the following to ~/.config/helix/languages.toml:

[[language]]
name = "rust"

[language.debugger]
name = "lldb-vscode"
transport = "stdio"
command = "lldb-vscode"

[[language.debugger.templates]]
name = "binary"
request = "launch"
completion = [ { name = "binary", completion = "filename" } ]
args = { program = "{0}", initCommands = [ "command script import /opt/homebrew/Cellar/rust/1.71.0/lib/rustlib/etc/lldb_lookup.py", "command source -s 0 /opt/homebrew/Cellar/rust/1.71.0/lib/rustlib/etc/lldb_commands" ] }

and voila:

Screen Shot 2023-08-11 at 08 12 21
HealsCodes commented 1 year ago

We can ask lldb-vscode to execute these commands by using initCommands.

Add the following to ~/.config/helix/languages.toml:

[[language]]
name = "rust"

[language.debugger]
name = "lldb-vscode"
transport = "stdio"
command = "lldb-vscode"

[[language.debugger.templates]]
name = "binary"
request = "launch"
completion = [ { name = "binary", completion = "filename" } ]
args = { program = "{0}", initCommands = [ "command script import /opt/homebrew/Cellar/rust/1.71.0/lib/rustlib/etc/lldb_lookup.py", "command source -s 0 /opt/homebrew/Cellar/rust/1.71.0/lib/rustlib/etc/lldb_commands" ] }

Nice start but won't work for the majority of people and will break even on your system anytime soon as you're specifying a versioned path into a homebrew install on AppleSilicon.

Most people will likely install via rust-up (which goes to $HOME/.rustup) and even if they use homebrew the path is different between AppleSilicon (/opt/homebrew) and Intel (/usr/local/homebrew).

The path you want to pass to lldb-vscode is $(rustc --print sysroot)/lib/rustlib/etc/lldb_lookup.py which expands to the correct lldb_lookup.py for your active rustc regardless of which platform or version you're using.

Just leaves the problem that AFAIK neither helix nor lldb-vscode evaluate bash inlines.

[EDIT] - a workaround to get a version-independent path that tries to stay semi up-to-date:

Add this at the end of your $HOME/.zshrc:

# provide a version independent link to rustlib for the currently active rustc
rm -f "$HOME/.local/lib/rustlib" && mkdir -p "$HOME/.local/lib" && ln -s "$(rustc --print sysroot)/lib/rustlib" "$HOME/.local/lib/rustlib"

Then use this path for your languages.toml (replace PATH_TO_YOUR_HOME with whatever your $HOME is, sadly couldn't get around that part):

args = { program = "{0}", initCommands = [ "command script import /PATH_TO_YOUR_HOME/.local/lib/rustlib/etc/lldb_lookup.py", "command source -s 0 /PATH_TO_YOUR_HOME/.local/lib/rustlib/etc/lldb_commands" ] }
quantonganh commented 1 year ago

The goal here is to find out how to pass these script/command to lldb-vscode. You're correct in suggesting the use of symlinks instead of hardcoding the sysroot path, By the way, I think we can use ln -sf command instead of running rm -f "$HOME/.local/lib/rustlib" everytime.

I will update the wiki based on your suggestion.

HealsCodes commented 1 year ago

You're right, ln -sf does the job nicely.

I'm over-all a bit unhappy with having to have a symlink that my shell profile updates as it doesn't cover for those cases where - on the off chance - I update my rust version or switch the active rust version.

In an ideal world we would be able to have some way to tell helix to expand environment variables or execute backtick or inline shell statements so the line could really be reduced to command script import $(rustc --print sysroot)/lib/... (or mayhap that would be a PR for lldb-vscode really I'm not sure who should have responsibility).

Thank you for updating the wiki - I think rust debugging is one of the more asked-for topics in Helix :)

HealsCodes commented 1 year ago

@quantonganh - I am so sorry for pinging yet again bit I believe I found an even more elegant solution that also gets rid of the downsides of creating a symlink.

Copy this python snippet to /usr/local/etc/lldb_vscode_rustc_primer.py:

import subprocess
import pathlib
import lldb

# determine the sysroot for the active rust interpreter
rustlib_etc = pathlib.Path(subprocess.getoutput('rustc --print sysroot')) / 'lib' / 'rustlib' / 'etc'
if not rustlib_etc.exists():
    raise RuntimeError('Unable to determine rustc sysroot')

# load lldb_lookup.py and execute lldb_commands with the correct path
lldb.debugger.HandleCommand(f"""command script import "{rustlib_etc / 'lldb_lookup.py'}" """)
lldb.debugger.HandleCommand(f"""command source -s 0 "{rustlib_etc / 'lldb_commands'}" """)

And then use this configuration for helix:

[[language]]
name = "rust"

[language.debugger]
name = "lldb-vscode"
transport = "stdio"
command = "lldb-vscode"

[[language.debugger.templates]]
name = "binary"
request = "launch"
completion = [ { name = "binary", completion = "filename" } ]
args = { program = "{0}", initCommands = [ "command script import /usr/local/etc/lldb_vscode_rustc_primer.py" ] }

The script takes care of querying the correct rustc sysroot and loading the bindings right at the moment when lldb-vscode is started and is 100% independent of symlinks or ARM / Intel / rust-up or homebrew setups as long as you have rustc in your system path.

quantonganh commented 1 year ago

@Shirk Nice solution. Would you mind updating the wiki?

cino189 commented 2 weeks ago

Revisiting this due to a name change for lldb-vscode. Since now it is called lldb-dap, at least on arch, i simply had to replace lldb-vscode with lldb-dap like so:

[[language]]
name = "rust"

[language.debugger]
name = "lldb-dap"
transport = "stdio"
command = "lldb-dap"

[[language.debugger.templates]]
name = "binary"
request = "launch"
completion = [ { name = "binary", completion = "filename" } ]
args = { program = "{0}", initCommands = [ "command script import /usr/local/etc/lldb_vscode_rustc_primer.py" ] }

&str is correctly resolved as a string instead of showing the memory address

image

David-Else commented 2 weeks ago

@cino189 It should be fixed in the latest release version: https://github.com/helix-editor/helix/pull/10091

cino189 commented 2 weeks ago

@David-Else with the latest version it is indeed picking up the lldb-dap command by default. However by default without the command script import lldb-dap does not resolve variables values but returns the memory address. With the command script import local variables values are resolved for simple types.

I simply updated the configuration provided by @HealsCodes to work with the lldb-dap command instead that with lldb-vscode.

David-Else commented 2 weeks ago

@cino189 Cool. I was just thinking you didn't need to copy out anything from https://github.com/helix-editor/helix/blob/6cca98264fe308bd6a4f7f85be2d821b58f60b4a/languages.toml#L260C1-L281C23 as it is all inherited. Just overwrite the sections in need of an update. I know it seems a bit OCD, but then if the config changes in the main repo you won't get caught out.