Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/cecil.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVitek Karas <10670590+vitek-karas@users.noreply.github.com>2022-09-30 00:08:47 +0300
committerGitHub <noreply@github.com>2022-09-30 00:08:47 +0300
commit9eb00e40195963ec797ddd2bd785a33ff458958d (patch)
tree9914a74188edae8f2fefe85637d9dfcee15d2a74
parent7d36386f53566513dc22195b5ad1487ea3a95cc7 (diff)
ILProcessor should also update custom debug info (#867)
* ILProcessor should also update custom debug info (#34) * ILProcessor should also update custom debug info When eidting IL with ILProcessor various pieces of debug information have references to the origin IL instructions. These references can be either resolved (point to Instruction instance), in which case the editting mostly works, or unresolved (store IL offset only) in which case they need to be resolved before the editting can occur (after the edit the original IL offsets are invalid and unresolvable). This is effectively a continuation of https://github.com/jbevain/cecil/pull/687 which implemented this for local scopes. This change extends this to async method stepping info and state machine scopes. The change refactors the code to make it easier to reuse the same logic between the various debug infos being processed. Updated the existing tests from https://github.com/jbevain/cecil/pull/687 to include async and state machine debug info (completely made up) and validate that it gets updated correctly. * PR Feedback Renamed some parameters/locals to better match the existing code style. * PR Feedback * Fix test on Linux Native PDB is not supported on Linux and the test infra falls back to portable PDB automatically. Since the two PDB implementations read the custom debug info from a different place the test constructing the input needs to adapt to this difference as well.
-rw-r--r--Mono.Cecil.Cil/MethodBody.cs229
-rw-r--r--Test/Mono.Cecil.Tests/ILProcessorTests.cs127
2 files changed, 263 insertions, 93 deletions
diff --git a/Mono.Cecil.Cil/MethodBody.cs b/Mono.Cecil.Cil/MethodBody.cs
index c9236db..89bfc90 100644
--- a/Mono.Cecil.Cil/MethodBody.cs
+++ b/Mono.Cecil.Cil/MethodBody.cs
@@ -258,7 +258,7 @@ namespace Mono.Cecil.Cil {
item.next = current;
}
- UpdateLocalScopes (null, null);
+ UpdateDebugInformation (null, null);
}
protected override void OnSet (Instruction item, int index)
@@ -271,7 +271,7 @@ namespace Mono.Cecil.Cil {
current.previous = null;
current.next = null;
- UpdateLocalScopes (item, current);
+ UpdateDebugInformation (item, current);
}
protected override void OnRemove (Instruction item, int index)
@@ -285,7 +285,7 @@ namespace Mono.Cecil.Cil {
next.previous = item.previous;
RemoveSequencePoint (item);
- UpdateLocalScopes (item, next ?? previous);
+ UpdateDebugInformation (item, next ?? previous);
item.previous = null;
item.next = null;
@@ -306,126 +306,189 @@ namespace Mono.Cecil.Cil {
}
}
- void UpdateLocalScopes (Instruction removedInstruction, Instruction existingInstruction)
+ void UpdateDebugInformation (Instruction removedInstruction, Instruction existingInstruction)
{
- var debug_info = method.debug_info;
- if (debug_info == null)
- return;
-
- // Local scopes store start/end pair of "instruction offsets". Instruction offset can be either resolved, in which case it
+ // Various bits of debug information store instruction offsets (as "pointers" to the IL)
+ // Instruction offset can be either resolved, in which case it
// has a reference to Instruction, or unresolved in which case it stores numerical offset (instruction offset in the body).
- // Typically local scopes loaded from PE/PDB files will be resolved, but it's not a requirement.
+ // Depending on where the InstructionOffset comes from (loaded from PE/PDB or constructed) it can be in either state.
// Each instruction has its own offset, which is populated on load, but never updated (this would be pretty expensive to do).
// Instructions created during the editting will typically have offset 0 (so incorrect).
- // Local scopes created during editing will also likely be resolved (so no numerical offsets).
- // So while local scopes which are unresolved are relatively rare if they appear, manipulating them based
- // on the offsets allone is pretty hard (since we can't rely on correct offsets of instructions).
- // On the other hand resolved local scopes are easy to maintain, since they point to instructions and thus inserting
+ // Manipulating unresolved InstructionOffsets is pretty hard (since we can't rely on correct offsets of instructions).
+ // On the other hand resolved InstructionOffsets are easy to maintain, since they point to instructions and thus inserting
// instructions is basically a no-op and removing instructions is as easy as changing the pointer.
// For this reason the algorithm here is:
// - First make sure that all instruction offsets are resolved - if not - resolve them
- // - First time this will be relatively expensinve as it will walk the entire method body to convert offsets to instruction pointers
- // Almost all local scopes are stored in the "right" order (sequentially per start offsets), so the code uses a simple one-item
- // cache instruction<->offset to avoid walking instructions multiple times (that would only happen for scopes which are out of order).
- // - Subsequent calls should be cheap as it will only walk all local scopes without doing anything
- // - If there was an edit on local scope which makes some of them unresolved, the cost is proportional
+ // - First time this will be relatively expensive as it will walk the entire method body to convert offsets to instruction pointers
+ // Within the same debug info, IL offsets are typically stored in the "right" order (sequentially per start offsets),
+ // so the code uses a simple one-item cache instruction<->offset to avoid walking instructions multiple times
+ // (that would only happen for scopes which are out of order).
+ // - Subsequent calls should be cheap as it will only walk all local scopes without doing anything (as it checks that they're resolved)
+ // - If there was an edit which adds some unresolved, the cost is proportional (the code will only resolve those)
// - Then update as necessary by manipulaitng instruction references alone
- InstructionOffsetCache cache = new InstructionOffsetCache () {
- Offset = 0,
- Index = 0,
- Instruction = items [0]
- };
+ InstructionOffsetResolver resolver = new InstructionOffsetResolver (items, removedInstruction, existingInstruction);
+
+ if (method.debug_info != null)
+ UpdateLocalScope (method.debug_info.Scope, ref resolver);
+
+ var custom_debug_infos = method.custom_infos ?? method.debug_info?.custom_infos;
+ if (custom_debug_infos != null) {
+ foreach (var custom_debug_info in custom_debug_infos) {
+ switch (custom_debug_info) {
+ case StateMachineScopeDebugInformation state_machine_scope:
+ UpdateStateMachineScope (state_machine_scope, ref resolver);
+ break;
- UpdateLocalScope (debug_info.Scope, removedInstruction, existingInstruction, ref cache);
+ case AsyncMethodBodyDebugInformation async_method_body:
+ UpdateAsyncMethodBody (async_method_body, ref resolver);
+ break;
+
+ default:
+ // No need to update the other debug info as they don't store instruction references
+ break;
+ }
+ }
+ }
}
- void UpdateLocalScope (ScopeDebugInformation scope, Instruction removedInstruction, Instruction existingInstruction, ref InstructionOffsetCache cache)
+ void UpdateLocalScope (ScopeDebugInformation scope, ref InstructionOffsetResolver resolver)
{
if (scope == null)
return;
- if (!scope.Start.IsResolved)
- scope.Start = ResolveInstructionOffset (scope.Start, ref cache);
-
- if (!scope.Start.IsEndOfMethod && scope.Start.ResolvedInstruction == removedInstruction)
- scope.Start = new InstructionOffset (existingInstruction);
+ scope.Start = resolver.Resolve (scope.Start);
if (scope.HasScopes) {
foreach (var subScope in scope.Scopes)
- UpdateLocalScope (subScope, removedInstruction, existingInstruction, ref cache);
+ UpdateLocalScope (subScope, ref resolver);
}
- if (!scope.End.IsResolved)
- scope.End = ResolveInstructionOffset (scope.End, ref cache);
-
- if (!scope.End.IsEndOfMethod && scope.End.ResolvedInstruction == removedInstruction)
- scope.End = new InstructionOffset (existingInstruction);
+ scope.End = resolver.Resolve (scope.End);
}
- struct InstructionOffsetCache {
- public int Offset;
- public int Index;
- public Instruction Instruction;
+ void UpdateStateMachineScope (StateMachineScopeDebugInformation debugInfo, ref InstructionOffsetResolver resolver)
+ {
+ resolver.Restart ();
+ foreach (var scope in debugInfo.Scopes) {
+ scope.Start = resolver.Resolve (scope.Start);
+ scope.End = resolver.Resolve (scope.End);
+ }
}
- InstructionOffset ResolveInstructionOffset(InstructionOffset inputOffset, ref InstructionOffsetCache cache)
+ void UpdateAsyncMethodBody (AsyncMethodBodyDebugInformation debugInfo, ref InstructionOffsetResolver resolver)
{
- if (inputOffset.IsResolved)
- return inputOffset;
+ if (!debugInfo.CatchHandler.IsResolved) {
+ resolver.Restart ();
+ debugInfo.CatchHandler = resolver.Resolve (debugInfo.CatchHandler);
+ }
- int offset = inputOffset.Offset;
+ resolver.Restart ();
+ for (int i = 0; i < debugInfo.Yields.Count; i++) {
+ debugInfo.Yields [i] = resolver.Resolve (debugInfo.Yields [i]);
+ }
- if (cache.Offset == offset)
- return new InstructionOffset (cache.Instruction);
+ resolver.Restart ();
+ for (int i = 0; i < debugInfo.Resumes.Count; i++) {
+ debugInfo.Resumes [i] = resolver.Resolve (debugInfo.Resumes [i]);
+ }
+ }
- if (cache.Offset > offset) {
- // This should be rare - we're resolving offset pointing to a place before the current cache position
- // resolve by walking the instructions from start and don't cache the result.
- int size = 0;
- for (int i = 0; i < items.Length; i++) {
- // The array can be larger than the actual size, in which case its padded with nulls at the end
- // so when we reach null, treat it as an end of the IL.
- if (items [i] == null)
- return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+ struct InstructionOffsetResolver {
+ readonly Instruction [] items;
+ readonly Instruction removed_instruction;
+ readonly Instruction existing_instruction;
- if (size == offset)
- return new InstructionOffset (items [i]);
+ int cache_offset;
+ int cache_index;
+ Instruction cache_instruction;
- if (size > offset)
- return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+ public int LastOffset { get => cache_offset; }
- size += items [i].GetSize ();
- }
+ public InstructionOffsetResolver (Instruction[] instructions, Instruction removedInstruction, Instruction existingInstruction)
+ {
+ items = instructions;
+ removed_instruction = removedInstruction;
+ existing_instruction = existingInstruction;
+ cache_offset = 0;
+ cache_index = 0;
+ cache_instruction = items [0];
+ }
- // Offset is larger than the size of the body - so it points after the end
- return new InstructionOffset ();
- } else {
- // The offset points after the current cache position - so continue counting and update the cache
- int size = cache.Offset;
- for (int i = cache.Index; i < items.Length; i++) {
- cache.Index = i;
- cache.Offset = size;
+ public void Restart ()
+ {
+ cache_offset = 0;
+ cache_index = 0;
+ cache_instruction = items [0];
+ }
- var item = items [i];
+ public InstructionOffset Resolve (InstructionOffset inputOffset)
+ {
+ var result = ResolveInstructionOffset (inputOffset);
+ if (!result.IsEndOfMethod && result.ResolvedInstruction == removed_instruction)
+ result = new InstructionOffset (existing_instruction);
- // Allow for trailing null values in the case of
- // instructions.Size < instructions.Capacity
- if (item == null)
- return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+ return result;
+ }
- cache.Instruction = item;
+ InstructionOffset ResolveInstructionOffset (InstructionOffset inputOffset)
+ {
+ if (inputOffset.IsResolved)
+ return inputOffset;
- if (cache.Offset == offset)
- return new InstructionOffset (cache.Instruction);
+ int offset = inputOffset.Offset;
- if (cache.Offset > offset)
- return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+ if (cache_offset == offset)
+ return new InstructionOffset (cache_instruction);
- size += item.GetSize ();
- }
+ if (cache_offset > offset) {
+ // This should be rare - we're resolving offset pointing to a place before the current cache position
+ // resolve by walking the instructions from start and don't cache the result.
+ int size = 0;
+ for (int i = 0; i < items.Length; i++) {
+ // The array can be larger than the actual size, in which case its padded with nulls at the end
+ // so when we reach null, treat it as an end of the IL.
+ if (items [i] == null)
+ return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+
+ if (size == offset)
+ return new InstructionOffset (items [i]);
+
+ if (size > offset)
+ return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+
+ size += items [i].GetSize ();
+ }
+
+ // Offset is larger than the size of the body - so it points after the end
+ return new InstructionOffset ();
+ } else {
+ // The offset points after the current cache position - so continue counting and update the cache
+ int size = cache_offset;
+ for (int i = cache_index; i < items.Length; i++) {
+ cache_index = i;
+ cache_offset = size;
- return new InstructionOffset ();
+ var item = items [i];
+
+ // Allow for trailing null values in the case of
+ // instructions.Size < instructions.Capacity
+ if (item == null)
+ return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+
+ cache_instruction = item;
+
+ if (cache_offset == offset)
+ return new InstructionOffset (cache_instruction);
+
+ if (cache_offset > offset)
+ return new InstructionOffset (i == 0 ? items [0] : items [i - 1]);
+
+ size += item.GetSize ();
+ }
+
+ return new InstructionOffset ();
+ }
}
}
}
diff --git a/Test/Mono.Cecil.Tests/ILProcessorTests.cs b/Test/Mono.Cecil.Tests/ILProcessorTests.cs
index c1dc13c..fd462c3 100644
--- a/Test/Mono.Cecil.Tests/ILProcessorTests.cs
+++ b/Test/Mono.Cecil.Tests/ILProcessorTests.cs
@@ -163,7 +163,7 @@ namespace Mono.Cecil.Tests {
[TestCase (RoundtripType.PortablePdb, true, true, false)]
public void InsertAfterWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL)
{
- var methodBody = CreateTestMethodWithLocalScopes (padIL);
+ var methodBody = CreateTestMethodWithLocalScopes (roundtripType, padIL);
methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
var il = methodBody.GetILProcessor ();
@@ -174,6 +174,10 @@ namespace Mono.Cecil.Tests {
AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 3);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 4, null);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 5, 6);
+ AssertStateMachineScope (methodBody, 1, 7);
+ AssertAsyncMethodSteppingInfo (methodBody, 0, 1, 1);
+ AssertAsyncMethodSteppingInfo (methodBody, 1, 5, 6);
+ AssertAsyncMethodSteppingInfo (methodBody, 2, 7, 7);
methodBody.Method.Module.Dispose ();
}
@@ -189,7 +193,7 @@ namespace Mono.Cecil.Tests {
[TestCase (RoundtripType.PortablePdb, true, true, false)]
public void RemoveWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL)
{
- var methodBody = CreateTestMethodWithLocalScopes (padIL);
+ var methodBody = CreateTestMethodWithLocalScopes (roundtripType, padIL);
methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
var il = methodBody.GetILProcessor ();
@@ -200,6 +204,10 @@ namespace Mono.Cecil.Tests {
AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 1);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 2, null);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 3, 4);
+ AssertStateMachineScope (methodBody, 1, 5);
+ AssertAsyncMethodSteppingInfo (methodBody, 0, 1, 1);
+ AssertAsyncMethodSteppingInfo (methodBody, 1, 3, 4);
+ AssertAsyncMethodSteppingInfo (methodBody, 2, 5, 5);
methodBody.Method.Module.Dispose ();
}
@@ -215,7 +223,7 @@ namespace Mono.Cecil.Tests {
[TestCase (RoundtripType.PortablePdb, true, true, false)]
public void ReplaceWithSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL)
{
- var methodBody = CreateTestMethodWithLocalScopes (padIL);
+ var methodBody = CreateTestMethodWithLocalScopes (roundtripType, padIL);
methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
var il = methodBody.GetILProcessor ();
@@ -226,6 +234,10 @@ namespace Mono.Cecil.Tests {
AssertLocalScope (methodBody, wholeBodyScope.Scopes [0], 1, 2);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 3, null);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 4, 5);
+ AssertStateMachineScope (methodBody, 1, 6);
+ AssertAsyncMethodSteppingInfo (methodBody, 0, 1, 1);
+ AssertAsyncMethodSteppingInfo (methodBody, 1, 4, 5);
+ AssertAsyncMethodSteppingInfo (methodBody, 2, 6, 6);
methodBody.Method.Module.Dispose ();
}
@@ -241,7 +253,7 @@ namespace Mono.Cecil.Tests {
[TestCase (RoundtripType.PortablePdb, true, true, false)]
public void EditBodyWithScopesAndSymbolRoundtrip (RoundtripType roundtripType, bool forceUnresolved, bool reverseScopes, bool padIL)
{
- var methodBody = CreateTestMethodWithLocalScopes (padIL);
+ var methodBody = CreateTestMethodWithLocalScopes (roundtripType, padIL);
methodBody = RoundtripMethodBody (methodBody, roundtripType, forceUnresolved, reverseScopes);
var il = methodBody.GetILProcessor ();
@@ -260,6 +272,10 @@ namespace Mono.Cecil.Tests {
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1], 3, null);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [0], 4, 5);
AssertLocalScope (methodBody, wholeBodyScope.Scopes [1].Scopes [1], 5, 6);
+ AssertStateMachineScope (methodBody, 1, 7);
+ AssertAsyncMethodSteppingInfo (methodBody, 0, 1, 1);
+ AssertAsyncMethodSteppingInfo (methodBody, 1, 4, 5);
+ AssertAsyncMethodSteppingInfo (methodBody, 2, 7, 7);
methodBody.Method.Module.Dispose ();
}
@@ -310,6 +326,21 @@ namespace Mono.Cecil.Tests {
return method.Body;
}
+ static MethodDefinition CreateEmptyTestMethod (ModuleDefinition module, string name)
+ {
+ var method = new MethodDefinition {
+ Name = name,
+ Attributes = MethodAttributes.Public | MethodAttributes.Static
+ };
+
+ var il = method.Body.GetILProcessor ();
+ il.Emit (OpCodes.Ret);
+
+ method.ReturnType = module.ImportReference (typeof (void));
+
+ return method;
+ }
+
static ScopeDebugInformation VerifyWholeBodyScope (MethodBody body)
{
var debug_info = body.Method.DebugInformation;
@@ -318,17 +349,51 @@ namespace Mono.Cecil.Tests {
return debug_info.Scope;
}
+ static void AssertInstructionOffset (Instruction instruction, InstructionOffset instructionOffset)
+ {
+ if (instructionOffset.IsResolved)
+ Assert.AreEqual (instruction, instructionOffset.ResolvedInstruction);
+ else
+ Assert.AreEqual (instruction.Offset, instructionOffset.Offset);
+ }
+
+ static void AssertEndOfScopeOffset (MethodBody methodBody, InstructionOffset instructionOffset, int? index)
+ {
+ if (index.HasValue)
+ AssertInstructionOffset (methodBody.Instructions [index.Value], instructionOffset);
+ else
+ Assert.IsTrue (instructionOffset.IsEndOfMethod);
+ }
+
static void AssertLocalScope (MethodBody methodBody, ScopeDebugInformation scope, int startIndex, int? endIndex)
{
Assert.IsNotNull (scope);
- Assert.AreEqual (methodBody.Instructions [startIndex], scope.Start.ResolvedInstruction);
- if (endIndex.HasValue)
- Assert.AreEqual (methodBody.Instructions [endIndex.Value], scope.End.ResolvedInstruction);
- else
- Assert.IsTrue (scope.End.IsEndOfMethod);
+ AssertInstructionOffset (methodBody.Instructions [startIndex], scope.Start);
+ AssertEndOfScopeOffset (methodBody, scope.End, endIndex);
+ }
+
+ static void AssertStateMachineScope (MethodBody methodBody, int startIndex, int? endIndex)
+ {
+ var customDebugInfo = methodBody.Method.HasCustomDebugInformations ? methodBody.Method.CustomDebugInformations : methodBody.Method.DebugInformation.CustomDebugInformations;
+ var stateMachineScope = customDebugInfo.OfType<StateMachineScopeDebugInformation> ().SingleOrDefault ();
+ Assert.IsNotNull (stateMachineScope);
+ Assert.AreEqual (1, stateMachineScope.Scopes.Count);
+ AssertInstructionOffset (methodBody.Instructions [startIndex], stateMachineScope.Scopes [0].Start);
+ AssertEndOfScopeOffset (methodBody, stateMachineScope.Scopes [0].End, endIndex);
+ }
+
+ static void AssertAsyncMethodSteppingInfo (MethodBody methodBody, int infoNumber, int yieldIndex, int resumeIndex)
+ {
+ var customDebugInfo = methodBody.Method.HasCustomDebugInformations ? methodBody.Method.CustomDebugInformations : methodBody.Method.DebugInformation.CustomDebugInformations;
+ var asyncMethodInfo = customDebugInfo.OfType<AsyncMethodBodyDebugInformation> ().SingleOrDefault ();
+ Assert.IsNotNull (asyncMethodInfo);
+ Assert.Greater (asyncMethodInfo.Yields.Count, infoNumber);
+ Assert.AreEqual (asyncMethodInfo.Yields.Count, asyncMethodInfo.Resumes.Count);
+ AssertInstructionOffset (methodBody.Instructions [yieldIndex], asyncMethodInfo.Yields [infoNumber]);
+ AssertInstructionOffset (methodBody.Instructions [resumeIndex], asyncMethodInfo.Resumes [infoNumber]);
}
- static MethodBody CreateTestMethodWithLocalScopes (bool padILWithNulls)
+ static MethodBody CreateTestMethodWithLocalScopes (RoundtripType roundtripType, bool padILWithNulls)
{
var module = ModuleDefinition.CreateModule ("TestILProcessor", ModuleKind.Dll);
var type = new TypeDefinition ("NS", "TestType", TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed, module.ImportReference (typeof (object)));
@@ -342,6 +407,9 @@ namespace Mono.Cecil.Tests {
method.ReturnType = module.ImportReference (typeof (void));
type.Methods.Add (method);
+ var emptyMethod = CreateEmptyTestMethod (module, "empty");
+ type.Methods.Add (emptyMethod);
+
methodBody.InitLocals = true;
var tempVar1 = new VariableDefinition (module.ImportReference (typeof (string)));
methodBody.Variables.Add (tempVar1);
@@ -363,6 +431,7 @@ namespace Mono.Cecil.Tests {
}
// The method looks like this:
+ // Scopes
// | Scope | Scope.Scopes[0] | Scope.Scopes[1] | Scope.Scopes[1].Scopes[0]
// #0 Nop | > | | |
// #1 Ldloc_0 | . | > | |
@@ -372,6 +441,17 @@ namespace Mono.Cecil.Tests {
// #5 Ldloc_2 | . | | . | <
// #6 Nop | . | | . |
// <end of m> | < | | < |
+ //
+ // Async and state machine infos
+ // | Catch handler | Yields | Resumes | State machine |
+ // #0 Nop | | | | |
+ // #1 Ldloc_0 | | 0 | 0 | > |
+ // #2 Nop | | | | . |
+ // #3 Ldloc_1 | | | | . |
+ // #4 Nop | | 1 | | . |
+ // #5 Ldloc_2 | | | 1 | . |
+ // #6 Nop | * | 2 | 2 | < |
+ // <end of m> | | | | |
var instructions = methodBody.Instructions;
debug_info.Scope = new ScopeDebugInformation (instructions[0], null) {
@@ -389,6 +469,33 @@ namespace Mono.Cecil.Tests {
}
};
+ // For some reason the Native PDB reader/writer store the custom info on the method.DebugInfo.CustomInfo, while portable PDB stores it on method.CustomInfo.
+ var customDebugInfo = (roundtripType == RoundtripType.Pdb && Platform.HasNativePdbSupport)
+ ? method.DebugInformation.CustomDebugInformations : method.CustomDebugInformations;
+ customDebugInfo.Add (new StateMachineScopeDebugInformation () {
+ Scopes = {
+ new StateMachineScope(instructions[1], instructions[6])
+ }
+ });
+ customDebugInfo.Add (new AsyncMethodBodyDebugInformation () {
+ CatchHandler = new InstructionOffset (instructions [6]),
+ Yields = {
+ new InstructionOffset (instructions [1]),
+ new InstructionOffset (instructions [4]),
+ new InstructionOffset (instructions [6])
+ },
+ Resumes = {
+ new InstructionOffset (instructions [1]),
+ new InstructionOffset (instructions [5]),
+ new InstructionOffset (instructions [6]),
+ },
+ ResumeMethods = {
+ emptyMethod,
+ emptyMethod,
+ emptyMethod
+ }
+ });
+
return methodBody;
}