private void InstrumentInstructions(MethodDefinition methodDefinition, IEnumerable <SequencePoint> sequencePoints, string[] fileLines, InstrumentedAssembly instrumentedAssembly, string sourceRelativePath, MethodReference hitInstructionReference, Dictionary <int, Instruction> instructions, ILProcessor ilProcessor, VariableDefinition methodContextVariable) { foreach (var sequencePoint in sequencePoints) { var code = sequencePoint.ExtractCode(fileLines); if (code == null || code == "{" || code == "}") { continue; } var instruction = instructions[sequencePoint.Offset]; // if the previous instruction is a Prefix instruction then this instruction MUST go with it. // we cannot put an instruction between the two. if (instruction.Previous != null && instruction.Previous.OpCode.OpCodeType == OpCodeType.Prefix) { continue; } var instructionId = ++id; instrumentedAssembly.AddInstruction(sourceRelativePath, new InstrumentedInstruction { Id = instructionId, StartLine = sequencePoint.StartLine, EndLine = sequencePoint.EndLine, StartColumn = sequencePoint.StartColumn, EndColumn = sequencePoint.EndColumn, Class = methodDefinition.DeclaringType.FullName, Method = methodDefinition.Name, MethodFullName = methodDefinition.FullName, Instruction = instruction.ToString() }); InstrumentInstruction(instructionId, instruction, hitInstructionReference, ilProcessor, methodContextVariable); } }
private InstrumentedAssembly InstrumentAssemblyIfNecessary(string assemblyFile) { var assemblyDirectory = Path.GetDirectoryName(assemblyFile); var resolver = new DefaultAssemblyResolver(); resolver.AddSearchDirectory(assemblyDirectory); using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyFile, new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver })) { if (!HasSourceFiles(assemblyDefinition)) { return(null); } if (assemblyDefinition.CustomAttributes.Any(a => a.AttributeType.Name == "InstrumentedAttribute")) { throw new Exception($"Assembly \"{assemblyFile}\" is already instrumented"); } Console.WriteLine($"Instrumenting assembly \"{assemblyDefinition.Name.Name}\""); var instrumentedAssembly = new InstrumentedAssembly(assemblyDefinition.Name.Name); var instrumentedAttributeReference = assemblyDefinition.MainModule.ImportReference(instrumentedAttributeConstructor); assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedAttributeReference)); var enterMethodInfo = hitServiceType.GetMethod("EnterMethod"); var exitMethodInfo = methodContextType.GetMethod("Exit"); var hitInstructionMethodInfo = methodContextType.GetMethod("HitInstruction"); var methodContextClassReference = assemblyDefinition.MainModule.ImportReference(methodContextType); var enterMethodReference = assemblyDefinition.MainModule.ImportReference(enterMethodInfo); var exitMethodReference = assemblyDefinition.MainModule.ImportReference(exitMethodInfo); var hitInstructionReference = assemblyDefinition.MainModule.ImportReference(hitInstructionMethodInfo); var methods = assemblyDefinition.GetAllMethods(); var documentsGroups = methods .SelectMany(m => m.DebugInformation.SequencePoints, (method, s) => new { Method = method, SequencePoint = s, s.Document }) .GroupBy(j => j.Document) .ToArray(); foreach (var documentGroup in documentsGroups) { if (!sourceFiles.Contains(documentGroup.Key.Url)) { continue; } var sourceRelativePath = GetSourceRelativePath(documentGroup.Key.Url); if (documentGroup.Key.FileHasChanged()) { Console.WriteLine($"Ignoring modified file \"{documentGroup.Key.Url}\""); continue; } var fileLines = File.ReadAllLines(documentGroup.Key.Url); var methodGroups = documentGroup .GroupBy(j => j.Method, j => j.SequencePoint) .ToArray(); foreach (var methodGroup in methodGroups) { var methodDefinition = methodGroup.Key; var ilProcessor = methodDefinition.Body.GetILProcessor(); ilProcessor.Body.SimplifyMacros(); var instructions = methodDefinition.Body.Instructions.ToDictionary(i => i.Offset); var methodContextVariable = new VariableDefinition(methodContextClassReference); methodDefinition.Body.Variables.Add(methodContextVariable); var pathParamLoadInstruction = ilProcessor.Create(OpCodes.Ldstr, hitsFile); var enterMethodInstruction = ilProcessor.Create(OpCodes.Call, enterMethodReference); var storeMethodResultInstruction = ilProcessor.Create(OpCodes.Stloc, methodContextVariable); ilProcessor.InsertBefore(instructions[0], storeMethodResultInstruction); ilProcessor.InsertBefore(storeMethodResultInstruction, enterMethodInstruction); ilProcessor.InsertBefore(enterMethodInstruction, pathParamLoadInstruction); UpdateInstructionReferences(methodDefinition, instructions[0], pathParamLoadInstruction); foreach (var instruction in instructions.Values) { if (instruction.OpCode == OpCodes.Ret) { var loadMethodContextInstruction = ilProcessor.Create(OpCodes.Ldloc, methodContextVariable); var exitMethodInstruction = ilProcessor.Create(OpCodes.Callvirt, exitMethodReference); ilProcessor.InsertBefore(instruction, exitMethodInstruction); ilProcessor.InsertBefore(exitMethodInstruction, loadMethodContextInstruction); UpdateInstructionReferences(methodDefinition, instruction, loadMethodContextInstruction); } } foreach (var sequencePoint in methodGroup) { var code = sequencePoint.ExtractCode(fileLines); if (code == null || code == "{" || code == "}") { continue; } var instruction = instructions[sequencePoint.Offset]; // if the previous instruction is a Prefix instruction then this instruction MUST go with it. // we cannot put an instruction between the two. if (instruction.Previous != null && instruction.Previous.OpCode.OpCodeType == OpCodeType.Prefix) { continue; } var instructionId = ++id; instrumentedAssembly.AddInstruction(sourceRelativePath, new InstrumentedInstruction { Id = instructionId, StartLine = sequencePoint.StartLine, EndLine = sequencePoint.EndLine, StartColumn = sequencePoint.StartColumn, EndColumn = sequencePoint.EndColumn, Class = methodDefinition.DeclaringType.FullName, Method = methodDefinition.Name, MethodFullName = methodDefinition.FullName, Instruction = instruction.ToString() }); InstrumentInstruction(instructionId, instruction, hitInstructionReference, methodDefinition, ilProcessor, methodContextVariable); } ilProcessor.Body.OptimizeMacros(); } } var miniCoverTempPath = GetMiniCoverTempPath(); var instrumentedAssemblyFile = Path.Combine(miniCoverTempPath, $"{Guid.NewGuid()}.dll"); var instrumentedPdbFile = GetPdbFile(instrumentedAssemblyFile); assemblyDefinition.Write(instrumentedAssemblyFile, new WriterParameters { WriteSymbols = true }); instrumentedAssembly.TempAssemblyFile = instrumentedAssemblyFile; instrumentedAssembly.TempPdbFile = instrumentedPdbFile; return(instrumentedAssembly); } }
private void InstrumentInstructions( InstrumentationContext context, MethodDefinition methodDefinition, InstrumentedAssembly instrumentedAssembly, Dictionary <int, Instruction> instructionsByOffset, ILProcessor ilProcessor, VariableDefinition methodContextVariable, InstrumentedMethod instrumentedMethod) { var hitInstructionReference = methodDefinition.Module.GetOrImportReference(hitInstructionMethodInfo); foreach (var sequencePoint in methodDefinition.DebugInformation.SequencePoints) { var document = sequencePoint.Document; if (document.FileHasChanged()) { _logger.LogInformation("Ignoring modified file {file}", document.Url); continue; } if (sequencePoint.IsHidden) { continue; } var documentUrl = sequencePoint.Document.Url; var documentLines = _fileReader.ReadAllLines(new FileInfo(documentUrl)); var code = documentLines.ExtractCode( sequencePoint.StartLine, sequencePoint.EndLine, sequencePoint.StartColumn, sequencePoint.EndColumn); if (code == null || code == "{" || code == "}") { continue; } var instruction = instructionsByOffset[sequencePoint.Offset]; // if the previous instruction is a Prefix instruction then this instruction MUST go with it. // we cannot put an instruction between the two. if (instruction.Previous != null && instruction.Previous.OpCode.OpCodeType == OpCodeType.Prefix) { continue; } if (!ilProcessor.Body.Instructions.Contains(instruction)) { var methodFullName = $"{methodDefinition.DeclaringType.FullName}.{methodDefinition.Name}"; _logger.LogWarning("Skipping instruction because it was removed from method {method}", methodFullName); continue; } var sourceRelativePath = GetSourceRelativePath(context, documentUrl); var instructionId = ++context.InstructionId; instrumentedAssembly.AddInstruction(sourceRelativePath, new InstrumentedInstruction { Id = instructionId, StartLine = sequencePoint.StartLine, EndLine = sequencePoint.EndLine, StartColumn = sequencePoint.StartColumn, EndColumn = sequencePoint.EndColumn, Instruction = instruction.ToString(), Method = instrumentedMethod, Code = code }); ilProcessor.InsertBefore(instruction, new[] { ilProcessor.Create(OpCodes.Ldloc, methodContextVariable), ilProcessor.Create(OpCodes.Ldc_I4, instructionId), ilProcessor.Create(OpCodes.Callvirt, hitInstructionReference) }, true); } }