This is an archive of the discontinued LLVM Phabricator instance.

[lldb-vscode] Fix lldb init file stdout issue
AbandonedPublic

Authored by yinghuitan on Feb 12 2021, 11:30 AM.

Details

Summary

VScode DAP client uses stdout to receive protocol message. This means any stdout/stderr from debugger would be treated as DAP packet. This means any lldb commands in init file can generate random output and break DAP client.
This diff fixes the issue by explicitly sourcing lldbinit file after stdout/stderr is redirected to dev_null instead of relying on SBDebugger::Create().
There is another issue that SBDebugger::Create() only sources the lldbinit file from home directory not debugger's cwd. This diverages from what lldb does. This diff explicitly sources init file from cwd as well.
An integration test is added.

Diff Detail

Event Timeline

yinghuitan requested review of this revision.Feb 12 2021, 11:30 AM
yinghuitan created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptFeb 12 2021, 11:30 AM

After thinking about this a bit more, it seems a mistake that we can't change the file handles _prior_ to sourcing the init files. I have proposed one solution in the inline comments to create a new SBDebugger::Create() method that takes the input output and error file handles as arguments. The other way to do this is to make it so that we _can_ run the init files later. Currently if we say "false" to "bool source_init_files", then the command interpreter marks the init files as not being able to be run again later if you call the SBCommandInterpreter::SourceInitFileInHomeDirectory(...). So we need to either be able to either specify the file handles at debugger creation time, or run them one time later. The current "run them once" means "you can only do this at debugger creation time, but never again if you specify false for source_init_files. We can change this to be "only run the init files once. so if we already run them in SBDebugger::Create(), then don't run them again if you call SBCommandInterpreter::SourceInitFileInHomeDirectory()

lldb/tools/lldb-vscode/lldb-vscode.cpp
1444

So after thinking about this some more, it might be better to add a new lldb::SBDebugger::Create method that takes the input, output and error handles as SBFile objects. This will allow us to not have to duplicate the entire .lldbinit loading stuff in this patch. My proposal would be to add a new create method:

lldb::SBDebugger lldb::SBDebugger::Create(bool source_init_files, SBFile input, SBFile output, SBFile error);

SBFile has multiple constructors that will work for us:

SBFile()
SBFile(FILE *file, bool transfer_ownership);
SBFile(int fd, const char *mode, bool transfer_ownership);

If we default construct a SBFile, it will contain no valid shared pointer in the private "FileSP m_opaque_sp;" member. Any of the SBFile objects that are not valid, won't end up being set in the debugger. All valid file handles will be set _prior_ to sourcing the init files, and then we can avoid any problems.

in the SBDebugger.cpp you would do something like:

lldb::SBDebugger lldb::SBDebugger::Create(bool source_init_files, SBFile input, SBFile output, SBFile error) {

  static std::recursive_mutex g_mutex;
  std::lock_guard<std::recursive_mutex> guard(g_mutex);
  debugger.reset(Debugger::CreateInstance(callback, baton));
  if (input.IsValid())
    debugger.SetInputFile(input);
  if (output.IsValid())
    debugger.SetOutputFile(output):
  if (error.IsValid())
    debugger.SetErrorFile(error);
  SBCommandInterpreter interp = debugger.GetCommandInterpreter();
  if (source_init_files) {
    interp.get()->SkipLLDBInitFiles(false);
    interp.get()->SkipAppInitFiles(false);
    SBCommandReturnObject result;
    interp.SourceInitFileInHomeDirectory(result, false);
  } else {
    interp.get()->SkipLLDBInitFiles(true);
    interp.get()->SkipAppInitFiles(true);
  }
  return debugger;
}

I added Jim and Pavel to get some more feedback.

The main issue is the Visual Studio Code debug adaptors like lldb-vscode receive and send packets on STDIN and STDOUT. The debugger is created during the "initialize" request which expects a response. If the debugger tries to run the init files when the debugger is created, it risks outputting some info to STDOUT and causing the entire debug session to fail the stdout is the default output file handle for debuggers. If you don't run the init files, they are marked as skipped in the command interpreter and calling SBCommandInterpreter::SourceInitFileInHomeDirectory(...) will do nothing if you call it later.

A further complication of this patch is that we intend to support different LLDB.framework binaries in order to support Swift debugging in the future by setting a DYLD_FRAMEWORK_PATH in the environment of lldb-vscode when it is launched so we can use the LLDB.framework (or liblldb.so for linux) that matches the swift we need. This means that if we change the API by adding the new lldb::SBDebugger::Create() method that takes the SBFile handles for in/out/error, then we can't use these older LLDB versions since it will crash when it doesn't find this new create method. The current version of this patch does actually allow us to use these older LLDB.framework binaries, though it does duplicate the running of the init files manually. So maybe this is the best option for now, but I wanted to include a few other people to get their input.

yinghuitan abandoned this revision.Mar 14 2022, 9:44 AM
Herald added a project: Restricted Project. · View Herald TranscriptMar 14 2022, 9:44 AM