Write SOS command output to SBCommandReturnObject (#4086)#5728
Write SOS command output to SBCommandReturnObject (#4086)#5728leculver merged 2 commits intodotnet:mainfrom
Conversation
SOS commands invoked via SBCommandInterpreter.HandleCommand() were producing console output but returning 0 bytes in SBCommandReturnObject. This broke LLDB Python scripts that capture SOS output programmatically. Root cause: LLDBServices::OutputString() only wrote to file handles via fputs(), completely bypassing the LLDB result object. Fix: Add m_currentResult pointer to LLDBServices. sosCommand::DoExecute sets it before calling the SOS command function and clears it after. OutputString() now writes to the SBCommandReturnObject via Printf() when a result is set, or falls back to fputs() for direct console use. This avoids double output — LLDB auto-prints the result object content after DoExecute returns, so fputs must be skipped when capturing.
There was a problem hiding this comment.
Pull request overview
This PR fixes SOS command output to properly write to SBCommandReturnObject when invoked programmatically via SBCommandInterpreter.HandleCommand(). Previously, SOS commands only wrote to file handles via fputs(), which meant Python scripts and other programmatic callers could not capture the command output.
Changes:
- Add
m_currentResultpointer toLLDBServicesto track the active result object during command execution - Modify
OutputString()to write toSBCommandReturnObjectwhen available, falling back to file handles for direct console use - Wrap SOS command execution with
SetCurrentResult()/ClearCurrentResult()calls to manage the result object lifecycle
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/SOS/lldbplugin/services.h | Adds m_currentResult member and public setter/clearer methods to track the active SBCommandReturnObject |
| src/SOS/lldbplugin/services.cpp | Initializes m_currentResult to nullptr and modifies OutputString() to route output to the result object when set |
| src/SOS/lldbplugin/soscommand.cpp | Sets/clears the result object before/after calling SOS command functions to enable output capture |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
hmm strange. Converting this to a draft until I investigate test failures much later today. |
…urnObject After routing SOS output to SBCommandReturnObject (issue dotnet#4086 fix), GetOutput() returns text that already ends with a newline. Using print() to write this added an extra trailing newline, causing ClrStackWithNumberOfFrames tests to fail because the line count verification regex saw more lines than expected. Switch from print() to sys.stdout.write() for command output/error to avoid the double newline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
This is ready now. All tests pass and it works well in my local ad-hoc testing. |
|
I forgot to include the test script (name it: test_sos_output.py): import lldb
def test_sos_output(debugger, command, result, internal_dict):
interp = debugger.GetCommandInterpreter()
# Run clrstack programmatically — the way Python scripts and
# lldb-dap invoke SOS commands.
res = lldb.SBCommandReturnObject()
interp.HandleCommand("clrstack", res)
output = res.GetOutput()
if output:
print("clrstack output captured successfully:")
print(output)
else:
print("BUG: clrstack produced no output in SBCommandReturnObject.")
print(" The text printed above came from fputs() to stdout,")
print(" but the caller has no way to read it.")
def __lldb_init_module(debugger, internal_dict):
debugger.HandleCommand("command script add -f test_sos_output.test_sos_output test_sos_output")
debugger.HandleCommand("test_sos_output")It invokes Before this fix: This is the easiest way to show the fix is working. In general, we don't want folks screen-scraping SOS (just use clrmd), but for quick and dirty testing or tasks it is super helpful. This also affects lldb-dap when hosting lldb within another debugger, which is the place I ran into it. |
steveisok
left a comment
There was a problem hiding this comment.
I think this change makes sense. My general reaction is do we need to be more friendly like this elsewhere?
In one place I can think of. We don't want folks parsing SOS output, SOS is not an interface, neither is There might be similar things in other dotnet diagnostic tools. We want command line/output parsing to be crisp and clean for AI tools, but I don't use them enough to really have a strong sense of the rest. Might be worth a low-priority backlog item to investigate later. |
This one is a bit outside of my wheelhouse. I had to lean into copilot for building and testing to fix this. This gist explains the overall change, reasoning, and testing: https://gist.github.com/leculver/3b97b312ff5217441e35cf7c57d21a17.
The underlying issue does legitimately break tools trying to use SOS within them. The best example for me personally is lldb-dap, hosting lldb as a subprocess does strange things with SOS where the command output comes as events instead of text output like 99.99% of everything else.
This change makes LLDB produce the right output in the right circumstances.
I generally write the fixes myself, even with llm help, but the following is (unfortunately) copilot generated, and the fix too. But it does work quite well in the tests I built and fixes the issue I have in lldb-dap in my personal project.
Fixes #4086.
--
SOS commands invoked via SBCommandInterpreter.HandleCommand() were producing console output but returning 0 bytes in SBCommandReturnObject. This broke LLDB Python scripts that capture SOS output programmatically.
Root cause: LLDBServices::OutputString() only wrote to file handles via fputs(), completely bypassing the LLDB result object.
Fix: Add m_currentResult pointer to LLDBServices. sosCommand::DoExecute sets it before calling the SOS command function and clears it after. OutputString() now writes to the SBCommandReturnObject via Printf() when a result is set, or falls back to fputs() for direct console use. This avoids double output — LLDB auto-prints the result object content after DoExecute returns, so fputs must be skipped when capturing.