Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/SSHDebugPS/AD7/AD7Process.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class AD7Process : IDebugProcess2, IDebugProcessSecurity2, IDebugProces
/// <summary>
/// Flags are only used in ps command scenarios. It will be set to 0 for others.
/// </summary>
private readonly uint _flags;
private readonly uint? _flags;

/// <summary>
/// Returns true if _commandLine appears to hold a real file name + args rather than just a description
Expand Down Expand Up @@ -297,7 +297,7 @@ string IDebugUnixProcess.GetProcessArchitecture()
{
// For Apple Silicon M1, it is possible that the process we are attaching to is being emulated as x86_64.
// The process is emulated if it has process flags has P_TRANSLATED (0x20000).
if (_port.IsOSX() && _systemArch == "arm64")
if (_port.IsOSX() && _systemArch == "arm64" && _flags.HasValue)
{
if ((_flags & 0x20000) != 0)
{
Expand Down
30 changes: 27 additions & 3 deletions src/SSHDebugPS/IConnection.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using Microsoft.DebugEngineHost;
using Microsoft.SSHDebugPS.Utilities;
Expand Down Expand Up @@ -104,13 +106,13 @@ public class Process
/// <summary>
/// Only used by the PSOutputParser
/// </summary>
public uint Flags { get; private set; }
public uint? Flags { get; private set; }
public string SystemArch { get; private set; }
public string CommandLine { get; private set; }
public string UserName { get; private set; }
public bool IsSameUser { get; private set; }

public Process(uint id, string arch, uint flags, string userName, string commandLine, bool isSameUser)
public Process(uint id, string arch, uint? flags, string userName, string commandLine, bool isSameUser)
{
this.Id = id;
this.Flags = flags;
Expand All @@ -121,15 +123,37 @@ public Process(uint id, string arch, uint flags, string userName, string command
}
}

internal static class OperatingSystemStringConverter
{
internal static PlatformID ConvertToPlatformID(string value)
{
if (!string.IsNullOrEmpty(value))
{
value = value.ToLowerInvariant();
if (value.Contains("darwin"))
{
return PlatformID.MacOSX;
} else if (value.Contains("linux"))
{
return PlatformID.Unix;
}
}
Debug.Fail($"Expected a valid platform '{value}' of darwin or linux, but falling back to linux.");
return PlatformID.Unix;
}
}

internal class SystemInformation
{
public string UserName { get; private set; }
public string Architecture { get; private set; }
public PlatformID Platform { get; private set; }

public SystemInformation(string username, string architecture)
public SystemInformation(string username, string architecture, PlatformID platform)
{
this.UserName = username;
this.Architecture = architecture;
Platform = platform;
}
}
}
48 changes: 28 additions & 20 deletions src/SSHDebugPS/PSOutputParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,27 @@ public string Extract(string line)
// Use padding to expand column width. 10 for pid and 32 for userid as that is the max size for each
// Tested this format with different distributions of Linux and container distributions. This command (and the alternative without the flags) seems
// to be the one that works the best between standard *nix and BusyBox implementations of ps.
private const string PSCommandLineFormat = "ps{0}-o pid=pppppppppp -o flags=ffffffff -o ruser=rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr -o args";
private const string PSCommandLineFormat = "ps{0}-o pid=pppppppppp{1} -o ruser=rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr -o args";
private SystemInformation _currentSystemInformation;
private ColumnDef _pidCol;
private ColumnDef _flagsCol;
private ColumnDef _ruserCol;
private ColumnDef _argsCol;

public static string PSCommandLine = PSCommandLineFormat.FormatInvariantWithArgs(" axww ");
public static string AltPSCommandLine = PSCommandLineFormat.FormatInvariantWithArgs(" ");
// In order to determine the architecture of a process, we need to run the ps command with 'flags'.
// However, certain of distros of Linux do not support flags, so only add this for macOS.
private string PSFlagFormat => _currentSystemInformation.Platform == PlatformID.MacOSX ? " -o flags=ffffffff" : string.Empty;

public static List<Process> Parse(string output, SystemInformation systemInformation)
{
return new PSOutputParser().ParseInternal(output, systemInformation);
}
public string PSCommandLine => PSCommandLineFormat.FormatInvariantWithArgs(" axww ", PSFlagFormat);
public string AltPSCommandLine => PSCommandLineFormat.FormatInvariantWithArgs(" ", PSFlagFormat);

private PSOutputParser()
public PSOutputParser(SystemInformation systemInformation)
{
_currentSystemInformation = systemInformation;
}

private List<Process> ParseInternal(string output, SystemInformation systemInformation)
public List<Process> Parse(string output)
{
_currentSystemInformation = systemInformation;
List<Process> processList = new List<Process>();

using (var reader = new StringReader(output))
Expand Down Expand Up @@ -136,14 +135,18 @@ private bool ProcessHeaderLine(/*OPTIONAL*/ string headerLine)
if (!SkipNonWhitespace(headerLine, ref index))
return false;

_flagsCol = new ColumnDef(colStart, index);
/// <see cref PSFlagFormat/> on why this is only executed for macOS.
if (_currentSystemInformation.Platform == PlatformID.MacOSX)
{
_flagsCol = new ColumnDef(colStart, index);

if (!SkipWhitespace(headerLine, ref index))
return false;
if (!SkipWhitespace(headerLine, ref index))
return false;

colStart = index;
if (!SkipNonWhitespace(headerLine, ref index))
return false;
colStart = index;
if (!SkipNonWhitespace(headerLine, ref index))
return false;
}

_ruserCol = new ColumnDef(colStart, index);

Expand All @@ -170,10 +173,15 @@ private Process SplitPSLine(string line)
if (!uint.TryParse(pidText, NumberStyles.None, CultureInfo.InvariantCulture, out pid))
return null;

uint flags;
string flagsText = _flagsCol.Extract(line);
if (!uint.TryParse(flagsText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out flags))
return null;
uint? flags = null;
/// <see cref PSFlagFormat/> on why this is only executed for macOS.
if (_currentSystemInformation.Platform == PlatformID.MacOSX)
{
string flagsText = _flagsCol.Extract(line);
if (!uint.TryParse(flagsText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint tempFlags))
return null;
flags = tempFlags;
}

string ruser = _ruserCol.Extract(line);
string commandLine = _argsCol.Extract(line);
Expand Down
21 changes: 15 additions & 6 deletions src/SSHDebugPS/PipeConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ public override bool IsLinux()
/// <returns>SystemInformation containing username and architecture. If it was unable to obtain any of these, the value will be set to string.Empty.</returns>
public SystemInformation GetSystemInformation()
{
string commandOutput;
string errorMessage;
string commandOutput = string.Empty;
string errorMessage = string.Empty;
int exitCode;

string username = string.Empty;
Expand All @@ -108,13 +108,19 @@ public SystemInformation GetSystemInformation()
username = commandOutput;
}

string platform = string.Empty;
if (ExecuteCommand("uname", Timeout.Infinite, commandOutput: out commandOutput, errorMessage: out errorMessage, exitCode: out exitCode))
{
platform = commandOutput;
}

string architecture = string.Empty;
if (ExecuteCommand("uname -m", Timeout.Infinite, commandOutput: out commandOutput, errorMessage: out errorMessage, exitCode: out exitCode))
{
architecture = commandOutput;
}

return new SystemInformation(username, architecture);
return new SystemInformation(username, architecture, OperatingSystemStringConverter.ConvertToPlatformID(platform));
}

public override List<Process> ListProcesses()
Expand Down Expand Up @@ -152,12 +158,15 @@ private bool PSListProcess(SystemInformation systemInformation, out string error
errorMessage = string.Empty;
string commandOutput;
int exitCode;
if (!ExecuteCommand(PSOutputParser.PSCommandLine, Timeout.Infinite, out commandOutput, out errorMessage, out exitCode))

PSOutputParser psOutputParser = new PSOutputParser(systemInformation);

if (!ExecuteCommand(psOutputParser.PSCommandLine, Timeout.Infinite, out commandOutput, out errorMessage, out exitCode))
{
// Clear output and errorMessage
commandOutput = string.Empty;
errorMessage = string.Empty;
if (!ExecuteCommand(PSOutputParser.AltPSCommandLine, Timeout.Infinite, out commandOutput, out errorMessage, out exitCode))
if (!ExecuteCommand(psOutputParser.AltPSCommandLine, Timeout.Infinite, out commandOutput, out errorMessage, out exitCode))
{
if (exitCode == 127)
{
Expand All @@ -174,7 +183,7 @@ private bool PSListProcess(SystemInformation systemInformation, out string error
}
}

processes = PSOutputParser.Parse(commandOutput, systemInformation);
processes = psOutputParser.Parse(commandOutput);
return true;
}

Expand Down
15 changes: 12 additions & 3 deletions src/SSHDebugPS/SSH/SSHConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,31 @@ public override List<Process> ListProcesses()
username = usernameCommand.Output.TrimEnd('\n', '\r'); // trim line endings because 'id' command ends with a newline
}

string operatingSystem = string.Empty;
var operatingSystemCommand = _remoteSystem.Shell.ExecuteCommand("uname", Timeout.InfiniteTimeSpan);
if (operatingSystemCommand.ExitCode == 0)
{
operatingSystem = operatingSystemCommand.Output.TrimEnd('\n', '\r'); // trim line endings because 'uname' command ends with a newline
}

string architecture = string.Empty;
var architectureCommand = _remoteSystem.Shell.ExecuteCommand("uname -m", Timeout.InfiniteTimeSpan);
if (architectureCommand.ExitCode == 0)
{
architecture = architectureCommand.Output.TrimEnd('\n', '\r'); // trim line endings because 'uname -m' command ends with a newline
}

SystemInformation systemInformation = new SystemInformation(username, architecture);
SystemInformation systemInformation = new SystemInformation(username, architecture, OperatingSystemStringConverter.ConvertToPlatformID(operatingSystem));

PSOutputParser psOutputParser = new PSOutputParser(systemInformation);

var command = _remoteSystem.Shell.ExecuteCommand(PSOutputParser.PSCommandLine, Timeout.InfiniteTimeSpan);
var command = _remoteSystem.Shell.ExecuteCommand(psOutputParser.PSCommandLine, Timeout.InfiniteTimeSpan);
if (command.ExitCode != 0)
{
throw new CommandFailedException(StringResources.Error_PSFailed);
}

return PSOutputParser.Parse(command.Output, systemInformation);
return psOutputParser.Parse(command.Output);
}

/// <inheritdoc/>
Expand Down
59 changes: 43 additions & 16 deletions src/SSHDebugTests/PSOutputParserTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using Microsoft.SSHDebugPS;
using System.Collections.Generic;
using Xunit;
Expand All @@ -9,22 +10,46 @@ namespace SSHDebugTests
{
public class PSOutputParserTests
{
[Fact]
public void PSOutputParser_macOS()
{
const string username = "username";
const string architecture = "x86_64";
const string input =
"pppppppppp ffffffff rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr ARGS\n" +
"1 4004 root /sbin/launchd\n" +
"50 1004004 root /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/Support/fseventsd\n" +
"70 1004004 root /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Support/mds\n" +
"83 4004 _timed /usr/libexec/timed\n" +
"96 80004104 root /System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow console\n" +
"7835 4104 username ps axww -o pid=pppppppppp -o flags=ffffffff -o ruser=rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr -o args\n";

PSOutputParser psOutputParser = new PSOutputParser(new SystemInformation(username, architecture, PlatformID.MacOSX));
List<Process> r = psOutputParser.Parse(input);
Assert.Equal(5, r.Count);
// Testing flags here as PID USER ARGS are tested in the other tests.
Assert.Equal(r[0].Flags.Value, (uint)0x4004);
Assert.Equal(r[1].Flags.Value, (uint)0x1004004);
Assert.Equal(r[4].Flags.Value, (uint)0x80004104);
}

[Fact]
public void PSOutputParser_Ubuntu14()
{
const string username = "greggm";
const string architecture = "x86_64";
// example output from ps on a real Ubuntu 14 machine (with many processes removed):
const string input =
"pppppppppp ffffffff rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr COMMAND\n" +
" 1 0 root /sbin/init\n" +
" 2 0 root [kthreadd]\n" +
" 720 0 message+ dbus-daemon --system --fork\n" +
" 2389 0 greggm -bash\n" +
" 2580 0 root /sbin/dhclient -d -sf /usr/lib/NetworkManager/nm-dhcp-client.action -pf /run/sendsigs.omit.d/network-manager.dhclient-eth0.pid -lf /var/lib/NetworkManager/dhclient-d08a482b-ff90-4007-9b13-6500eb94b673-eth0.lease -cf /var/lib/NetworkManager/dhclient-eth0.conf eth0\n" +
" 2913 0 greggm ps axww -o pid=pppppppppp -o flags=ffffffff -o ruser=rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr -o args\n";
"pppppppppp rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr COMMAND\n" +
" 1 root /sbin/init\n" +
" 2 root [kthreadd]\n" +
" 720 message+ dbus-daemon --system --fork\n" +
" 2389 greggm -bash\n" +
" 2580 root /sbin/dhclient -d -sf /usr/lib/NetworkManager/nm-dhcp-client.action -pf /run/sendsigs.omit.d/network-manager.dhclient-eth0.pid -lf /var/lib/NetworkManager/dhclient-d08a482b-ff90-4007-9b13-6500eb94b673-eth0.lease -cf /var/lib/NetworkManager/dhclient-eth0.conf eth0\n" +
" 2913 greggm ps axww -o pid=pppppppppp -o ruser=rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr -o args\n";

List<Process> r = PSOutputParser.Parse(input, new SystemInformation(username, architecture));
PSOutputParser psOutputParser = new PSOutputParser(new SystemInformation(username, architecture, PlatformID.Unix));
List<Process> r = psOutputParser.Parse(input);
Assert.Equal(5, r.Count);

uint[] pids = { 1, 2, 720, 2389, 2580 };
Expand All @@ -47,10 +72,11 @@ public void PSOutputParser_SmallCol()
const string architecture = "x86_64";
// made up output for what could happen if the fields were all just 1 character in size
const string input =
"A B C D\n" +
"9 0 r /sbin/init";
"A B C\n" +
"9 r /sbin/init";

List<Process> r = PSOutputParser.Parse(input, new SystemInformation(username, architecture));
PSOutputParser psOutputParser = new PSOutputParser(new SystemInformation(username, architecture, PlatformID.Unix));
List<Process> r = psOutputParser.Parse(input);
Assert.Single(r);
Assert.Equal<uint>(9, r[0].Id);
Assert.Equal("r", r[0].UserName);
Expand All @@ -64,12 +90,13 @@ public void PSOutputParser_NoUserName()
const string username = "";
const string architecture = "";
const string input =
"pppppppppp ffffffff rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr COMMAND\n" +
" 1 0 root /sbin/init\n" +
" 720 0 dbus-daemon --system --fork\n" +
" 2389 0 greggm -bash\n";
"pppppppppp rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr COMMAND\n" +
" 1 root /sbin/init\n" +
" 720 dbus-daemon --system --fork\n" +
" 2389 greggm -bash\n";

List<Process> r = PSOutputParser.Parse(input, new SystemInformation(username, architecture));
PSOutputParser psOutputParser = new PSOutputParser(new SystemInformation(username, architecture, PlatformID.Unix));
List<Process> r = psOutputParser.Parse(input);
Assert.Equal(3, r.Count);

uint[] pids = { 1, 720, 2389 };
Expand Down
Loading