private void Export(ILogger logger, Summary summary, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase, ref int referenceIndex) { logger.WriteLine($"<h2>{summary[benchmarkCase].GetRuntimeInfo()}</h2>"); logger.WriteLine("<table><tbody>"); int methodIndex = 0; foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) { referenceIndex++; logger.WriteLine($"<tr><th colspan=\"2\" style=\"text-align: left;\">{method.Name}</th><th></th></tr>"); var pretty = DisassemblyPrettifier.Prettify(method, disassemblyResult, config, $"M{methodIndex++:00}"); bool even = false, diffTheLabels = pretty.Count > 1; foreach (var element in pretty) { if (element is DisassemblyPrettifier.Label label) { even = !even; logger.WriteLine($"<tr class=\"{(even && diffTheLabels ? "evenMap" : string.Empty)}\">"); logger.WriteLine($"<td id=\"{referenceIndex}_{label.Id}\" class=\"label\" data-label=\"{referenceIndex}_{label.TextRepresentation}\"><pre><code>{label.TextRepresentation}</pre></code></td>"); logger.WriteLine("<td> </td></tr>"); continue; } logger.WriteLine($"<tr class=\"{(even && diffTheLabels ? "evenMap" : string.Empty)}\">"); logger.Write("<td></td>"); if (element is DisassemblyPrettifier.Reference reference) { logger.WriteLine($"<td id=\"{referenceIndex}\" class=\"reference\" data-reference=\"{referenceIndex}_{reference.Id}\"><a href=\"#{referenceIndex}_{reference.Id}\"><pre><code>{reference.TextRepresentation}</pre></code></a></td>"); } else { logger.WriteLine($"<td><pre><code>{element.TextRepresentation}</pre></code></td>"); } logger.Write("</tr>"); } logger.WriteLine("<tr><td colspan=\"{2}\"> </td></tr>"); } foreach (var withProblems in disassemblyResult.Methods .Where(method => !string.IsNullOrEmpty(method.Problem)) .GroupBy(method => method.Problem)) { logger.WriteLine($"<tr><td colspan=\"{2}\"><b>{withProblems.Key}</b></td></tr>"); foreach (var withProblem in withProblems) { logger.WriteLine($"<tr><td colspan=\"{2}\">{withProblem.Name}</td></tr>"); } logger.WriteLine("<tr><td colspan=\"{2}\"></td></tr>"); } logger.WriteLine("</tbody></table>"); }
public bool TryDisassembleInstruction(ulong pc, byte[] data, uint flags, out DisassemblyResult result, int memoryOffset = 0) { var strBuf = Marshal.AllocHGlobal(1024); var marshalledData = Marshal.AllocHGlobal(data.Length - memoryOffset); Marshal.Copy(data, memoryOffset, marshalledData, data.Length - memoryOffset); var bytes = llvm_disasm_instruction(context, marshalledData, (ulong)(data.Length - memoryOffset), strBuf, 1024); if (bytes == 0) { result = default(DisassemblyResult); return(false); } var strBldr = new StringBuilder(); if (!HexFormatter(strBldr, bytes, memoryOffset, data)) { result = default(DisassemblyResult); return(false); } result = new DisassemblyResult { PC = pc, OpcodeSize = bytes, OpcodeString = strBldr.ToString(), DisassemblyString = Marshal.PtrToStringAnsi(strBuf) }; Marshal.FreeHGlobal(strBuf); Marshal.FreeHGlobal(marshalledData); return(true); }
public void CanParseInvalidMonoDisassemblyOutput() { const string input = @"lalala"; var expected = new DisassemblyResult { Methods = new[] { new DisassembledMethod { Name = "Foo", Maps = new[] { new Map { Instructions = input .Split('\r', '\n') .Where(line => !string.IsNullOrWhiteSpace(line)) .Select(line => new Diagnosers.Code { TextRepresentation = line }) .ToArray() } } } }, Errors = new[] { @"It's impossible to find assembly instructions in the mono output" } }; Check(input, expected, "Foo"); }
internal static void Export(ILogger logger, DisassemblyResult disassemblyResult, bool quotingCode = true) { int methodIndex = 0; foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) { if (quotingCode) { logger.WriteLine("```assembly"); } logger.WriteLine($"; {method.Name}"); var pretty = DisassemblyPrettifier.Prettify(method, $"M{methodIndex++:00}"); uint totalSizeInBytes = 0; foreach (var element in pretty) { if (element is DisassemblyPrettifier.Label label) { logger.WriteLine($"{label.TextRepresentation}:"); continue; } if (element.Source is Asm asm) { totalSizeInBytes += asm.SizeInBytes; } string prefix = " "; logger.WriteLine($"{prefix}{element.TextRepresentation.Replace("\n", "\n" + prefix)}"); } logger.WriteLine($"; Total bytes of code {totalSizeInBytes}"); if (quotingCode) { logger.WriteLine("```"); } } foreach (var withProblems in disassemblyResult.Methods .Where(method => !string.IsNullOrEmpty(method.Problem)) .GroupBy(method => method.Problem)) { logger.WriteLine($"**{withProblems.Key}**"); foreach (var withProblem in withProblems) { logger.WriteLine(withProblem.Name); } } logger.WriteLine(); }
public void CanParseMonoDisassemblyOutputFromWindowsWithoutTools() { const string input = @" Basic block 0 starting at offset 0xd Basic block 3 starting at offset 0xd Basic block 5 starting at offset 0x18 Basic block 4 starting at offset 0x1c Basic block 6 starting at offset 0x21 Basic block 1 starting at offset 0x24 CFA: [31] def_cfa: %rsp+0x8 Method int BenchmarkDotNet.Samples.Intro.IntroDisasm:Foo () emitted at 0000027E7E4E12E0 to 0000027E7E4E1312 (code length 50) [BenchmarkDotNet.Samples.dll] 'as' is not recognized as an internal or external command, operable program or batch file. 'x86_64-w64-mingw32-objdump.exe' is not recognized as an internal or external command, operable program or batch file. "; var expected = new DisassemblyResult { Methods = new[] { new DisassembledMethod { Name = "Foo", Maps = new[] { new Map { Instructions = input .Split('\r', '\n') .Where(line => !string.IsNullOrWhiteSpace(line)) .Select(line => new Diagnosers.Code { TextRepresentation = line }) .ToArray() } } } }, Errors = new[] { @"It's impossible to get Mono disasm because you don't have some required tools: 'as' is not recognized as an internal or external command 'x86_64-w64-mingw32-objdump.exe' is not recognized as an internal or external command" } }; Check(input, expected, "Foo"); }
private static void Check(string input, DisassemblyResult expected, string methodName) { var disassemblyResult = MonoDisassembler.OutputParser.Parse( input.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), methodName, commandLine: string.Empty); Assert.Equal(expected.Methods.Single().Name, disassemblyResult.Methods.Single().Name); Assert.Equal(expected.Methods[0].Maps[0].Instructions.Length, disassemblyResult.Methods[0].Maps[0].Instructions.Length); for (int i = 0; i < expected.Methods[0].Maps[0].Instructions.Length; i++) { Assert.Equal(expected.Methods[0].Maps[0].Instructions[i].TextRepresentation, disassemblyResult.Methods[0].Maps[0].Instructions[i].TextRepresentation); } }
private string SaveDisassemblyResult(Summary summary, DisassemblyResult disassemblyResult) { string filePath = $"{Path.Combine(summary.ResultsDirectoryPath, Guid.NewGuid().ToString())}-diff.temp"; if (File.Exists(filePath)) { File.Delete(filePath); } using (var stream = new StreamWriter(filePath, append: false)) { using (var streamLogger = new StreamLogger(stream)) { GithubMarkdownDisassemblyExporter.Export(streamLogger, disassemblyResult, config, quotingCode: false); } } return(filePath); }
private static string Export(Summary summary, BenchmarkCase benchmarkCase, DisassemblyResult disassemblyResult, PmcStats pmcStats) { string filePath = $"{Path.Combine(summary.ResultsDirectoryPath, benchmarkCase.Descriptor.WorkloadMethod.Name)}-{benchmarkCase.Job.Environment.Jit}-{benchmarkCase.Job.Environment.Platform}-instructionPointer.html"; if (File.Exists(filePath)) { File.Delete(filePath); } var totals = SumHardwareCountersStatsOfBenchmarkedCode(disassemblyResult, pmcStats); var perMethod = SumHardwareCountersPerMethod(disassemblyResult, pmcStats); using (var stream = StreamWriter.FromPath(filePath)) { Export(new StreamLogger(stream), benchmarkCase, totals, perMethod, pmcStats.Counters.Keys.ToArray()); } return(filePath); }
private bool TryDisassembleRiscVInstruction(ulong pc, byte[] memory, uint flags, out DisassemblyResult result, int memoryOffset = 0) { var opcode = BitHelper.ToUInt32(memory, memoryOffset, Math.Min(4, memory.Length - memoryOffset), true); if (!TryDecodeRiscVOpcodeLength(opcode, out var opcodeLength)) { result = default(DisassemblyResult); return(false); } // trim opcode and keep only `opcodeLength` LSBytes opcode &= uint.MaxValue >> (64 - (opcodeLength * 8)); result = new DisassemblyResult() { PC = pc, OpcodeSize = opcodeLength, OpcodeString = opcode.ToString("x").PadLeft(opcodeLength, '0') }; return(true); }
private static void Check(string input, DisassemblyResult expected, string methodName) { var disassemblyResult = MonoDisassembler.OutputParser.Parse( input.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), methodName, commandLine: string.Empty); Assert.Equal(expected.Methods.Single().Name, disassemblyResult.Methods.Single().Name); Assert.Equal(expected.Methods[0].Maps[0].SourceCodes.Length, disassemblyResult.Methods[0].Maps[0].SourceCodes.Length); for (int i = 0; i < expected.Methods[0].Maps[0].SourceCodes.Length; i++) { Assert.Equal(((MonoCode)expected.Methods[0].Maps[0].SourceCodes[i]).Text, ((MonoCode)disassemblyResult.Methods[0].Maps[0].SourceCodes[i]).Text); } Assert.Equal(expected.Errors.Length, disassemblyResult.Errors.Length); for (int i = 0; i < expected.Errors.Length; i++) { Assert.Equal(expected.Errors[i].Replace("\r", "").Replace("\n", ""), disassemblyResult.Errors[i].Replace("\r", "").Replace("\n", "")); } }
private string Export(Summary summary, Benchmark benchmark, DisassemblyResult disassemblyResult, PmcStats pmcStats) { var filePath = $"{Path.Combine(summary.ResultsDirectoryPath, benchmark.Target.Method.Name)}-{benchmark.Job.Env.Jit}-{benchmark.Job.Env.Platform}-instructionPointer.html"; if (File.Exists(filePath)) { File.Delete(filePath); } checked { var totals = SumHardwareCountersStatsOfBenchmarkedCode(disassemblyResult, pmcStats); var perMethod = SumHardwareCountersPerMethod(disassemblyResult, pmcStats); using (var stream = Portability.StreamWriter.FromPath(filePath)) { Export(new StreamLogger(stream), benchmark, totals, perMethod, pmcStats.Counters.Keys.ToArray()); } } return(filePath); }
private string Export(Summary summary, BenchmarkCase benchmarkCase, DisassemblyResult disassemblyResult, PmcStats pmcStats) { string filePath = Path.Combine(summary.ResultsDirectoryPath, $"{FolderNameHelper.ToFolderName(benchmarkCase.Descriptor.Type)}." + $"{benchmarkCase.Descriptor.WorkloadMethod.Name}." + $"{GetShortRuntimeInfo(summary[benchmarkCase].GetRuntimeInfo())}.counters.html"); filePath.DeleteFileIfExists(); var totals = SumHardwareCountersStatsOfBenchmarkedCode(disassemblyResult, pmcStats); var perMethod = SumHardwareCountersPerMethod(disassemblyResult, pmcStats); using (var stream = new StreamWriter(filePath, append: false)) { using (var streamLogger = new StreamLogger(stream)) { Export(streamLogger, benchmarkCase, totals, perMethod, pmcStats.Counters.Keys.ToArray()); } } return(filePath); }
internal static string BuildDisassemblyString(DisassemblyResult disassemblyResult, DisassemblyDiagnoserConfig config) { StringBuilder sb = new StringBuilder(); int methodIndex = 0; foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) { sb.AppendLine("```assembly"); sb.AppendLine($"; {method.Name}"); var pretty = Prettify.Value.Invoke(method, disassemblyResult, config, $"M{methodIndex++:00}"); ulong totalSizeInBytes = 0; foreach (var element in pretty) { if (element.Source() is Asm asm) { checked { totalSizeInBytes += (uint)asm.Instruction.ByteLength; } sb.AppendLine($" {element.TextRepresentation()}"); } else // it's a DisassemblyPrettifier.Label (internal type..) { sb.AppendLine($"{element.TextRepresentation()}:"); } } sb.AppendLine($"; Total bytes of code {totalSizeInBytes}"); sb.AppendLine("```"); } return(sb.ToString()); }
/// <summary> /// there might be some hardware counter events not belonging to the benchmarked code (for example CLR or BenchmarkDotNet's Engine) /// to calculate the % per IP we need to know the total per benchmark, not per process /// </summary> private static Dictionary <HardwareCounter, (ulong withoutNoise, ulong total)> SumHardwareCountersStatsOfBenchmarkedCode( DisassemblyResult disassemblyResult, PmcStats pmcStats) { IEnumerable <ulong> Range(Asm asm) { // most probably asm.StartAddress would be enough, but I don't want to miss any edge case for (ulong instructionPointer = asm.StartAddress; instructionPointer < asm.EndAddress; instructionPointer++) { yield return(instructionPointer); } } var instructionPointers = new HashSet <ulong>( disassemblyResult .Methods .SelectMany(method => method.Maps) .SelectMany(map => map.Instructions) .OfType <Asm>() .SelectMany(Range) .Distinct()); return(pmcStats.Counters.ToDictionary(data => data.Key, data => { ulong withoutNoise = 0, total = 0; foreach (var ipToCount in data.Value.PerInstructionPointer) { total += ipToCount.Value; if (instructionPointers.Contains(ipToCount.Key)) { withoutNoise += ipToCount.Value; } } return (withoutNoise, total); })); }
public bool TryDisassembleInstruction(ulong pc, byte[] memory, uint flags, out DisassemblyResult result, int memoryOffset = 0) { switch (pc) { case 0xFFFFFFF0: case 0xFFFFFFF1: // Return to Handler mode, exception return uses non-floating-point state from the MSP and execution uses MSP after return. result = new DisassemblyResult { PC = pc, OpcodeSize = 4, OpcodeString = pc.ToString("X"), DisassemblyString = "Handler mode: non-floating-point state, MSP/MSP" }; return(true); case 0xFFFFFFF8: case 0xFFFFFFF9: // Return to Thread mode, exception return uses non-floating-point state from the MSP and execution uses MSP after return. result = new DisassemblyResult { PC = pc, OpcodeSize = 4, OpcodeString = pc.ToString("X"), DisassemblyString = "Thread mode: non-floating-point state, MSP/MSP" }; return(true); case 0xFFFFFFFC: case 0xFFFFFFFD: // Return to Thread mode, exception return uses non-floating-point state from the PSP and execution uses PSP after return. result = new DisassemblyResult { PC = pc, OpcodeSize = 4, OpcodeString = pc.ToString("X"), DisassemblyString = "Thread mode: non-floating-point state, PSP/PSP" }; return(true); case 0xFFFFFFE0: case 0xFFFFFFE1: // Return to Handler mode, exception return uses floating-point state from the MSP and execution uses MSP after return. result = new DisassemblyResult { PC = pc, OpcodeSize = 4, OpcodeString = pc.ToString("X"), DisassemblyString = "Handler mode: floating-point state, MSP/MSP" }; return(true); case 0xFFFFFFE8: case 0xFFFFFFE9: // Return to Thread mode, exception return uses floating-point state from the MSP and execution uses MSP after return. result = new DisassemblyResult { PC = pc, OpcodeSize = 4, OpcodeString = pc.ToString("X"), DisassemblyString = "Thread mode: floating-point state, MSP/MSP" }; return(true); case 0xFFFFFFEC: case 0xFFFFFFED: // Return to Thread mode, exception return uses floating-point state from the PSP and execution uses PSP after return. result = new DisassemblyResult { PC = pc, OpcodeSize = 4, OpcodeString = pc.ToString("X"), DisassemblyString = "Thread mode: floating-point state, PSP/PSP" }; return(true); default: return(underlyingDisassembler.TryDisassembleInstruction(pc, memory, flags, out result, memoryOffset)); } }
public void CanParseMonoDisassemblyOutputFromMac() { const string input = @" CFA: [0] def_cfa: %rsp+0x8 CFA: [0] offset: unknown at cfa-0x8 CFA: [4] def_cfa_offset: 0x10 CFA: [8] offset: %r15 at cfa-0x10 Basic block 0 starting at offset 0xb Basic block 2 starting at offset 0xb Basic block 1 starting at offset 0x27 CFA: [2f] def_cfa: %rsp+0x8 Method void BenchmarkDotNet.Samples.CPU.Cpu_Atomics:NoLock () emitted at 0x1027cdf80 to 0x1027cdfb0 (code length 48) [BenchmarkDotNet.Samples.exe] /var/folders/ld/p9yn04fs3ys6h_dkyxvv95_40000gn/T/.WuSVhL: (__TEXT,__text) section chmarkDotNet_Samples_CPU_Cpu_Atomics_NoLock: 0000000000000000 subq $0x8, %rsp 0000000000000004 movq %r15, (%rsp) 0000000000000008 movq %rdi, %r15 000000000000000b movslq 0x18(%r15), %rax 000000000000000f incl %eax 0000000000000011 movl %eax, 0x18(%r15) 0000000000000015 incl %eax 0000000000000017 movl %eax, 0x18(%r15) 000000000000001b incl %eax 000000000000001d movl %eax, 0x18(%r15) 0000000000000021 incl %eax 0000000000000023 movl %eax, 0x18(%r15) 0000000000000027 movq (%rsp), %r15 000000000000002b addq $0x8, %rsp 000000000000002f retq" ; var expected = new DisassemblyResult() { Methods = new[] { new DisassembledMethod() { Name = "NoLock", Maps = new[] { new Map() { Instructions = new[] { new Diagnosers.Code { TextRepresentation = "subq\t$0x8, %rsp" }, new Diagnosers.Code { TextRepresentation = "movq\t%r15, (%rsp)" }, new Diagnosers.Code { TextRepresentation = "movq\t%rdi, %r15" }, new Diagnosers.Code { TextRepresentation = "movslq\t0x18(%r15), %rax" }, new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, new Diagnosers.Code { TextRepresentation = "movq\t(%rsp), %r15" }, new Diagnosers.Code { TextRepresentation = "addq\t$0x8, %rsp" }, new Diagnosers.Code { TextRepresentation = "retq" } } } } } } }; Check(input, expected, "NoLock"); }
private static IReadOnlyList <MethodWithCounters> SumHardwareCountersPerMethod(DisassemblyResult disassemblyResult, PmcStats pmcStats) { var model = new List <MethodWithCounters>(disassemblyResult.Methods.Length); foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) { var groups = new List <List <CodeWithCounters> >(); foreach (var map in method.Maps) { var codeWithCounters = new List <CodeWithCounters>(map.Instructions.Length); foreach (var instruction in map.Instructions) { var totalsPerCounter = pmcStats.Counters.Keys.ToDictionary(key => key, _ => default(ulong)); if (instruction is Asm asm) { foreach (var hardwareCounter in pmcStats.Counters) { // most probably asm.StartAddress would be enough, but I don't want to miss any edge case for (ulong instructionPointer = asm.StartAddress; instructionPointer < asm.EndAddress; instructionPointer++) { if (hardwareCounter.Value.PerInstructionPointer.TryGetValue(instructionPointer, out ulong value)) { totalsPerCounter[hardwareCounter.Key] = totalsPerCounter[hardwareCounter.Key] + value; } } } } codeWithCounters.Add(new CodeWithCounters { Code = instruction, SumPerCounter = totalsPerCounter }); } groups.Add(codeWithCounters); } model.Add(new MethodWithCounters { Method = method, Instructions = groups, SumPerCounter = pmcStats.Counters.Keys.ToDictionary( hardwareCounter => hardwareCounter, hardwareCounter => { ulong sum = 0; foreach (var group in groups) { foreach (var codeWithCounter in group) { sum += codeWithCounter.SumPerCounter[hardwareCounter]; } } return(sum); } ) }); } return(model); }
public void CanParseMonoDisassemblyOutput() { const string input = @" LOCAL REGALLOC BLOCK 2: 1 il_seq_point il: 0x0 2 loadi4_membase R11 <- [%edi + 0xc] 3 int_add_imm R13 <- R11 [1] clobbers: 1 4 storei4_membase_reg [%edi + 0xc] <- R13 5 il_seq_point il: 0xe 6 move R16 <- R13 7 int_add_imm R18 <- R16 [1] clobbers: 1 8 storei4_membase_reg [%edi + 0xc] <- R18 9 il_seq_point il: 0x1c 10 move R21 <- R18 11 int_add_imm R23 <- R21 [1] clobbers: 1 12 storei4_membase_reg [%edi + 0xc] <- R23 13 il_seq_point il: 0x2a 14 move R26 <- R23 15 int_add_imm R28 <- R26 [1] clobbers: 1 16 storei4_membase_reg [%edi + 0xc] <- R28 17 il_seq_point il: 0x38 liveness: %edi [4 - 0] liveness: R11 [2 - 2] liveness: R13 [3 - 3] liveness: R16 [6 - 6] liveness: R18 [7 - 7] liveness: R21 [10 - 10] liveness: R23 [11 - 11] liveness: R26 [14 - 14] liveness: R28 [15 - 15] processing: 17 il_seq_point il: 0x38 17 il_seq_point il: 0x38 processing: 16 storei4_membase_reg [%edi + 0xc] <- R28 assigned sreg1 %eax to R28 16 storei4_membase_reg [%edi + 0xc] <- %eax processing: 15 int_add_imm R28 <- R26 [1] clobbers: 1 assigned dreg %eax to dest R28 freeable %eax (R28) (born in 15) assigned sreg1 %eax to R26 15 int_add_imm %eax <- %eax [1] clobbers: 1 processing: 14 move R26 <- R23 assigned dreg %eax to dest R26 freeable %eax (R26) (born in 14) assigned sreg1 %eax to R23 14 move %eax <- %eax processing: 13 il_seq_point il: 0x2a 13 il_seq_point il: 0x2a processing: 12 storei4_membase_reg [%edi + 0xc] <- R23 12 storei4_membase_reg [%edi + 0xc] <- %eax processing: 11 int_add_imm R23 <- R21 [1] clobbers: 1 assigned dreg %eax to dest R23 freeable %eax (R23) (born in 11) assigned sreg1 %eax to R21 11 int_add_imm %eax <- %eax [1] clobbers: 1 processing: 10 move R21 <- R18 assigned dreg %eax to dest R21 freeable %eax (R21) (born in 10) assigned sreg1 %eax to R18 10 move %eax <- %eax processing: 9 il_seq_point il: 0x1c 9 il_seq_point il: 0x1c processing: 8 storei4_membase_reg [%edi + 0xc] <- R18 8 storei4_membase_reg [%edi + 0xc] <- %eax processing: 7 int_add_imm R18 <- R16 [1] clobbers: 1 assigned dreg %eax to dest R18 freeable %eax (R18) (born in 7) assigned sreg1 %eax to R16 7 int_add_imm %eax <- %eax [1] clobbers: 1 processing: 6 move R16 <- R13 assigned dreg %eax to dest R16 freeable %eax (R16) (born in 6) assigned sreg1 %eax to R13 6 move %eax <- %eax processing: 5 il_seq_point il: 0xe 5 il_seq_point il: 0xe processing: 4 storei4_membase_reg [%edi + 0xc] <- R13 4 storei4_membase_reg [%edi + 0xc] <- %eax processing: 3 int_add_imm R13 <- R11 [1] clobbers: 1 assigned dreg %eax to dest R13 freeable %eax (R13) (born in 3) assigned sreg1 %eax to R11 3 int_add_imm %eax <- %eax [1] clobbers: 1 processing: 2 loadi4_membase R11 <- [%edi + 0xc] assigned dreg %eax to dest R11 freeable %eax (R11) (born in 2) 2 loadi4_membase %eax <- [%edi + 0xc] processing: 1 il_seq_point il: 0x0 1 il_seq_point il: 0x0 CFA: [0] def_cfa: %esp+0x4 CFA: [0] offset: unknown at cfa-0x4 CFA: [1] def_cfa_offset: 0x8 CFA: [1] offset: %ebp at cfa-0x8 CFA: [3] def_cfa_reg: %ebp CFA: [4] offset: %edi at cfa-0xc Argument 0 assigned to register %edi Basic block 0 starting at offset 0xa Basic block 2 starting at offset 0xa Basic block 1 starting at offset 0x1d Method void BenchmarkDotNet.Samples.CPU.Cpu_Atomics:NoLock () emitted at 03AC11D0 to 03AC11F6 (code length 38) [BenchmarkDotNet.Samples.exe] "; var expected = new DisassemblyResult() { Methods = new[] { new DisassembledMethod() { Name = "NoLock", Maps = new Map[] { new Map() { Instructions = new[] { new Diagnosers.Code { TextRepresentation = "loadi4_membase %eax <- [%edi + 0xc]" }, new Diagnosers.Code { TextRepresentation = "int_add_imm %eax <- %eax [1] clobbers: 1" }, new Diagnosers.Code { TextRepresentation = "storei4_membase_reg [%edi + 0xc] <- %eax" }, new Diagnosers.Code { TextRepresentation = "move %eax <- %eax" }, new Diagnosers.Code { TextRepresentation = "int_add_imm %eax <- %eax [1] clobbers: 1" }, new Diagnosers.Code { TextRepresentation = "storei4_membase_reg [%edi + 0xc] <- %eax" }, new Diagnosers.Code { TextRepresentation = "move %eax <- %eax" }, new Diagnosers.Code { TextRepresentation = "int_add_imm %eax <- %eax [1] clobbers: 1" }, new Diagnosers.Code { TextRepresentation = "storei4_membase_reg [%edi + 0xc] <- %eax" }, new Diagnosers.Code { TextRepresentation = "move %eax <- %eax" }, new Diagnosers.Code { TextRepresentation = "int_add_imm %eax <- %eax [1] clobbers: 1" }, new Diagnosers.Code { TextRepresentation = "storei4_membase_reg [%edi + 0xc] <- %eax" }, } } } } } }; var disassemblyResult = MonoDisassembler.OutputParser.Parse( input.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), "BenchmarkDotNet.Samples.CPU.Cpu_Atomics:NoLock", "NoLock"); Assert.Equal(expected.Methods.Single().Name, disassemblyResult.Methods.Single().Name); Assert.Equal(expected.Methods[0].Maps[0].Instructions.Length, disassemblyResult.Methods[0].Maps[0].Instructions.Length); for (int i = 0; i < expected.Methods[0].Maps[0].Instructions.Length; i++) { Assert.Equal(expected.Methods[0].Maps[0].Instructions[i].TextRepresentation, disassemblyResult.Methods[0].Maps[0].Instructions[i].TextRepresentation); } }
public void CanParseMonoDisassemblyOutputFromWindows() { const string input = @" CFA: [0] def_cfa: %rsp+0x8 CFA: [0] offset: unknown at cfa-0x8 CFA: [4] def_cfa_offset: 0x30 CFA: [8] offset: %rsi at cfa-0x30 CFA: [d] offset: %r14 at cfa-0x28 CFA: [12] offset: %r15 at cfa-0x20 Basic block 0 starting at offset 0x12 Basic block 3 starting at offset 0x12 Basic block 5 starting at offset 0x20 Basic block 4 starting at offset 0x37 Basic block 6 starting at offset 0x4a Basic block 1 starting at offset 0x52 CFA: [64] def_cfa: %rsp+0x8 Method int BenchmarkDotNet.Samples.My:NoLock () emitted at 000001D748E912E0 to 000001D748E91345 (code length 101) [BenchmarkDotNet.Samples.dll] /test.o: file format pe-x86-64 Disassembly of section .text: 0000000000000000 <chmarkDotNet_Samples_My_NoLock>: 0: 48 83 ec 28 sub $0x28,%rsp 4: 48 89 34 24 mov %rsi,(%rsp) 8: 4c 89 74 24 08 mov %r14,0x8(%rsp) d: 4c 89 7c 24 10 mov %r15,0x10(%rsp) 12: 45 33 ff xor %r15d,%r15d 15: 45 33 f6 xor %r14d,%r14d 18: eb 1d jmp 37 <chmarkDotNet_Samples_My_NoLock+0x37> 1a: 48 8d 64 24 00 lea 0x0(%rsp),%rsp 1f: 90 nop 20: 49 8b c7 mov %r15,%rax 23: 49 8b ce mov %r14,%rcx 26: ba 02 00 00 00 mov $0x2,%edx 2b: 0f af ca imul %edx,%ecx 2e: 4c 8b f8 mov %rax,%r15 31: 44 03 f9 add %ecx,%r15d 34: 41 ff c6 inc %r14d 37: 41 83 fe 0d cmp $0xd,%r14d 3b: 40 0f 9c c6 setl %sil 3f: 48 0f b6 f6 movzbq %sil,%rsi 43: 48 8b c6 mov %rsi,%rax 46: 85 c0 test %eax,%eax 48: 75 d6 jne 20 <chmarkDotNet_Samples_My_NoLock+0x20> 4a: 44 89 7c 24 18 mov %r15d,0x18(%rsp) 4f: 49 8b c7 mov %r15,%rax 52: 48 8b 34 24 mov (%rsp),%rsi 56: 4c 8b 74 24 08 mov 0x8(%rsp),%r14 5b: 4c 8b 7c 24 10 mov 0x10(%rsp),%r15 60: 48 83 c4 28 add $0x28,%rsp 64: c3 retq 65: 90 nop 66: 90 nop "; var expected = new DisassemblyResult() { Methods = new[] { new DisassembledMethod() { Name = "NoLock", Maps = new[] { new Map() { Instructions = new[] { new Diagnosers.Code { TextRepresentation = "sub $0x28,%rsp" }, new Diagnosers.Code { TextRepresentation = "mov %rsi,(%rsp)" }, new Diagnosers.Code { TextRepresentation = "mov %r14,0x8(%rsp)" }, new Diagnosers.Code { TextRepresentation = "mov %r15,0x10(%rsp)" }, new Diagnosers.Code { TextRepresentation = "xor %r15d,%r15d" }, new Diagnosers.Code { TextRepresentation = "xor %r14d,%r14d" }, new Diagnosers.Code { TextRepresentation = "jmp 37 <chmarkDotNet_Samples_My_NoLock+0x37>" }, new Diagnosers.Code { TextRepresentation = "lea 0x0(%rsp),%rsp" }, new Diagnosers.Code { TextRepresentation = "nop" }, new Diagnosers.Code { TextRepresentation = "mov %r15,%rax" }, new Diagnosers.Code { TextRepresentation = "mov %r14,%rcx" }, new Diagnosers.Code { TextRepresentation = "mov $0x2,%edx" }, new Diagnosers.Code { TextRepresentation = "imul %edx,%ecx" }, new Diagnosers.Code { TextRepresentation = "mov %rax,%r15" }, new Diagnosers.Code { TextRepresentation = "add %ecx,%r15d" }, new Diagnosers.Code { TextRepresentation = "inc %r14d" }, new Diagnosers.Code { TextRepresentation = "cmp $0xd,%r14d" }, new Diagnosers.Code { TextRepresentation = "setl %sil" }, new Diagnosers.Code { TextRepresentation = "movzbq %sil,%rsi" }, new Diagnosers.Code { TextRepresentation = "mov %rsi,%rax" }, new Diagnosers.Code { TextRepresentation = "test %eax,%eax" }, new Diagnosers.Code { TextRepresentation = "jne 20 <chmarkDotNet_Samples_My_NoLock+0x20>" }, new Diagnosers.Code { TextRepresentation = "mov %r15d,0x18(%rsp)" }, new Diagnosers.Code { TextRepresentation = "mov %r15,%rax" }, new Diagnosers.Code { TextRepresentation = "mov (%rsp),%rsi" }, new Diagnosers.Code { TextRepresentation = "mov 0x8(%rsp),%r14" }, new Diagnosers.Code { TextRepresentation = "mov 0x10(%rsp),%r15" }, new Diagnosers.Code { TextRepresentation = "add $0x28,%rsp" }, new Diagnosers.Code { TextRepresentation = "retq" }, } } } } } }; Check(input, expected, "NoLock"); }
internal static void Export(ILogger logger, DisassemblyResult disassemblyResult, DisassemblyDiagnoserConfig config, bool quotingCode = true) { int methodIndex = 0; foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) { if (quotingCode) { logger.WriteLine("```assembly"); } logger.WriteLine($"; {method.Name}"); var pretty = DisassemblyPrettifier.Prettify(method, disassemblyResult, config, $"M{methodIndex++:00}"); ulong totalSizeInBytes = 0; foreach (var element in pretty) { if (element is DisassemblyPrettifier.Label label) { logger.WriteLine($"{label.TextRepresentation}:"); } else if (element.Source is Sharp sharp) { logger.WriteLine($"; {sharp.Text.Replace("\n", "\n; ")}"); // they are multiline and we need to add ; for each line } else if (element.Source is Asm asm) { checked { totalSizeInBytes += (uint)asm.Instruction.ByteLength; } logger.WriteLine($" {element.TextRepresentation}"); } else if (element.Source is MonoCode mono) { logger.WriteLine(mono.Text); } } logger.WriteLine($"; Total bytes of code {totalSizeInBytes}"); if (quotingCode) { logger.WriteLine("```"); } } foreach (var withProblems in disassemblyResult.Methods .Where(method => !string.IsNullOrEmpty(method.Problem)) .GroupBy(method => method.Problem)) { logger.WriteLine($"**{withProblems.Key}**"); foreach (var withProblem in withProblems) { logger.WriteLine(withProblem.Name); } } logger.WriteLine(); }
private void Export(ILogger logger, DisassemblyResult disassemblyResult, Benchmark benchmark) { logger.WriteLine("<!DOCTYPE html><html lang='en'><head><meta charset='utf-8' />"); logger.WriteLine($"<title>Output of DisassemblyDiagnoser for {benchmark.DisplayInfo}</title>"); logger.WriteLine(InstructionPointerExporter.CssStyle); logger.WriteLine("</head>"); logger.WriteLine("<body>"); logger.WriteLine("<table>"); logger.WriteLine("<tbody>"); var methodNameToNativeCode = disassemblyResult.Methods .Where(method => string.IsNullOrEmpty(method.Problem)) .ToDictionary(method => method.Name, method => method.NativeCode); foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) { // I am using NativeCode as the id to avoid any problems with special characters like <> in html ;) logger.WriteLine( $"<tr><th colspan=\"2\" id=\"{method.NativeCode}\" style=\"text-align: left;\">{FormatMethodAddress(method.NativeCode)} {method.Name}</th><th></th></tr>"); // there is no need to distinguish the maps visually if there is only one type of code var diffTheMaps = method.Maps.SelectMany(map => map.Instructions).Select(ins => ins.GetType()).Distinct().Count() > 1; bool evenMap = true; foreach (var map in method.Maps) { foreach (var instruction in map.Instructions) { logger.WriteLine($"<tr class=\"{(evenMap && diffTheMaps ? "evenMap" : string.Empty)}\">"); logger.WriteLine($"<td><pre><code>{instruction.TextRepresentation}</pre></code></td>"); if (!string.IsNullOrEmpty(instruction.Comment) && methodNameToNativeCode.TryGetValue(instruction.Comment, out var id)) { logger.WriteLine($"<td><a href=\"#{id}\">{GetShortName(instruction.Comment)}</a></td>"); } else { logger.WriteLine($"<td>{instruction.Comment}</td>"); } logger.WriteLine("</tr>"); } evenMap = !evenMap; } logger.WriteLine("<tr><td colspan=\"{2}\"> </td></tr>"); } foreach (var withProblems in disassemblyResult.Methods .Where(method => !string.IsNullOrEmpty(method.Problem)) .GroupBy(method => method.Problem)) { logger.WriteLine($"<tr><td colspan=\"{2}\"><b>{withProblems.Key}</b></td></tr>"); foreach (var withProblem in withProblems) { logger.WriteLine($"<tr><td colspan=\"{2}\">{withProblem.Name}</td></tr>"); } logger.WriteLine("<tr><td colspan=\"{2}\"></td></tr>"); } logger.WriteLine("</tbody></table></body></html>"); }
public void CanParseMonoDisassemblyOutput() { const string input = @" CFA: [0] def_cfa: %rsp+0x8 CFA: [0] offset: unknown at cfa-0x8 CFA: [4] def_cfa_offset: 0x10 CFA: [8] offset: %r15 at cfa-0x10 Basic block 0 starting at offset 0xb Basic block 2 starting at offset 0xb Basic block 1 starting at offset 0x27 CFA: [2f] def_cfa: %rsp+0x8 Method void BenchmarkDotNet.Samples.CPU.Cpu_Atomics:NoLock () emitted at 0x1027cdf80 to 0x1027cdfb0 (code length 48) [BenchmarkDotNet.Samples.exe] /var/folders/ld/p9yn04fs3ys6h_dkyxvv95_40000gn/T/.WuSVhL: (__TEXT,__text) section chmarkDotNet_Samples_CPU_Cpu_Atomics_NoLock: 0000000000000000 subq $0x8, %rsp 0000000000000004 movq %r15, (%rsp) 0000000000000008 movq %rdi, %r15 000000000000000b movslq 0x18(%r15), %rax 000000000000000f incl %eax 0000000000000011 movl %eax, 0x18(%r15) 0000000000000015 incl %eax 0000000000000017 movl %eax, 0x18(%r15) 000000000000001b incl %eax 000000000000001d movl %eax, 0x18(%r15) 0000000000000021 incl %eax 0000000000000023 movl %eax, 0x18(%r15) 0000000000000027 movq (%rsp), %r15 000000000000002b addq $0x8, %rsp 000000000000002f retq" ; var expected = new DisassemblyResult() { Methods = new[] { new DisassembledMethod() { Name = "NoLock", Maps = new[] { new Map() { Instructions = new[] { new Diagnosers.Code { TextRepresentation = "subq\t$0x8, %rsp" }, new Diagnosers.Code { TextRepresentation = "movq\t%r15, (%rsp)" }, new Diagnosers.Code { TextRepresentation = "movq\t%rdi, %r15" }, new Diagnosers.Code { TextRepresentation = "movslq\t0x18(%r15), %rax" }, new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, new Diagnosers.Code { TextRepresentation = "incl\t%eax" }, new Diagnosers.Code { TextRepresentation = "movl\t%eax, 0x18(%r15)" }, new Diagnosers.Code { TextRepresentation = "movq\t(%rsp), %r15" }, new Diagnosers.Code { TextRepresentation = "addq\t$0x8, %rsp" }, new Diagnosers.Code { TextRepresentation = "retq" } } } } } } }; var disassemblyResult = MonoDisassembler.OutputParser.Parse( input.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None), "NoLock", commandLine: string.Empty); Assert.Equal(expected.Methods.Single().Name, disassemblyResult.Methods.Single().Name); Assert.Equal(expected.Methods[0].Maps[0].Instructions.Length, disassemblyResult.Methods[0].Maps[0].Instructions.Length); for (int i = 0; i < expected.Methods[0].Maps[0].Instructions.Length; i++) { Assert.Equal(expected.Methods[0].Maps[0].Instructions[i].TextRepresentation, disassemblyResult.Methods[0].Maps[0].Instructions[i].TextRepresentation); } }
internal static IReadOnlyList <Element> Prettify(DisassembledMethod method, DisassemblyResult disassemblyResult, DisassemblyDiagnoserConfig config, string labelPrefix) { var asmInstructions = method.Maps.SelectMany(map => map.SourceCodes.OfType <Asm>()).ToArray(); // first of all, we search of referenced addresses (jump|calls) var referencedAddresses = new HashSet <ulong>(); foreach (var asm in asmInstructions) { if (ClrMdV2Disassembler.TryGetReferencedAddress(asm.Instruction, disassemblyResult.PointerSize, out ulong referencedAddress)) { referencedAddresses.Add(referencedAddress); } } // for every IP that is referenced, we emit a uinque label var addressesToLabels = new Dictionary <ulong, string>(); int currentLabelIndex = 0; foreach (var instruction in asmInstructions) { if (referencedAddresses.Contains(instruction.InstructionPointer) && !addressesToLabels.ContainsKey(instruction.InstructionPointer)) { addressesToLabels.Add(instruction.InstructionPointer, $"{labelPrefix}_L{currentLabelIndex++:00}"); } } var formatterWithLabelsSymbols = config.GetFormatterWithSymbolSolver(addressesToLabels); var formatterWithGlobalSymbols = config.GetFormatterWithSymbolSolver(disassemblyResult.AddressToNameMapping); var prettified = new List <Element>(); foreach (var map in method.Maps) { foreach (var instruction in map.SourceCodes) { if (instruction is Sharp sharp) { prettified.Add(new Element(sharp.Text, sharp)); } else if (instruction is MonoCode mono) { prettified.Add(new Element(mono.Text, mono)); } else if (instruction is Asm asm) { // this IP is referenced by some jump|call, so we add a label if (addressesToLabels.TryGetValue(asm.InstructionPointer, out string label)) { prettified.Add(new Label(label)); } if (ClrMdV2Disassembler.TryGetReferencedAddress(asm.Instruction, disassemblyResult.PointerSize, out ulong referencedAddress)) { // jump or a call within same method if (addressesToLabels.TryGetValue(referencedAddress, out string translated)) { prettified.Add(new Reference(InstructionFormatter.Format(asm.Instruction, formatterWithLabelsSymbols, config.PrintInstructionAddresses, disassemblyResult.PointerSize), translated, asm)); continue; } // call to a known method if (disassemblyResult.AddressToNameMapping.ContainsKey(referencedAddress)) { prettified.Add(new Element(InstructionFormatter.Format(asm.Instruction, formatterWithGlobalSymbols, config.PrintInstructionAddresses, disassemblyResult.PointerSize), asm)); continue; } } prettified.Add(new Element(InstructionFormatter.Format(asm.Instruction, formatterWithGlobalSymbols, config.PrintInstructionAddresses, disassemblyResult.PointerSize), asm)); } } } return(prettified); }
public bool TryDisassembleInstruction(ulong pc, byte[] data, uint flags, out DisassemblyResult result, int memoryOffset = 0) { return(GetDisassembler(flags).TryDisassembleInstruction(pc, data, flags, out result, memoryOffset)); }