Page MenuHomePhabricator

[lldb] Support debugging utility functions

Authored by JDevlieghere on Feb 22 2021, 8:32 PM.



LLDB uses utility functions to run code in the inferior for its own internal purposes, such as reading classes from the Objective-C runtime for example. Because these expressions should be transparent to the user, we ignore breakpoints and unwind the stack on errors, which makes them hard to debug.

This patch adds a new setting target.debug-utility-expression that, when enabled, changes these options to facilitate debugging. It enables breakpoints, disables unwinding and writes out the utility function source code to disk.

Here's what this looks like in action. I added a nullptr dereference to __lldb_apple_objc_v2_get_shared_cache_class_info and used TestDataFormatterObjCNSContainer as an example:

$ lldb a.out 
(lldb) target create "a.out"
Current executable set to 'a.out' (x86_64).
(lldb) b main.m:782
Breakpoint 1: where = a.out`main + 11752 at main.m:783:4, address = 0x0000000100006838
(lldb) setting set target.debug-utility-expression true
(lldb) r
Process 77039 stopped
* thread #1, queue = '', stop reason = breakpoint 1.1
    frame #0: 0x0000000100006838 a.out`main(argc=1, argv=0x00007ffeefbff500) at main.m:783:4
   780               forKeyPath:@"atoms"
   781                  options:0
   782                  context:NULL]; // Set break point at this line.
-> 783    [newMutableDictionary addObserver:[My_KVO_Observer new]
   784                           forKeyPath:@"weirdKeyToKVO"
   785                              options:NSKeyValueObservingOptionNew
   786                              context:NULL];

Process 77039 launched: 'a.out' (x86_64)
(lldb) frame variable newArray nsDictionary newDictionary nscfDictionary cfDictionaryRef newMutableDictionary newMutableDictionaryRef cfarray_ref mutable_array_ref
warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.
Process 77039 stopped
* thread #1, queue = '', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x00000001001df2de __lldb_apple_objc_v2_get_shared_cache_class_info`__lldb_apple_objc_v2_get_shared_cache_class_info(objc_opt_ro_ptr=0x00007fff2031bc08, class_infos_ptr=0x000000010063b000, class_infos_byte_size=1572864, should_log=0) at lldb-9f30fe.expr:68
   65                                                     uint32_t class_infos_byte_size,
   66                                                     uint32_t should_log)
   67   {
-> 68       int *i = 0;
   69       *i = 10;
   70       uint32_t idx = 0;
   71       DEBUG_PRINTF ("objc_opt_ro_ptr = %p\n", objc_opt_ro_ptr);

Diff Detail

Event Timeline

JDevlieghere created this revision.Feb 22 2021, 8:32 PM
JDevlieghere requested review of this revision.Feb 22 2021, 8:32 PM
jingham accepted this revision.Feb 23 2021, 9:28 AM

This will be really handy! The code for UserExpressions ends up doing this same thing (over in ClangExpressionParser::ParseInternal. But given how light-weight creating the file is I'm pretty sure it isn't worth trying to activate that code starting from the UtilityExpression. LGTM.

This revision is now accepted and ready to land.Feb 23 2021, 9:28 AM
shafik added a subscriber: shafik.Feb 23 2021, 9:48 AM
shafik added inline comments.

It feels a little weird you are using the name debug but using it in the call to SetDebug(...) but are using it in the call to SetGenerateDebugInfo(...)


Why additional #line 1.

JDevlieghere marked 2 inline comments as done.Feb 23 2021, 9:57 AM
JDevlieghere added inline comments.

Do you have a suggestion? Change the variable to debug_utility_function?


Good catch, this is a remnant of me playing around with the line directive, it shouldn't have made it into the patch.

teemperor added inline comments.Feb 23 2021, 9:58 AM

I assume this is to get the proper file name in the final source code. This would in theory also be required for top-level expressions, but as they are not curiously not using ClangExpressionSourceCode they never get this prefix (and are missing all our fancy type definitions here).

Anyway, this change will prompt the ClangModulesDeclVendor (which usually loads Obj-C modules) to also start loading the imported C++ modules (which I believe won't break anything but just cause a bunch of warnings about failing to load modules). See the only other use of g_prefix_file_name where we filter out imported modules from the wrapper file.

I think the idea is that the prefix starts the wrapper and then ClangExpressionSourceCode adds some generated prefixes and then we do a #line to terminate the wrapper. Not sure that's the right place for this use case, but just putting it into the ClangUtilityFunction where we concat the source code should work (even though then this logic is stuck in the initalizer, but oh well...)


Can you document debug (and maybe we could call it allow_debugging or generate_debuginfo which is how we call that in normal expressions)?

1595 ↗(On Diff #325658)



Not sure if that's needed. ScratchTypeSystem is bound to a specific target, so this seems like a good central place to read/use the value of the setting. I wouldn't mind the current way, but this is extending the generic TypeSystem interface and that's just always a bit of a pain to change in the future (when we hopefully have more TypeSystems for different langauges).

JDevlieghere marked 6 inline comments as done.

Address code review feedback

teemperor accepted this revision.Feb 24 2021, 10:39 AM

Some small nitpicks about comments, otherwise LGTM

I was kinda thinking how we could test this, but all our utility functions are anyway only on macOS and very Obj-C runtime related, so that sounds like a pain to do right...

(PS: Also I would appreciate if you could shoehorn tablegen into this patch somehow. The getters/setters for the properties seems like a good candidate...)


unrelated change


// Write the source code to a file so that LLDB's source manager can display it when debugging the code


I think this should match the parameter name in the source (and have a doxygen comment).

I am pretty sure other Unix-es share the code to dlopen images that backs "Process::LoadImage". It is trivial compared to the ObjC ones, but is does have a couple of lines of code, 'cause it tries the dlopen and then if that fails, calls dlerror to fetch the error. That is done with a UtilityFunction because it turns out that's performance sensitive for swift.

This revision was landed with ongoing or failed builds.Feb 24 2021, 11:36 AM
This revision was automatically updated to reflect the committed changes.
JDevlieghere marked an inline comment as done.
Herald added a project: Restricted Project. · View Herald TranscriptFeb 24 2021, 11:36 AM