[RFC] System-wide lldbinit

Hello everyone,

We have a need (and it is my understanding that we’re not the only one) to tweak the default configuration of lldb in order to ensure it interacts well with our internal tools. We would like to do so in a way that is supported upstream. I am not aware of any mechanism that would allow us to do that (if there is one, please let me know). For that reason, I would like to propose a new feature: a system-wide lldbinit file.

Unlike $HOME/.lldbinit, which is specific to one user, this file would live in a central location, and would be shared by all users. It would be automatically sourced by lldb (unless –no-lldbinit is given) upon startup. This would happen before $HOME/.lldbinit (and $PWD/.lldbinit) processing, to give the user a chance to override any unwanted settings. I propose to have the location of the file be configurable at build time, via a cmake option, say LLDB_SYSTEMWIDE_INIT_LOCATION.

If the variable is set to an absolute path (e.g. /etc/lldb/lldbinit), then the file would be searched in that location. This setup could be suitable for e.g. linux distributions (our distribution model is similar) to package an extra lldbinit file to configure lldb (for example, to set split debug file search paths) to match their setup. Relative paths would be interpreted relative to the location of (lib)lldb. This model may be suitable for “independent” distribution channels, where one gives the user a zip file containing a build of lldb (perhaps together with some SDK), and an lldbinit file which configures it to make use of the files inside that SDK. Setting the variable to an empty string would disable the feature altogether.

In case anyone is wondering, gdb also has a similar capability, but it is more featureful – in addition to a file, one can also specify directory, and all files in that directory will be automatically executed, depending on the extension (python files, gdb command scripts, …). I’m not proposing anything like that here, as it’s not needed for my use case, and the functionality can be emulated with a bit of python scripting.

Due to the system-wide scope of the feature, writing automated tests for it is going to be very tricky. If the file location is relative, then we could theoretically write a test that copies the entire lldb installation to a temp folder, adds an init file, and then checks that it got executed, but such a test would be pretty ugly. Therefore I propose to skip an end-to-end test for the feature. If there are some bits that could be tested at a lower level, then we could handle that with a unit test, but I am hoping that it will be possible to reuse the existing file sourcing machinery to make this work, and that the entire feature will almost be a “one liner”.

Let me know what you think,
Pavel

To clarify, do you mean that when using lldb it’s relative to lldb and when using liblldb, it’s relative to liblldb? I assume so, otherwise it’s not possible to decide.

Intalling lldb-10 on Ubuntu I have /usr/lib/llvm-10/bin/lldb and /usr/lib/llvm-10/lib/liblldb.so.1.

What are the rules for existing lldb init files? Is it relative to the location of lldb or the place you invoke lldb? (I think the latter, but maybe I’m thinking of python imports not general paths) For a system wide file I can see how it would be confusing to pay attention to the cwd though.

Would the implementation of this essentially be adding an extra search path to the existing list? Maybe with different relative path rules. The more the rules differ the more I’d want a test for it.

Do we have any logging that tells you where it’s looking for these files? You could at least test that, look for “no global init file set” or similar. (also if not, a good feature to add!)

Though here you could do ../ and be in the same place, but you get the idea. Doing that generally might be tricky.

Actually, no :slight_smile: It would always be relative to the location of liblldb, similar to how other paths (lldb-server location, location of python files, …) are defined in lldb. The reason this can work is because the code for all of this would live it liblldb, and it can find it’s own location through dlinfo(3) et al.

In fact, the ability to specify an absolute path is a fairly novel thing here – I don’t really needed but I didn’t really see a reason to exclude it. And it might be easier to specify the location as /etc/lldb, instead of as …/…/etc/lldb or …/…/…/…/etc/lldb, depending whether you want to install liblldb to /usr/lib, or /usr/lib/llvm-XX/lib

There is a $HOME/.lldbinit, whose location is fixed (well, kinda - you can always set HOME to someplace else). And there’s a $PWD/.lldbinit, whose location depends on the current directory (this one is disabled by default).

I don’t think you could call something a system-wide initialization file, if it depends on the location where you start the debugger.

It’s not exactly a search path, at least not in the sense that the first entry wins. The entries are independent and cumulative. We’d first look for a system-wide file, and run it if it’s there. Then we’d look at the homedir lldb init, and do the same, followed by the cwd lldbinit.

The logging might be nice, but I don’t see how it would help. The problem is one of not having control over the very existence of that file. A user might have an /etc/lldbinit on his system, or he may not. And since you cannot affect that from a test, there isn’t much you can assert.

