使用Mono.Cecil注入方法调用会在构造函数中引发InvalidProgramException。

huangapple go评论60阅读模式
英文:

Injecting method calls using Mono.Cecil draws InvalidProgramException in constructor

问题

我使用Mono.Cecil在构造函数中注入了两个方法调用。但不知何故,构造始终失败,并显示以下错误:

InvalidProgramException: Invalid IL code in Networking.ServerController:.ctor (): IL_0028: callvirt  0x0600017f

基本上我所做的是为每个stfld注入一个方法调用,同时将方法的名称(不包括类名)和当前字段值(装箱后)作为对象传递给以下方法(从两个上层的基类中继承):

protected void LocalNetworkVariableChange(string variableName, object value) {
  // 无论我放什么都会失败
}

注入前的IL代码:

IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_001d: ret

注入后:

IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByClientId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByPlayerId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_001d: ret

(!)指令前面的标签在通过Mono.Cecil写出指令到文件时是惰性更新的。这就是为什么它们似乎具有错误的偏移量并且都映射到IL_0000

最终的IL代码如下:

.method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
{
  // Code size       62 (0x3e)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  newobj     instance void [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer>::.ctor()
  IL_0006:  stfld      [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
  IL_000b:  ldarg.0
  IL_000c:  newobj     instance void [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer>::.ctor()
  IL_0011:  stfld      [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
  IL_0016:  ldarg.0
  IL_0017:  call       instance void class Networking.PeerController`1<class Networking.ServerController>::.ctor()
  IL_001c:  nop
  IL_001d:  ldarg.0
  IL_001e:  ldstr      "_connectedPlayersByClientId"
  IL_0023:  ldfld      [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
  IL_0028:  callvirt   instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
                                                                                               object)
  IL_002d:  ldarg.0
  IL_002e:  ldstr      "_connectedPlayersByPlayerId"
  IL_0033:  ldfld      [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
  IL_0038:  callvirt   instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
                                                                                               object)
  IL_003d:  ret
} // end of method ServerController::.ctor

这两个字段如下:

[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<ushort, ConnectedPlayer> _connectedPlayersByClientId = new
  Dictionary<ushort, ConnectedPlayer>();

[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<NuID, ConnectedPlayer> _connectedPlayersByPlayerId = new
  Dictionary<NuID, ConnectedPlayer>();

有任何想法吗?

英文:

I use Mono.Cecil to inject two method calls into a constructor. Somehow the construction always fails with the following error:

InvalidProgramException: Invalid IL code in Networking.ServerController:.ctor (): IL_0028: callvirt  0x0600017f

What I basically do is to inject a method call to the following method (from two base-classes above) for every stfld while passing the name of the method (without class) and the current field value (boxed) as object:

protected void LocalNetworkVariableChange(string variableName, object value) {
  // Fails no matter what i put here
}

The IL code before injection:

IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_001d: ret

After Injection:

IL_0000: ldarg.0
IL_0001: newobj System.Void System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0006: stfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_000b: ldarg.0
IL_000c: newobj System.Void System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer>::.ctor()
IL_0011: stfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0016: ldarg.0
IL_0017: call System.Void Networking.PeerController`1<Networking.ServerController>::.ctor()
IL_001c: nop
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByClientId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<System.UInt16,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_0000: ldarg.0
IL_0000: ldstr "_connectedPlayersByPlayerId"
IL_0000: ldfld System.Collections.Generic.Dictionary`2<Util.NuID,Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
IL_0000: callvirt System.Void Networking.NetworkBehaviour::LocalNetworkVariableChange(System.String,System.Object)
IL_001d: ret

(!) The labels in front of the instructions are updated lazy when writing out the instructions to file via Mono.Cecil. This is why the seem to have a wrong offset and map all to IL_0000.

The final IL code looks like this:

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // Code size       62 (0x3e)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  newobj     instance void [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer>::.ctor()
    IL_0006:  stfld      [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
    IL_000b:  ldarg.0
    IL_000c:  newobj     instance void [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer>::.ctor()
    IL_0011:  stfld      [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
    IL_0016:  ldarg.0
    IL_0017:  call       instance void class Networking.PeerController`1<class Networking.ServerController>::.ctor()
    IL_001c:  nop
    IL_001d:  ldarg.0
    IL_001e:  ldstr      "_connectedPlayersByClientId"
    IL_0023:  ldfld      [netstandard]System.Collections.Generic.Dictionary`2<uint16,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByClientId
    IL_0028:  callvirt   instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
                                                                                               object)
    IL_002d:  ldarg.0
    IL_002e:  ldstr      "_connectedPlayersByPlayerId"
    IL_0033:  ldfld      [netstandard]System.Collections.Generic.Dictionary`2<class Util.NuID,class Networking.ServerController/ConnectedPlayer> Networking.ServerController::_connectedPlayersByPlayerId
    IL_0038:  callvirt   instance void Networking.NetworkBehaviour::LocalNetworkVariableChange(string,
                                                                                               object)
    IL_003d:  ret
  } // end of method ServerController::.ctor

The two fields look like this:

[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<ushort, ConnectedPlayer> _connectedPlayersByClientId = new
  Dictionary<ushort, ConnectedPlayer>();
[NetworkVariable(SyncOnChange = false)]
private readonly Dictionary<NuID, ConnectedPlayer> _connectedPlayersByPlayerId = new
  Dictionary<NuID, ConnectedPlayer>();

Any idea?

答案1

得分: 1

第一个问题是box System.Collections.Generic.Dictionary...。你不能将一个引用类型进行装箱。

第二个问题是ldfld。根据文档

在顺序执行时,堆栈的转换行为如下:

  1. 将一个对象引用(或指针)推送到堆栈上。
  2. 从堆栈中弹出对象引用(或指针);找到对象中指定字段的值。
  3. 将字段中存储的值推送到堆栈上。

换句话说,ldfld 需要知道要从中读取字段的对象,并且它会从堆栈中消耗掉该对象参数。

因此,你需要:

ldarg.0   // 加载方法调用的 'this'
ldstr ... // 加载要传递给方法的字符串
ldarg.0   // 加载 ldfld 的 'this'
ldfld ... // 加载要传递给方法的字典
callvirt ...
英文:

The first problem is that box System.Collections.Generic.Dictionary.... You can't box a reference type.

The second problem is the ldfld. From the docs:

> The stack transitional behavior, in sequential order, is:
>
> 1. An object reference (or pointer) is pushed onto the stack.
> 2. The object reference (or pointer) is popped from the stack; the value of the specified field in the object is found.
> 3. The value stored in the field is pushed onto the stack.

In other words, ldfld needs to know the object to read the field from, and it consumes that object parameter from the stack.

So you need:

ldarg.0   // Load the 'this' for the method call
ldstr ... // Load the string to pass to the method
ldarg.0   // Load the 'this' for the ldfld
ldfld ... // Load the dictionary to pass to the method
callvirt ...

huangapple
  • 本文由 发表于 2023年7月10日 21:24:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/76654210.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定