M3 R3.2: instrument-grpc-nonstream also captures out/ref byte[] responses

Extends the IL-rewrite to log out (byref) byte[] params at method exit (ldarg + ldind.ref), not
just byte[] inputs. This captured GetTagInfosFromName's response, which located the per-tag GUID at
offset 8 = exactly where ParseTagInfoRecord reads "typeId" — proving the SDK already parses the GUID
AddStreamValues needs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
Joseph Doherty
2026-06-21 21:25:24 -04:00
parent dafafa0c98
commit 273103f882
@@ -1401,7 +1401,7 @@ static int InstrumentGrpcNonStream(string[] args)
continue;
}
// Input byte[] params are "System.Byte[]"; out/ref byte[] are "System.Byte[]&".
// ENTRY: log non-byref byte[] inputs ("System.Byte[]").
foreach (dnlib.DotNet.Parameter bufParam in method.Parameters
.Where(p => !p.IsHiddenThisParameter && p.Type.FullName == "System.Byte[]")
.ToArray())
@@ -1425,10 +1425,49 @@ static int InstrumentGrpcNonStream(string[] args)
Type = type.Name.String,
Method = method.Name.String,
Phase = phase,
Param = bufParam.Name,
Direction = "in",
Token = "0x" + method.MDToken.Raw.ToString("X8"),
});
}
// EXIT: log out/ref byte[] responses ("System.Byte[]&") before each ret. ldarg loads the
// managed pointer; ldind.ref dereferences it to the byte[]. (RPC wrappers set the out
// param right before a single ret, so branch-to-ret skew is not a concern here.)
dnlib.DotNet.Parameter[] outParams = method.Parameters
.Where(p => !p.IsHiddenThisParameter && p.Type.FullName == "System.Byte[]&")
.ToArray();
if (outParams.Length > 0)
{
Instruction[] rets = method.Body.Instructions.Where(i => i.OpCode == OpCodes.Ret).ToArray();
foreach (Instruction ret in rets)
{
var exit = new List<Instruction>();
foreach (dnlib.DotNet.Parameter op in outParams)
{
exit.Add(Instruction.Create(OpCodes.Ldstr, $"{type.Name}.{method.Name}.{op.Name}.out"));
exit.Add(Instruction.Create(OpCodes.Ldarg, op));
exit.Add(Instruction.Create(OpCodes.Ldind_Ref));
exit.Add(Instruction.Create(OpCodes.Call, logByteArray));
}
int retIndex = method.Body.Instructions.IndexOf(ret);
foreach (Instruction instruction in ((IEnumerable<Instruction>)exit).Reverse())
{
method.Body.Instructions.Insert(retIndex, instruction);
}
}
method.Body.MaxStack = (ushort)Math.Max((int)method.Body.MaxStack, 8);
foreach (dnlib.DotNet.Parameter op in outParams)
{
instrumented.Add(new
{
Type = type.Name.String,
Method = method.Name.String,
Phase = $"{type.Name}.{method.Name}.{op.Name}.out",
Direction = "out",
Token = "0x" + method.MDToken.Raw.ToString("X8"),
});
}
}
}
}