[RFC] Add a Python check command to lldb-dap and lldb (original) (raw)

November 26, 2025, 1:22pm 1

Description

This RFC proposes adding a check-python subcommand to lldb and lldb-dap. This would allow tools like the VSCode lldb-dap extension to check that Python is correctly configured on the system before attempting to run lldb-dap.

Motivation

The Python dependency of lldb has been a long standing pain point for users. It seems to mostly impact Windows where Python is not installed by default. @Nerixyz has put together a list of related issues.

As of [lldb][windows] add support for out of PATH python.dll resolution by charles-zablit · Pull Request #162509 · llvm/llvm-project · GitHub, on Windows, lldb now prints a proper error message if lldb cannot load Python. However, lldb-dap does not have this ability yet. Therefore, is a user uses lldb-dap through a the VSCode extension, it will fail to start without any meaningful error message.

A dedicated check-python subcommand would allow clients to explicitly run an early validation check and provide a meaningful error message to users if needed.

Implementation details

I propose that on Windows, we create a shared library which exposes the AddPythonDLLToSearchPath method introduced in [lldb][windows] add support for out of PATH python.dll resolution by charles-zablit · Pull Request #162509 · llvm/llvm-project · GitHub. This method could therefore be reused by lldb-dap and lldb’s driver and even exposed through the CLI as a subcommand, allowing third party tools to warn their users early if Python is not correctly installed.

Sounds like a good idea, thought we had something like this already.

Existing lldb Python and scripting options:

SCRIPTING:
  -l <value>    Alias for --script-language
  --print-script-interpreter-info
                Prints out a json dictionary with information about the scripting language interpreter.
  --python-path Prints out the path to the lldb.py file for this version of lldb.
  -P            Alias for --python-path
  --script-language <language>
                Tells the debugger to use the specified scripting language for user-defined scripts.

First one I expect could work even if Python itself was missing.

$ ./bin/lldb --python-path
/home/david.spickett/build-llvm-aarch64/local/lib/python3.10/dist-packages

This one I’m not so sure:

$ ./bin/lldb --print-script-interpreter-info
{"executable":"/usr/bin/python3.10","language":"python","lldb-pythonpath":"/home/david.spickett/build-llvm-aarch64/local/lib/python3.10/dist-packages","prefix":"/usr"}

This could be printing what it’s configured for, rather than what it can find.

Do you know what this prints on Windows and, if you can, what it prints when the DLL is missing?

Can you expand on why it needs to be a shared library? Is this so it can be shared to save disk space, or because it has to be done that way on Windows, or anything else?

Do you know what this prints on Windows and, if you can, what it prints when the DLL is missing?

Both --python-path and --print-script-interpreter-info fail on Windows if Python is missing.

The reason is that lldb/Driver.cpp loads liblldb.dll which itself loads python310.dll. Since the latter is missing, lldb fails to start/run the subcommands.

Since we are delay loading liblldb.dll on Windows, commands like lldb.exe -h work fine because they do not depend on anything inside liblldb.dll.

This could be printing what it’s configured for, rather than what it can find.

It could, however the code would have to live in Driver.cpp to avoid having to load liblldb.dll.

Can you expand on why it needs to be a shared library?

We want to share the Python detection logic between lldb and lldb-dap and it has to be outside of liblldb.dll because it loads python310.dll.

A shared library would allow code duplication between the 2 drivers.

--print-script-interpreter-info could be related to something that’s not Python, so we can’t use that for detection in all cases.

--python-path sounds like it’s doing the same thing you want but in a roundabout way with worse reporting. I also wonder if there are corner cases where that would fail but other things would not.

So probably best, and fairly cheap, to have a dedicated option as you’ve proposed.

Sorry, I meant “could be” as in - it (the option shown above) could in fact be printing what it was configured with.

In other words, I’m not sure if that option is “here is where I will look” or “here is where I looked and what I found”.

Anyway, it’s generically named so you couldn’t be sure it always referred to Python.

Tell me if I understood correctly.

I thought at first that the loading of the Python DLL by code in liblldb.dll was delayed.

Now I see what you mean. The code for this check cannot live in liblldb.dll, but both drivers have to use it.

Maybe we don’t need another shared library, the code could be included at build time in both, right? Not that it matters much, just an ergonomic choice.

Do we have a way to do this on other platforms? An option like --check-python should work everywhere and third parties would appreciate not having to if Windows it.

