Program.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. using System.Reflection;
  2. using System.Reflection.Emit;
  3. using System.Runtime.Loader;
  4. if (args.Length < 2)
  5. {
  6. Console.Error.WriteLine("Usage: LegacyAssemblyInspector <assembly.dll> <type-or-method-filter>");
  7. return 2;
  8. }
  9. var assemblyPath = Path.GetFullPath(args[0]);
  10. AssemblyLoadContext.Default.Resolving += (_, name) =>
  11. {
  12. var candidate = Path.Combine(Path.GetDirectoryName(assemblyPath)!, $"{name.Name}.dll");
  13. return File.Exists(candidate) ? AssemblyLoadContext.Default.LoadFromAssemblyPath(candidate) : null;
  14. };
  15. var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
  16. var filter = args[1];
  17. var oneByte = new OpCode[0x100];
  18. var twoByte = new OpCode[0x100];
  19. foreach (var field in typeof(OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static))
  20. {
  21. if (field.GetValue(null) is not OpCode code) continue;
  22. var value = unchecked((ushort)code.Value);
  23. if (value < 0x100) oneByte[value] = code;
  24. else if ((value & 0xff00) == 0xfe00) twoByte[value & 0xff] = code;
  25. }
  26. Type[] assemblyTypes;
  27. try
  28. {
  29. assemblyTypes = assembly.GetTypes();
  30. }
  31. catch (ReflectionTypeLoadException error)
  32. {
  33. assemblyTypes = error.Types.Where(type => type is not null).Cast<Type>().ToArray();
  34. Console.Error.WriteLine($"Loaded {assemblyTypes.Length} partial types; {error.LoaderExceptions.Length} types could not be loaded.");
  35. foreach (var loaderError in error.LoaderExceptions.Where(item => item is not null).Select(item => item!.Message).Distinct().Take(20))
  36. Console.Error.WriteLine($" {loaderError}");
  37. }
  38. foreach (var type in assemblyTypes.Where(type => type.FullName?.Contains(filter, StringComparison.OrdinalIgnoreCase) == true
  39. || type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly)
  40. .Any(method => method.Name.Contains(filter, StringComparison.OrdinalIgnoreCase))))
  41. {
  42. Console.WriteLine($"TYPE {type.FullName}");
  43. foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
  44. {
  45. Console.WriteLine($" METHOD {method}");
  46. foreach (var attribute in method.CustomAttributes)
  47. Console.WriteLine($" ATTR {attribute.AttributeType.FullName} {string.Join(",", attribute.ConstructorArguments.Select(value => value.Value))}");
  48. var body = method.GetMethodBody();
  49. if (body is null) continue;
  50. var bytes = body.GetILAsByteArray() ?? [];
  51. for (var offset = 0; offset < bytes.Length;)
  52. {
  53. var instructionOffset = offset;
  54. var code = bytes[offset++] == 0xfe ? twoByte[bytes[offset++]] : oneByte[bytes[offset - 1]];
  55. try
  56. {
  57. object? operand = null;
  58. switch (code.OperandType)
  59. {
  60. case OperandType.InlineString:
  61. operand = method.Module.ResolveString(BitConverter.ToInt32(bytes, offset)); offset += 4; break;
  62. case OperandType.InlineMethod:
  63. case OperandType.InlineField:
  64. case OperandType.InlineType:
  65. case OperandType.InlineTok:
  66. operand = method.Module.ResolveMember(BitConverter.ToInt32(bytes, offset), type.GetGenericArguments(), method.GetGenericArguments()); offset += 4; break;
  67. case OperandType.ShortInlineI: operand = bytes[offset++]; break;
  68. case OperandType.InlineI: operand = BitConverter.ToInt32(bytes, offset); offset += 4; break;
  69. case OperandType.InlineI8: operand = BitConverter.ToInt64(bytes, offset); offset += 8; break;
  70. case OperandType.ShortInlineR: operand = BitConverter.ToSingle(bytes, offset); offset += 4; break;
  71. case OperandType.InlineR: operand = BitConverter.ToDouble(bytes, offset); offset += 8; break;
  72. case OperandType.ShortInlineBrTarget: operand = offset + 1 + (sbyte)bytes[offset]; offset += 1; break;
  73. case OperandType.InlineBrTarget: operand = offset + 4 + BitConverter.ToInt32(bytes, offset); offset += 4; break;
  74. case OperandType.InlineSwitch:
  75. var count = BitConverter.ToInt32(bytes, offset); offset += 4 + count * 4; operand = $"{count} targets"; break;
  76. case OperandType.InlineVar: operand = BitConverter.ToUInt16(bytes, offset); offset += 2; break;
  77. case OperandType.ShortInlineVar: operand = bytes[offset++]; break;
  78. }
  79. if (operand is string || operand is MemberInfo)
  80. Console.WriteLine($" IL_{instructionOffset:x4} {code.Name} {operand}");
  81. }
  82. catch (Exception error)
  83. {
  84. Console.WriteLine($" IL_{instructionOffset:x4} {code.Name} <{error.GetType().Name}>");
  85. break;
  86. }
  87. }
  88. }
  89. }
  90. return 0;