Skip to content

Fix linux crash from disassembler #2413

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Sep 22, 2023
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
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/Disassemblers/Arm64Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ protected override IEnumerable<Asm> Decode(byte[] code, ulong startAddress, Stat
}
}
}
TryTranslateAddressToName(address, isPrestubMD, state, isIndirect, depth, currentMethod);
TryTranslateAddressToName(address, isPrestubMD, state, depth, currentMethod);
}

accumulator.Feed(instruction);
Expand Down
73 changes: 44 additions & 29 deletions src/BenchmarkDotNet/Disassemblers/ClrMdV2Disassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,32 @@ namespace BenchmarkDotNet.Disassemblers
// This Disassembler uses ClrMd v2x. Please keep it in sync with ClrMdV1Disassembler (if possible).
internal abstract class ClrMdV2Disassembler
{
// Translating an address to a method can cause AV and a process crash (https://github.com/dotnet/BenchmarkDotNet/issues/2070).
// It was fixed in https://github.com/dotnet/runtime/pull/79846,
// and most likely will be backported to 7.0.2 very soon (https://github.com/dotnet/runtime/pull/79862).
protected static readonly bool IsVulnerableToAvInDac = !RuntimeInformation.IsWindows() && Environment.Version < new Version(7, 0, 2);
private static readonly ulong MinValidAddress = GetMinValidAddress();

private static ulong GetMinValidAddress()
{
// https://github.com/dotnet/BenchmarkDotNet/pull/2413#issuecomment-1688100117
if (RuntimeInformation.IsWindows())
return ushort.MaxValue + 1;
if (RuntimeInformation.IsLinux())
return (ulong) Environment.SystemPageSize;
if (RuntimeInformation.IsMacOS())
return RuntimeInformation.GetCurrentPlatform() switch
{
Environments.Platform.X86 or Environments.Platform.X64 => 4096,
Environments.Platform.Arm64 => 0x100000000,
_ => throw new NotSupportedException($"{RuntimeInformation.GetCurrentPlatform()} is not supported")
};
throw new NotSupportedException($"{System.Runtime.InteropServices.RuntimeInformation.OSDescription} is not supported");
}

private static bool IsValidAddress(ulong address)
// -1 (ulong.MaxValue) address is invalid, and will crash the runtime in older runtimes. https://github.com/dotnet/runtime/pull/90794
// 0 is NULL and therefore never valid.
// Addresses less than the minimum virtual address are also invalid.
=> address != ulong.MaxValue
&& address != 0
&& address >= MinValidAddress;

internal DisassemblyResult AttachAndDisassemble(Settings settings)
{
Expand Down Expand Up @@ -245,13 +267,13 @@ protected static bool TryReadNativeCodeAddresses(ClrRuntime runtime, ClrMethod m
return false;
}

protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD, State state, bool isIndirectCallOrJump, int depth, ClrMethod currentMethod)
protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD, State state, int depth, ClrMethod currentMethod)
{
var runtime = state.Runtime;

if (state.AddressToNameMapping.ContainsKey(address))
if (!IsValidAddress(address) || state.AddressToNameMapping.ContainsKey(address))
return;

var runtime = state.Runtime;

var jitHelperFunctionName = runtime.GetJitHelperFunctionName(address);
if (!string.IsNullOrEmpty(jitHelperFunctionName))
{
Expand All @@ -260,9 +282,9 @@ protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD,
}

var method = runtime.GetMethodByInstructionPointer(address);
if (method is null && (address & ((uint)runtime.DataTarget.DataReader.PointerSize - 1)) == 0)
if (method is null && (address & ((uint) runtime.DataTarget.DataReader.PointerSize - 1)) == 0)
{
if (runtime.DataTarget.DataReader.ReadPointer(address, out ulong newAddress) && newAddress > ushort.MaxValue)
if (runtime.DataTarget.DataReader.ReadPointer(address, out ulong newAddress) && IsValidAddress(newAddress))
{
method = runtime.GetMethodByInstructionPointer(newAddress);

Expand All @@ -276,31 +298,24 @@ protected void TryTranslateAddressToName(ulong address, bool isAddressPrecodeMD,

if (method is null)
{
if (isAddressPrecodeMD || !IsVulnerableToAvInDac)
var methodDescriptor = runtime.GetMethodByHandle(address);
if (methodDescriptor is not null)
{
var methodDescriptor = runtime.GetMethodByHandle(address);
if (!(methodDescriptor is null))
if (isAddressPrecodeMD)
{
if (isAddressPrecodeMD)
{
state.AddressToNameMapping.Add(address, $"Precode of {methodDescriptor.Signature}");
}
else
{
state.AddressToNameMapping.Add(address, $"MD_{methodDescriptor.Signature}");
}
return;
state.AddressToNameMapping.Add(address, $"Precode of {methodDescriptor.Signature}");
}
else
{
state.AddressToNameMapping.Add(address, $"MD_{methodDescriptor.Signature}");
}
return;
}

if (!IsVulnerableToAvInDac)
var methodTableName = runtime.DacLibrary.SOSDacInterface.GetMethodTableName(address);
if (!string.IsNullOrEmpty(methodTableName))
{
var methodTableName = runtime.DacLibrary.SOSDacInterface.GetMethodTableName(address);
if (!string.IsNullOrEmpty(methodTableName))
{
state.AddressToNameMapping.Add(address, $"MT_{methodTableName}");
return;
}
state.AddressToNameMapping.Add(address, $"MT_{methodTableName}");
}
return;
}
Expand Down
35 changes: 21 additions & 14 deletions src/BenchmarkDotNet/Disassemblers/DisassemblyDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,24 +109,31 @@ public IEnumerable<ValidationError> Validate(ValidationParameters validationPara
yield return new ValidationError(true, "Currently NativeAOT has no DisassemblyDiagnoser support", benchmark);
}

if (RuntimeInformation.IsLinux() && ShouldUseClrMdDisassembler(benchmark))
if (ShouldUseClrMdDisassembler(benchmark))
{
var runtime = benchmark.Job.ResolveValue(EnvironmentMode.RuntimeCharacteristic, EnvironmentResolver.Instance);

if (runtime.RuntimeMoniker < RuntimeMoniker.NetCoreApp30)
{
yield return new ValidationError(true, $"{nameof(DisassemblyDiagnoser)} supports only .NET Core 3.0+", benchmark);
}

if (ptrace_scope.Value == "2")
if (RuntimeInformation.IsLinux())
{
yield return new ValidationError(false, $"ptrace_scope is set to 2, {nameof(DisassemblyDiagnoser)} is going to work only if you run as sudo");
}
else if (ptrace_scope.Value == "3")
{
yield return new ValidationError(true, $"ptrace_scope is set to 3, {nameof(DisassemblyDiagnoser)} is not going to work");
var runtime = benchmark.Job.ResolveValue(EnvironmentMode.RuntimeCharacteristic, EnvironmentResolver.Instance);

if (runtime.RuntimeMoniker < RuntimeMoniker.NetCoreApp30)
{
yield return new ValidationError(true, $"{nameof(DisassemblyDiagnoser)} supports only .NET Core 3.0+", benchmark);
}

if (ptrace_scope.Value == "2")
{
yield return new ValidationError(false, $"ptrace_scope is set to 2, {nameof(DisassemblyDiagnoser)} is going to work only if you run as sudo");
}
else if (ptrace_scope.Value == "3")
{
yield return new ValidationError(true, $"ptrace_scope is set to 3, {nameof(DisassemblyDiagnoser)} is not going to work");
}
}
}
else if (!ShouldUseMonoDisassembler(benchmark))
{
yield return new ValidationError(true, $"Only Windows and Linux are supported in DisassemblyDiagnoser without Mono. Current OS is {System.Runtime.InteropServices.RuntimeInformation.OSDescription}");
}
}
}

Expand Down
20 changes: 1 addition & 19 deletions src/BenchmarkDotNet/Disassemblers/IntelDisassembler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,7 @@ protected override IEnumerable<Asm> Decode(byte[] code, ulong startAddress, Stat
}
}
}

if (address > ushort.MaxValue)
{
if (!IsVulnerableToAvInDac || IsCallOrJump(instruction))
{
TryTranslateAddressToName(address, isPrestubMD, state, isIndirect, depth, currentMethod);
}
}
TryTranslateAddressToName(address, isPrestubMD, state, depth, currentMethod);
}

yield return new IntelAsm
Expand All @@ -102,17 +95,6 @@ protected override IEnumerable<Asm> Decode(byte[] code, ulong startAddress, Stat
}
}

private static bool IsCallOrJump(Instruction instruction)
=> instruction.FlowControl switch
{
FlowControl.Call => true,
FlowControl.IndirectCall => true,
FlowControl.ConditionalBranch => true,
FlowControl.IndirectBranch => true,
FlowControl.UnconditionalBranch => true,
_ => false
};

private static bool TryGetReferencedAddress(Instruction instruction, uint pointerSize, out ulong referencedAddress)
{
for (int i = 0; i < instruction.OpCount; i++)
Expand Down