Skip to content

Comments

Write SOS command output to SBCommandReturnObject (#4086)#5728

Merged
leculver merged 2 commits intodotnet:mainfrom
leculver:issue_4086
Feb 24, 2026
Merged

Write SOS command output to SBCommandReturnObject (#4086)#5728
leculver merged 2 commits intodotnet:mainfrom
leculver:issue_4086

Conversation

@leculver
Copy link
Contributor

@leculver leculver commented Feb 18, 2026

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.

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.
Copilot AI review requested due to automatic review settings February 18, 2026 03:27
@leculver leculver requested a review from a team as a code owner February 18, 2026 03:27
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_currentResult pointer to LLDBServices to track the active result object during command execution
  • Modify OutputString() to write to SBCommandReturnObject when 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.

@leculver
Copy link
Contributor Author

hmm strange. Converting this to a draft until I investigate test failures much later today.

@leculver leculver marked this pull request as draft February 18, 2026 10:21
…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>
@leculver leculver marked this pull request as ready for review February 19, 2026 11:22
@leculver
Copy link
Contributor Author

This is ready now. All tests pass and it works well in my local ad-hoc testing.

@leculver
Copy link
Contributor Author

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")
lldb -c <your_dump> -o 'command script import test_sos_output.py'

It invokes clrstack programmatically via HandleCommand() — the same path Python scripts and lldb-dap use — and tries to read the output from the result object.

Before this fix: GetOutput() returns an empty string. The output leaks to the console via fputs() but the caller can never capture it.
After this fix: GetOutput() returns the actual command output, matching how native LLDB commands like bt behave.

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.

Copy link
Member

@steveisok steveisok left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change makes sense. My general reaction is do we need to be more friendly like this elsewhere?

@leculver
Copy link
Contributor Author

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. dotnet-dump analyze is a little unfriendly to external tools hosting it in non-interactive mode. It's good that it can do multiple commands in a row on the command line, but it prints out extra output before the first command runs, it doesn't provide clear markers when a command runs like ... or similar. It does something like that but it doesn't do it quite right. (I haven't dug into this deeply, take the previous with a grain of salt...but it is something I ran into hosting dotnet-dump analyze.)

We don't want folks parsing SOS output, SOS is not an interface, neither is dotnet-dump analyze, but if it had crisp rules around its output, tools (especially AI tools) could better use it with fewer tokens. I'll take a look next week and put together a bug report/fix.

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.

@leculver leculver merged commit 5b594fb into dotnet:main Feb 24, 2026
19 checks passed
@leculver leculver deleted the issue_4086 branch February 24, 2026 02:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LLDB sos commands don't write output to SBCommandReturnObject

2 participants