Well it doesn’t add much to testing but checking that you get “no global init file path set” is at least more than 0 coverage.

I thought it would be useful more for troubleshooting. Say I’ve got my init file how I like it then I move systems or whatever, now I’ve got some weird non default behaviour. I’d want to know which init files it was processing. (or if the init file exists but there is a permissions issue)

That said I haven’t actually had that problem myself yet so that logging could totally be in lldb already.

This being a build time option definitely makes testing awkward. Even if the bot built with the path set to ../<path to where the test will take place> you would run a small risk that someone cancels the test suite, or the test itself fails before it can clean itself up. Leaving the init file in place.

Unless you picked a very minor setting that in theory wouldn’t change anything but then why would such a setting even exist if it wasn’t going to change some behaviour. Even a print will trip up a test looking for stdout. (and fill up the logs for no reason)

Clang’s toolchain tests are similar but there you can control what clang thinks its install dir is.

I think we have sufficient tests that cover the sourcing of files, so at least the majority of the complexity is already covered. For the system-specific part, maybe we could support an environment variable with the same name as the CMake setting and use that to override the path for testing. The existing tests do something similar by changing $HOME.

I’m not sure how much value this would add, given that it would be a slight change to the code path, but it would allow us to test things like it getting sourced first etc.

This seems like a useful feature in some contexts, and a site configuration file is pretty standard for a variety of tools. So I have no problems with the idea. Putting it relative to liblldb for relative paths (or the LLDB in the framework package) also seems like a good solution for distributions that want to be self-contained.

However, the security folks at Apple are always skeptical of anything that could squirrel behaviors into the debugger behind the user’s back. For instance, they were somewhat rightfully horrified that just putting a .dSYM folder with an appropriate UUID on the system could cause a debug session to run unaudited Python code all unbeknownst to the user. So it might be better if the CMake variable translated to a #define variable that eliminates the entire feature of looking for a site-specific lldbinit, rather than just specifying an empty path that’s checked in code. That way if you didn’t feel comfortable with doing this (or if somebody who could make you uncomfortable felt that way) you could unambiguously remove the feature from the lldb you ship.

Jim

+1. This should be fairly easy to implement and something we probably would like to use downstream.

Thanks for the support.

I haven’t yet tried implementing this, but I am sure we can figure out a way to make security folks comfortable.

(Though I can’t say that I see, security-wise, a difference between appending something to say LLDB.framework/lldbinit and changing LLDB.framework/.../lldb/__init__.py. If anything, it would be much easier to hide/obfuscate any malicious code in the second file. In fact, modifying that file was my backup plan.)

A system LLDB.framework is code signed, and changing a file in it should invalidate the code-signing such that clients refuse to load it. So both of your suggested methods of squirreling bad code into the LLDB.framework itself should be rejected by the code-signing check.

Jim

Do we want a system wide lldbinit, or do we really want a lldbinit that is unique to the current LLDB binary? Many times we end up adding new commands and if you have an LLDB init file that contains errors, it will stop running the commands in the .lldbinit file. If we made a LLDB build specific init file, then each LLDB distribution could have its own lldbinit file that could use specific new commands that might not work for other versions of LLDB.

We currently have a Facebook specific build of LLDB we distribute, and we modified the python code to auto import some Facebook modules when the program is loaded. This is another way to get this kind of LLDB specific configuration for your distribution. Let me know if you want the details on that.

Well… it can be both, depending on how you configure it. The thing I’m interested is mainly a lldbinit file specific to a single lldb installation (binary), one that can easily be installed together with that binary. I can sort of imagine a setup where we have two lldb installations (say different versions of lldb) configured to read from the same lldbinit file, but I would imagine that in that case, the two installations would be provided by the same entity, and it would be the responsibility of that entity to create an lldbinit file that works with both versions.

Yes, I am aware of that, although I don’t know the details of how it works. I am basically looking for a way to do that in an upstream-sanctioned way, one which does not require local patches. A “global” lldbinit file would fit that bill, and it seemed like it would fit in nicely into the existing lldb infrastructure. I was hoping that you would be able to use this as well. (BTW, our setup will most likely be done in python as well, and so the lldbinit file will probably consist of a single command script import --relative-to-command-file line).

If you do have a way to do that without local patches, and without a new lldbinit file, then I could drop this and do what you do instead.