Maybe you mean, there’s already a way to do this on non-Windows, and the two drivers can just use it. Windows is the missing piece.

That’s correct.

the code could be included at build time in both

I’m curious how would that look like with our build system? Just a header file #included by driver.cpp and lldb-dap.cpp?

Do we have a way to do this on other platforms?

Currently, no, because delay loading only exists on Windows. On macOS and Linux we would have to use dlopen and dlsym. Overall, the issue is less likely to happen on non-Windows platform because (AFAIK) we build with the OS’ Python. That’s true on macOS (Python 3.9) and may vary on Linux.

An option like --check-python should work everywhere and third parties would appreciate not having to if Windows it.

Would it be reasonable to just return “not implemented" on non Windows platforms?

Otherwise, if we want to support other platforms, and because we can’t delayload on Windows, we would have to create a standalone executable that does the Python check or dlopen liblldb in Driver.cpp and lldb-dap.cpp, but that’s a large refactor.

Given the large amount of issues on Windows compared to other platforms, it seems like it’s not worth doing either of the previous suggestions, as they would only help a very small set of users. What do you think?

More like a static lib linked into both. But this seems like the same result just different steps, I was just curious why it specifically needed to be a shared library.

For instance, was it because loading a shared library adds some kind of separation that means if it crashes then lldb-dap can still report a result. That sort of thing. (which is a made up example, I don’t think that’s the case)

For lldb-server we statically link a lot of things into it, but in that case we wan’t it to be easy to copy around so that’s why we do it. One more file in an install of llvm/lldb isn’t as big of a deal in the case of this new option.

I think there is a conflict between the implied intent of this new option and it’s proposed implementation. Implied as in the combination of what you’ve stated, and what I think users will think of it.

On the one hand, great idea. Simple way to check dependencies exist.

On the other, on non-Windows, it does nothing or does not work at all.

Now as you’ve said, technically it may not be possible to do an equivalent on non-Windows platforms, but never the less, this is the perception problem I’m having here.

In order of most to least preferable:

If there’s no appetite to implement something for non-Windows, I’d prefer the second option.

I was going to give the example of someone installing LLDB via. a ports system like on FreeBSD, but iirc that has a dependency manager that’d pull in the right Python for you. So I agree that we’d expect, and have seen, many fewer issues on non-Windows.

(hopefully even fewer with Jonas’ work with the stable API)

So while I would like this new option to do something useful everywhere, I don’t think it’s worth speculating too much on it until we hear other’s opinions.

I would rather help users on Windows than wait for a perfect way to do this, but I’ve included the pedantry argument to illustrate some trade offs.

Just to make sure I understand this second option:

On Windows we try to LoadLibrary("python.dll") and print whether or not we were successful. This should always return something even if python.dll is missing thanks to delay loading.

On POSIX we try to dlopen("python.so") and print an success message if we can. If we can’t, we can’t print anything anyways because liblldb would have failed to load python.so so the program would have crashed before even running the command.

On either platform, if a client (dap-vscode) tries to run this command and they either get a crash or an error message, they can almost confidently say something is wrong with Python.

Sorry, I should be have given an example. My idea there was that the option exists but does not check anything on non-Windows. It’s a nop.

$ lldb-dap --check-python
Note: Python library checking is only implemented for Windows, this option will always succeed on any other platform.
$ $?
0

This saves us the work of implementing the dlopen type idea, and saves users having to if windows use option otherwise don't. It’s not ideal but it’s a way forward without extensive work for non-Windows.

What you’re suggesting is that the option does do work everywhere, but it’s result is conveyed differently. That I had not thought of, and I your idea more.

They can say “any non zero return code means Python is not set up correctly”. They could do this right now by running lldb -b but that’s way more mysterious than lldb --check-python and if their product is then used on Windows, it works even better.

They could check for Python by running some random command, but --check-python would be guaranteed to check for Python.

Which is just another way of saying…

So this route sounds good to me. Implement the Windows side, add some strong documentation that this is the option to be used for this purpose, on any platform.

Strongly agree :slight_smile:

I don’t think that’s true. We have the same problem on every platform, but more effort has gone into changing how we distribute Python to avoid the issue elsewhere.

Anyway, my point is that this isn’t really tied to Windows: it’s more that the way folks install (or are used to installing) something like LLDB on Windows makes the problem more visible on that platform.

