Created
December 16, 2009 15:09
-
-
Save mscottford/257889 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'optparse' | |
require 'ostruct' | |
require 'Mono.Cecil' | |
class System::Reflection::Assembly | |
def get_type_from_name(name) | |
method(:GetType).overload(System::String).call(name) | |
end | |
end | |
class System::Type | |
def get_method_from_name_and_type(name, type) | |
self.method(:GetMethod). | |
overload(System::String, System::Array.of(System::Type)). | |
call(name, System::Array.of(System::Type).new([type.to_clr_type])) | |
end | |
end | |
module Log4NetInfo | |
def log_manager_get_logger | |
log_manager = assembly.get_type_from_name('log4net.LogManager') | |
raise "Could not load LogManager" if log_manager.nil? | |
log_manager.get_method_from_name_and_type('GetLogger', System::String) | |
end | |
def log_interface_debug | |
log_interface = assembly.get_type_from_name('log4net.ILog') | |
raise "Could not load ILog" if log_interface.nil? | |
log_interface.get_method_from_name_and_type('Debug', System::Object) | |
end | |
private | |
def assembly | |
IronRuby.require('log4net') | |
end | |
end | |
class CilWorkerHelper | |
def initialize(assembly, worker, insert_point) | |
@assembly = assembly | |
@worker = worker | |
@insert_point = insert_point | |
@last_instruction = nil | |
end | |
def ldstr(message) | |
insert(create_ldstr(message)) | |
end | |
def call(clr_method) | |
insert(create_call(clr_method)) | |
end | |
def call_virt(clr_method) | |
insert(create_call_virt(clr_method)) | |
end | |
private | |
def insert(instruction) | |
if @last_instruction.nil? | |
@worker.InsertBefore(@insert_point, instruction) | |
else | |
@worker.InsertAfter(@last_instruction, instruction) | |
end | |
@last_instruction = instruction | |
end | |
def create_call(clr_method) | |
op_code = Mono::Cecil::Cil::OpCodes.Call | |
method_reference = @assembly.MainModule.Import(clr_method) | |
@worker. | |
method(:Create). | |
overload(Mono::Cecil::Cil::OpCode, Mono::Cecil::MethodReference). | |
call(op_code, method_reference) | |
end | |
def create_call_virt(clr_method) | |
op_code = Mono::Cecil::Cil::OpCodes.Callvirt | |
method_reference = @assembly.MainModule.Import(clr_method) | |
@worker. | |
method(:Create). | |
overload(Mono::Cecil::Cil::OpCode, Mono::Cecil::MethodReference). | |
call(op_code, method_reference) | |
end | |
def create_ldstr(string) | |
op_code = Mono::Cecil::Cil::OpCodes.Ldstr | |
@worker. | |
method(:Create). | |
overload(Mono::Cecil::Cil::OpCode, System::String). | |
call(op_code, string) | |
end | |
end | |
class Instrumentor | |
include Log4NetInfo | |
def initialize(assembly_name) | |
@assembly_name = assembly_name | |
@assembly = Mono::Cecil::AssemblyFactory.method(:GetAssembly).overload(System::String).call(assembly_name) | |
end | |
def apply | |
@assembly.MainModule.Types.each do |type| | |
type.Constructors.each do |constructor| | |
name = "#{type.Namespace}.#{type.Name}.ctor" | |
puts "Instrumenting #{name}" | |
instrument(constructor, "Entering #{name}", "Exiting #{name}") | |
end | |
type.Methods.each do |method| | |
name = "#{type.Namespace}.#{type.Name}.#{method.Name}" | |
puts "Instrumenting #{name}" | |
instrument(method, "Entering #{name}", "Exiting #{name}") | |
end | |
end | |
puts "Saving instrumented assembly..." | |
Mono::Cecil::AssemblyFactory.SaveAssembly(@assembly, @assembly_name) | |
puts "done." | |
end | |
def remove | |
modified = false | |
@assembly.MainModule.Types.each do |type| | |
type.Constructors.each do |constructor| | |
if was_instrumented?(constructor) | |
name = "#{type.Namespace}.#{type.Name}.ctor" | |
puts "De-Instrumenting #{name}" | |
de_instrument(constructor) | |
modified = true | |
end | |
end | |
type.Methods.each do |method| | |
if was_instrumented?(method) | |
name = "#{type.Namespace}.#{type.Name}.#{method.Name}" | |
puts "De-Instrumenting #{name}" | |
de_instrument(method) | |
modified = true | |
end | |
end | |
end | |
if modified | |
puts "Saving de-instrumented assembly..." | |
Mono::Cecil::AssemblyFactory.SaveAssembly(@assembly, @assembly_name) | |
puts "done." | |
else | |
puts "Assembly does not contain any instrumentation." | |
end | |
end | |
private | |
def log_message(worker, insert_point, message) | |
cil = CilWorkerHelper.new(@assembly, worker, insert_point) | |
# ldstr "TracingLog" | |
cil.ldstr("TracingLog") | |
# call class [log4net]log4net.ILog [log4net]log4net.LogManager::GetLogger(string) | |
cil.call(log_manager_get_logger) | |
# ldstr message | |
cil.ldstr(message) | |
# callvirt instance void [log4net]log4net.ILog::Debug(object) | |
cil.call_virt(log_interface_debug) | |
end | |
def instrument(method, enter_message, exit_message) | |
if !method.nil? | |
worker = method.Body.CilWorker | |
first_instruction = method.Body.Instructions[0] | |
log_message(worker, first_instruction, enter_message) | |
last_instruction = method.Body.Instructions[method.Body.Instructions.Count - 1] | |
log_message(worker, last_instruction, exit_message) | |
end | |
end | |
def was_instrumented?(method) | |
# make sure that the first instruction is one that we added | |
first_instruction = method.Body.Instructions[0] | |
return (first_instruction.OpCode == Mono::Cecil::Cil::OpCodes.Ldstr and | |
first_instruction.Operand == "TracingLog") | |
end | |
def de_instrument(method) | |
if !method.nil? | |
if was_instrumented?(method) | |
# remove the first 4 instructions | |
4.times do | |
method.Body.Instructions.RemoveAt(0) | |
end | |
# remove the last 4 instructions (before the return) | |
4.times do | |
method.Body.Instructions.RemoveAt(method.Body.Instructions.Count - 2) | |
end | |
end | |
end | |
end | |
end | |
class InstrumentorOptions | |
def self.parse(args) | |
options = OpenStruct.new | |
options.assembly_name = nil | |
options.operation = nil | |
optionParser = OptionParser.new do |optionParser| | |
optionParser.banner = "Usage: instrumentor.rb [options] -a assembly_name" | |
optionParser.separator "" | |
optionParser.separator "Specific options:" | |
optionParser.on("-a assembly_name", "--assembly assembly_name", "The assembly to modify") do |assembly_name| | |
if !options.assembly_name.nil? | |
puts "Error: assembly may only be specified once" | |
puts optionParser | |
exit | |
end | |
options.assembly_name = assembly_name | |
end | |
optionParser.on("--apply", "Instrument the specified assembly. This is the default if no options are specified.") do | |
if !options.operation.nil? | |
if options.operation == :apply | |
puts "Error: --apply may only be supplied once" | |
elsif options.operation == :remove | |
puts "Error: --apply cannot be combined with --remove" | |
else | |
raise "Unexpected operation value" | |
end | |
puts optionParser | |
exit | |
end | |
options.operation = :apply | |
end | |
optionParser.on("--remove", "De-instrument the specified assembly") do | |
if !options.operation.nil? | |
if options.operation == :apply | |
puts "Error: --remove cannot be combined with --apply" | |
elsif options.operation == :remove | |
puts "Error: --remove may only be supplied once" | |
else | |
raise "Unexpected operation value" | |
end | |
puts optionParser | |
exit | |
end | |
options.operation = :remove | |
end | |
optionParser.on_tail("-h", "--help", "Show this message") do | |
puts optionsParser | |
exit | |
end | |
end | |
optionParser.parse!(args) | |
if options.assembly_name.nil? | |
puts "Error: assembly name must be specified." | |
puts optionParser | |
exit | |
end | |
if options.assembly_name == true | |
puts "Error: assembly name must be specified." | |
puts optionParser | |
exit | |
end | |
if options.operation.nil? | |
options.operation = :apply | |
end | |
options | |
end | |
end | |
options = InstrumentorOptions.parse(ARGV) | |
instrumentor = Instrumentor.new(options.assembly_name) | |
if options.operation == :apply | |
instrumentor.apply | |
elsif options.operation == :remove | |
instrumentor.remove | |
else | |
raise "Unexpected operation value." | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment