Program.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. using System.Reflection.Emit;
  2. using System.Reflection.Metadata;
  3. using System.Reflection.Metadata.Ecma335;
  4. using System.Reflection.PortableExecutable;
  5. if (args.Length < 2)
  6. {
  7. Console.Error.WriteLine("Usage: LegacyMetadataInspector <assembly.dll> <type-or-method-filter>");
  8. return 2;
  9. }
  10. using var stream = File.OpenRead(Path.GetFullPath(args[0]));
  11. using var pe = new PEReader(stream);
  12. var metadata = pe.GetMetadataReader();
  13. var filter = args[1];
  14. var oneByte = new OpCode[0x100];
  15. var twoByte = new OpCode[0x100];
  16. foreach (var field in typeof(OpCodes).GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
  17. {
  18. if (field.GetValue(null) is not OpCode code) continue;
  19. var value = unchecked((ushort)code.Value);
  20. if (value < 0x100) oneByte[value] = code;
  21. else if ((value & 0xff00) == 0xfe00) twoByte[value & 0xff] = code;
  22. }
  23. var methodsWithMatchingString = new HashSet<MethodDefinitionHandle>();
  24. foreach (var methodHandle in metadata.MethodDefinitions)
  25. {
  26. var method = metadata.GetMethodDefinition(methodHandle);
  27. if (method.RelativeVirtualAddress != 0 && MethodContainsString(method))
  28. methodsWithMatchingString.Add(methodHandle);
  29. }
  30. foreach (var typeHandle in metadata.TypeDefinitions)
  31. {
  32. var type = metadata.GetTypeDefinition(typeHandle);
  33. var typeName = FullTypeName(type.Namespace, type.Name);
  34. var methods = type.GetMethods().Select(handle => (Handle: handle, Value: metadata.GetMethodDefinition(handle))).ToArray();
  35. var typeMatches = typeName.Contains(filter, StringComparison.OrdinalIgnoreCase);
  36. var selectedMethods = typeMatches
  37. ? methods
  38. : methods.Where(item => metadata.GetString(item.Value.Name).Contains(filter, StringComparison.OrdinalIgnoreCase)
  39. || methodsWithMatchingString.Contains(item.Handle)).ToArray();
  40. if (selectedMethods.Length == 0)
  41. continue;
  42. Console.WriteLine($"TYPE {typeName}");
  43. foreach (var methodItem in selectedMethods)
  44. {
  45. var method = methodItem.Value;
  46. Console.WriteLine($" METHOD {metadata.GetString(method.Name)}");
  47. if (method.RelativeVirtualAddress == 0) continue;
  48. var bytes = pe.GetMethodBody(method.RelativeVirtualAddress).GetILBytes();
  49. if (bytes is null) continue;
  50. for (var offset = 0; offset < bytes.Length;)
  51. {
  52. var instructionOffset = offset;
  53. var code = bytes[offset++] == 0xfe ? twoByte[bytes[offset++]] : oneByte[bytes[offset - 1]];
  54. try
  55. {
  56. string? printedOperand = null;
  57. switch (code.OperandType)
  58. {
  59. case OperandType.InlineString:
  60. var userStringToken = BitConverter.ToInt32(bytes, offset); offset += 4;
  61. printedOperand = $"\"{metadata.GetUserString(MetadataTokens.UserStringHandle(userStringToken))}\"";
  62. break;
  63. case OperandType.InlineMethod:
  64. case OperandType.InlineField:
  65. case OperandType.InlineType:
  66. case OperandType.InlineTok:
  67. var entityToken = BitConverter.ToInt32(bytes, offset); offset += 4;
  68. printedOperand = DescribeEntity(MetadataTokens.EntityHandle(entityToken));
  69. break;
  70. case OperandType.InlineSig: offset += 4; break;
  71. case OperandType.ShortInlineI: offset += 1; break;
  72. case OperandType.InlineI: offset += 4; break;
  73. case OperandType.InlineI8: offset += 8; break;
  74. case OperandType.ShortInlineR: offset += 4; break;
  75. case OperandType.InlineR: offset += 8; break;
  76. case OperandType.ShortInlineBrTarget: offset += 1; break;
  77. case OperandType.InlineBrTarget: offset += 4; break;
  78. case OperandType.InlineSwitch:
  79. var count = BitConverter.ToInt32(bytes, offset); offset += 4 + count * 4; break;
  80. case OperandType.InlineVar: offset += 2; break;
  81. case OperandType.ShortInlineVar: offset += 1; break;
  82. }
  83. if (printedOperand is not null)
  84. Console.WriteLine($" IL_{instructionOffset:x4} {code.Name} {printedOperand}");
  85. }
  86. catch (Exception error)
  87. {
  88. Console.WriteLine($" IL_{instructionOffset:x4} {code.Name} <{error.GetType().Name}>");
  89. break;
  90. }
  91. }
  92. }
  93. }
  94. return 0;
  95. string FullTypeName(StringHandle namespaceHandle, StringHandle nameHandle)
  96. {
  97. var typeNamespace = metadata.GetString(namespaceHandle);
  98. var typeName = metadata.GetString(nameHandle);
  99. return string.IsNullOrEmpty(typeNamespace) ? typeName : $"{typeNamespace}.{typeName}";
  100. }
  101. string DescribeEntity(EntityHandle handle) => handle.Kind switch
  102. {
  103. HandleKind.MethodDefinition => $"method {metadata.GetString(metadata.GetMethodDefinition((MethodDefinitionHandle)handle).Name)}",
  104. HandleKind.FieldDefinition => $"field {metadata.GetString(metadata.GetFieldDefinition((FieldDefinitionHandle)handle).Name)}",
  105. HandleKind.MemberReference => $"member {metadata.GetString(metadata.GetMemberReference((MemberReferenceHandle)handle).Name)}",
  106. HandleKind.TypeDefinition => $"type {FullTypeName(metadata.GetTypeDefinition((TypeDefinitionHandle)handle).Namespace, metadata.GetTypeDefinition((TypeDefinitionHandle)handle).Name)}",
  107. HandleKind.TypeReference => $"type {FullTypeName(metadata.GetTypeReference((TypeReferenceHandle)handle).Namespace, metadata.GetTypeReference((TypeReferenceHandle)handle).Name)}",
  108. _ => handle.Kind.ToString()
  109. };
  110. bool MethodContainsString(MethodDefinition method)
  111. {
  112. try
  113. {
  114. var bytes = pe.GetMethodBody(method.RelativeVirtualAddress).GetILBytes();
  115. if (bytes is null) return false;
  116. for (var offset = 0; offset < bytes.Length;)
  117. {
  118. var code = bytes[offset++] == 0xfe ? twoByte[bytes[offset++]] : oneByte[bytes[offset - 1]];
  119. switch (code.OperandType)
  120. {
  121. case OperandType.InlineString:
  122. var token = BitConverter.ToInt32(bytes, offset); offset += 4;
  123. if (metadata.GetUserString(MetadataTokens.UserStringHandle(token)).Contains(filter, StringComparison.OrdinalIgnoreCase))
  124. return true;
  125. break;
  126. case OperandType.InlineMethod:
  127. case OperandType.InlineField:
  128. case OperandType.InlineType:
  129. case OperandType.InlineTok:
  130. case OperandType.InlineSig:
  131. case OperandType.ShortInlineR:
  132. case OperandType.InlineI:
  133. case OperandType.InlineBrTarget: offset += 4; break;
  134. case OperandType.InlineI8:
  135. case OperandType.InlineR: offset += 8; break;
  136. case OperandType.ShortInlineI:
  137. case OperandType.ShortInlineBrTarget:
  138. case OperandType.ShortInlineVar: offset += 1; break;
  139. case OperandType.InlineVar: offset += 2; break;
  140. case OperandType.InlineSwitch:
  141. var count = BitConverter.ToInt32(bytes, offset); offset += 4 + count * 4; break;
  142. }
  143. }
  144. }
  145. catch
  146. {
  147. return false;
  148. }
  149. return false;
  150. }