The delay-loading mechanism on Windows offers a unique opportunity to diagnose this issue more effectively than on other platforms. As you point out, there’s nothing equivalent on POSIX systems aside from not linking directly with libLLDB and using dlopen to load either LLDB or Python, neither of which is really viable (as discussed in [1]). That said, I don’t think we should pass on this opportunity just because there’s no equivalent elsewhere.

Given that there’s no realistic way to achieve this on other platforms, I’m leaning toward keeping this Windows-only. I don’t want to add a flag that people get excited about and then either complain that it should be available everywhere or need to read two RFCs to understand why that’s not an option.

I also agree that we should put this logic in a separate (statically linked) library so it can be shared. Since by design it won’t depend on anything in libLLDB, we can safely link it into the executables that do link libLLDB.

Finally, I want to call out that this addresses the symptoms rather than the root cause. After spending more than half a decade fighting this issue, I’m convinced that the only real solution is to change how we distribute LLDB and Python, which is exactly what I’m trying to do with [1].

[1] https://discourse.llvm.org/t/a-minimal-python-install-for-lldb/8865

Not sure if you would prefer the option to not exist at all on non-Windows, always fail, or always succeed or something else.

And I wrote a whole thing about how always failing would be the preferred route for me because at least you could look at the option help from a non-Windows machine that way.

Then I realised that lldb --check-python on non-Windows is going to crash before parsing the option anyway if Python is not present, right?

Separate thing: What does --check-python do when Python wasn’t enabled in the build? Succeed because of 0 Pythons, all 0 were present, or fail, because the question doesn’t make sense? I think the latter. You wouldn’t check Python was ok unless you were going to use it later.

Yes, that’s correct, lldb would not even be able to start because the loader would try to load python.dll (which it won’t find) even before running anything in main.

Would changing the name of the flag to --print-found-python-library be OK? If Python is found we return the path to the library we find. If Python is not found, on Windows, we emit a message, on Linux/macOS we just crash, which is an indication (although not perfect) that Python is not installed.

Separate thing: What does --check-python do when Python wasn’t enabled in the build?

If we go with --print-found-python-library, I agree, I think that “the question does not make sense” is more sensible. We could print an error saying that “LLDB was not built with Python enabled”.

Apologies for not stating my preferred solution more clearly: I would prefer for this option not to exist on the platforms where it cannot work meaningfully. As you and Charles correctly point out, the failure mode on other platforms it that we crash, which just makes us look bad.

Although I think it’s better than check-python, it doesn’t address my primary concern which is that crashing is just a bad error mode, but unfortunately we can’t do better (*).


(*) That statement isn’t entirely true. We could make the driver and lldb-dap re-exec itself and do something similar to what we do on Windows. It’s a bit clunky and would make debugging the driver more painful (especially on platforms that don’t support follow-on-fork). That keeps surprising folks trying to debug clang, but maybe that’s a trade-off we’re willing to take. I’m not actually advocating we do that. I’m only mentioning it for completeness.

I can see some other uses for this. “I have installed library X but lldb cannot import it”, “oh, it was loading Python X.Y not Python Y.X, that’s why”.

Awkward trade offs all around here, but only showing the option on Windows certainly shows LLDB’s intent most clearly. It’s the most conservative so we are not over committing. I am happy with it on those grounds.

Consumers of LLDB will end up with:

if Windows:
  try lldb --check-python
else
  try lldb -b # if it explodes, something is wrong :)

They would have had the else anyway, so we look no worse, and Windows improves a bit.

…though in fact you could do:

try lldb --check-python # Either this explodes, or it prints a path

Presuming that we would crash before we parsed the invalid option name.

Edit: do we endorse doing that? No. Could you? Sure :slight_smile:

The drawback to hiding it on non-Windows is that you can’t read the text that would tell you it’s Windows only unless you’re on Windows. It’ll be used by lldb-dap, so that’s one example people can find, but not everyone is going to do that.

Could add a section to Troubleshooting - 🐛 LLDB. Again they’ve got to find it, but you can read that from anywhere, it’s more likely to turn up on Google and we can link to it if they ask.

I think after that considering all the arguments and potential drawbacks, having lldb --check-python only on Windows is the best approach. As @JDevlieghere said, crashing on other platforms does not look good. Furthermore, Windows is where the vast majority of the issues are with the Python dependency.

If that’s OK with everyone, I will go ahead and open a PR to suggest an implementation.