fn generate_void_il_startup_method()

in src/profiler/elastic_apm_profiler/src/profiler/startup_hook.rs [58:459]


fn generate_void_il_startup_method(
    profiler_info: &ICorProfilerInfo4,
    module_id: ModuleID,
    module_metadata: &ModuleMetadata,
) -> Result<mdMethodDef, HRESULT> {
    let corlib_ref = helpers::create_assembly_ref_to_corlib(
        &module_metadata.assembly_emit,
        &module_metadata.cor_assembly_property,
    )?;

    log::trace!(
        "generate_void_il_startup_method: created corlib ref {} to {}",
        corlib_ref,
        &module_metadata.cor_assembly_property.name,
    );

    let object_type_ref = module_metadata
        .emit
        .define_type_ref_by_name(corlib_ref, "System.Object")
        .map_err(|e| {
            log::warn!("error defining type ref by name for System.Object. {:X}", e);
            e
        })?;

    let new_type_def = module_metadata
        .emit
        .define_type_def(
            "__ElasticVoidMethodType__",
            CorTypeAttr::tdAbstract | CorTypeAttr::tdSealed,
            object_type_ref,
            None,
        )
        .map_err(|e| {
            log::warn!("error defining type def __ElasticVoidMethodType__. {:X}", e);
            e
        })?;

    let initialize_signature = &[
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_DEFAULT.bits(),
        0,
        CorElementType::ELEMENT_TYPE_VOID as COR_SIGNATURE,
    ];

    log::trace!("generate_void_il_startup_method: define method __ElasticVoidMethodCall__");

    let new_method = module_metadata
        .emit
        .define_method(
            new_type_def,
            "__ElasticVoidMethodCall__",
            CorMethodAttr::mdStatic,
            initialize_signature,
            0,
            CorMethodImpl::miIL,
        )
        .map_err(|e| {
            log::warn!("error defining method __ElasticVoidMethodCall__. {:X}", e);
            e
        })?;

    let field_signature = &[
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_FIELD.bits(),
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
    ];

    let is_assembly_loaded_field_def = module_metadata
        .emit
        .define_field(
            new_type_def,
            "_isAssemblyLoaded",
            CorFieldAttr::fdStatic | CorFieldAttr::fdPrivate,
            field_signature,
            CorElementType::ELEMENT_TYPE_END,
            None,
            0,
        )
        .map_err(|e| {
            log::warn!("error defining field _isAssemblyLoaded. {:X}", e);
            e
        })?;

    let already_loaded_signature = &[
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_DEFAULT.bits(),
        0,
        CorElementType::ELEMENT_TYPE_BOOLEAN as COR_SIGNATURE,
    ];

    let already_loaded_method_token = module_metadata
        .emit
        .define_method(
            new_type_def,
            "IsAlreadyLoaded",
            CorMethodAttr::mdStatic | CorMethodAttr::mdPrivate,
            already_loaded_signature,
            0,
            CorMethodImpl::miIL,
        )
        .map_err(|e| {
            log::warn!("error defining method IsAlreadyLoaded. {:X}", e);
            e
        })?;

    let interlocked_type_ref = module_metadata
        .emit
        .define_type_ref_by_name(corlib_ref, "System.Threading.Interlocked")
        .map_err(|e| {
            log::warn!(
                "error defining type ref by name for System.Threading.Interlocked. {:X}",
                e
            );
            e
        })?;

    // Create method signature for System.Threading.Interlocked::CompareExchange(int32&, int32, int32)
    let interlocked_compare_exchange_signature = &[
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_DEFAULT.bits(),
        3,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_BYREF as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
    ];

    let interlocked_compare_member_ref = module_metadata
        .emit
        .define_member_ref(
            interlocked_type_ref,
            "CompareExchange",
            interlocked_compare_exchange_signature,
        )
        .map_err(|e| {
            log::warn!("error defining member ref CompareExchange. {:X}", e);
            e
        })?;

    // Write the instructions for the IsAlreadyLoaded method
    let instructions = vec![
        Instruction::ldsflda(is_assembly_loaded_field_def),
        Instruction::ldc_i4_1(),
        Instruction::ldc_i4_0(),
        Instruction::call(interlocked_compare_member_ref),
        Instruction::ldc_i4_1(),
        Instruction::ceq(),
        Instruction::ret(),
    ];

    let method_bytes = Method::tiny(instructions)
        .map_err(|e| {
            log::warn!("failed to define IsAlreadyLoaded method");
            E_FAIL
        })?
        .into_bytes();

    let allocator = profiler_info.get_il_function_body_allocator(module_id)?;
    let allocated_bytes = allocator.alloc(method_bytes.len() as ULONG)?;
    let address = unsafe { allocated_bytes.into_inner() };
    unsafe {
        std::ptr::copy(method_bytes.as_ptr(), address, method_bytes.len());
    }
    log::trace!("generate_void_il_startup_method: write IsAlreadyLoaded body");
    profiler_info
        .set_il_function_body(module_id, already_loaded_method_token, address as *const _)
        .map_err(|e| {
            log::warn!("generate_void_il_startup_method: failed to set il for IsAlreadyLoaded");
            e
        })?;

    let get_assembly_bytes_signature = &[
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_DEFAULT.bits(),
        4,
        CorElementType::ELEMENT_TYPE_VOID as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_BYREF as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_BYREF as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_BYREF as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_BYREF as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
    ];

    let pinvoke_method_def = module_metadata.emit.define_method(
        new_type_def,
        "GetAssemblyAndSymbolsBytes",
        CorMethodAttr::mdStatic | CorMethodAttr::mdPinvokeImpl | CorMethodAttr::mdHideBySig,
        get_assembly_bytes_signature,
        0,
        CorMethodImpl::empty()).map_err(|e| {
        log::warn!("generate_void_il_startup_method: failed to define method GetAssemblyAndSymbolsBytes");
        e
    })?;

    module_metadata.emit.set_method_impl_flags(pinvoke_method_def, CorMethodImpl::miPreserveSig).map_err(|e| {
        log::warn!("generate_void_il_startup_method: failed to set method impl flags for GetAssemblyAndSymbolsBytes");
        e
    })?;

    let native_profiler_file = env::get_native_profiler_file()?;
    let profiler_ref = module_metadata
        .emit
        .define_module_ref(&native_profiler_file)?;

    module_metadata.emit.define_pinvoke_map(
        pinvoke_method_def,
        CorPinvokeMap::empty(),
        "GetAssemblyAndSymbolsBytes",
        profiler_ref).map_err(|e| {
        log::warn!("generate_void_il_startup_method: failed to define pinvoke map for GetAssemblyAndSymbolsBytes");
        e
    })?;

    let byte_type_ref = module_metadata.emit.define_type_ref_by_name(corlib_ref, "System.Byte").map_err(|e| {
        log::warn!("generate_void_il_startup_method: failed to define type ref by name for System.Byte");
        e
    })?;
    let marshal_type_ref = module_metadata.emit.define_type_ref_by_name(corlib_ref, "System.Runtime.InteropServices.Marshal").map_err(|e| {
        log::warn!("generate_void_il_startup_method: failed to define type ref by name for System.Runtime.InteropServices.Marshal");
        e
    })?;

    let marshal_copy_signature = &[
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_DEFAULT.bits(),
        4,
        CorElementType::ELEMENT_TYPE_VOID as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_SZARRAY as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_U1 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
    ];

    let marshal_copy_member_ref = module_metadata
        .emit
        .define_member_ref(marshal_type_ref, "Copy", marshal_copy_signature)
        .map_err(|e| {
            log::warn!("generate_void_il_startup_method: failed to define member ref for Copy");
            e
        })?;

    let system_reflection_assembly_type_ref = module_metadata.emit.define_type_ref_by_name(corlib_ref, "System.Reflection.Assembly").map_err(|e| {
        log::warn!("generate_void_il_startup_method: failed to define type ref by name for System.Reflection.Assembly");
        e
    })?;

    let mut assembly_load_signature: Vec<COR_SIGNATURE> = vec![
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_DEFAULT.bits(),
        2,
        CorElementType::ELEMENT_TYPE_CLASS as COR_SIGNATURE,
    ];
    assembly_load_signature
        .append(&mut compress_token(system_reflection_assembly_type_ref).unwrap());
    assembly_load_signature.push(CorElementType::ELEMENT_TYPE_SZARRAY as COR_SIGNATURE);
    assembly_load_signature.push(CorElementType::ELEMENT_TYPE_U1 as COR_SIGNATURE);
    assembly_load_signature.push(CorElementType::ELEMENT_TYPE_SZARRAY as COR_SIGNATURE);
    assembly_load_signature.push(CorElementType::ELEMENT_TYPE_U1 as COR_SIGNATURE);

    let assembly_load_member_ref = module_metadata
        .emit
        .define_member_ref(
            system_reflection_assembly_type_ref,
            "Load",
            &assembly_load_signature,
        )
        .map_err(|e| {
            log::warn!("generate_void_il_startup_method: failed to define member ref Load");
            e
        })?;

    let assembly_create_instance_signature = &[
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_HASTHIS.bits(),
        1,
        CorElementType::ELEMENT_TYPE_OBJECT as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_STRING as COR_SIGNATURE,
    ];

    let assembly_create_instance_member_ref = module_metadata
        .emit
        .define_member_ref(
            system_reflection_assembly_type_ref,
            "CreateInstance",
            assembly_create_instance_signature,
        )
        .map_err(|e| {
            log::warn!(
                "generate_void_il_startup_method: failed to define member ref CreateInstance"
            );
            e
        })?;

    let load_helper_token = module_metadata
        .emit
        .define_user_string(managed::MANAGED_PROFILER_ASSEMBLY_LOADER_STARTUP)
        .map_err(|e| {
            log::warn!(
                "generate_void_il_startup_method: failed to define user string {}",
                managed::MANAGED_PROFILER_ASSEMBLY_LOADER_STARTUP
            );
            e
        })?;

    let mut locals_signature = vec![
        CorCallingConvention::IMAGE_CEE_CS_CALLCONV_LOCAL_SIG.bits(),
        7,
        CorElementType::ELEMENT_TYPE_I as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_I4 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_SZARRAY as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_U1 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_SZARRAY as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_U1 as COR_SIGNATURE,
        CorElementType::ELEMENT_TYPE_CLASS as COR_SIGNATURE,
    ];
    locals_signature.append(&mut compress_token(system_reflection_assembly_type_ref).unwrap());

    let locals_signature_token = module_metadata.emit.get_token_from_sig(&locals_signature)?;

    let instructions = vec![
        // Step 0) Check if the assembly was already loaded
        Instruction::call(already_loaded_method_token),
        // val is the offset of the instruction to go to when false
        Instruction::brfalse_s(Instruction::ret().len() as i8),
        Instruction::ret(),
        // Step 1) Call void GetAssemblyAndSymbolsBytes(out IntPtr assemblyPtr,
        // out int assemblySize, out IntPtr symbolsPtr, out int symbolsSize)
        Instruction::ldloca_s(0),
        Instruction::ldloca_s(1),
        Instruction::ldloca_s(2),
        Instruction::ldloca_s(3),
        Instruction::call(pinvoke_method_def),
        // Step 2) Call void Marshal.Copy(IntPtr source, byte[] destination,
        // int startIndex, int length) to populate the managed assembly bytes
        Instruction::ldloc_1(),
        Instruction::newarr(byte_type_ref),
        Instruction::stloc_s(4),
        Instruction::ldloc_0(),
        Instruction::ldloc_s(4),
        Instruction::ldc_i4_0(),
        Instruction::ldloc_1(),
        Instruction::call(marshal_copy_member_ref),
        // Step 3) Call void Marshal.Copy(IntPtr source, byte[] destination,
        // int startIndex, int length) to populate the symbols bytes
        Instruction::ldloc_3(),
        Instruction::newarr(byte_type_ref),
        Instruction::stloc_s(5),
        Instruction::ldloc_2(),
        Instruction::ldloc_s(5),
        Instruction::ldc_i4_0(),
        Instruction::ldloc_3(),
        Instruction::call(marshal_copy_member_ref),
        // Step 4) Call System.Reflection.Assembly System.Reflection.Assembly.Load(byte[], byte[]))
        Instruction::ldloc_s(4),
        Instruction::ldloc_s(5),
        Instruction::call(assembly_load_member_ref),
        Instruction::stloc_s(6),
        // Step 5) Call instance method Assembly.CreateInstance("Elastic.Apm.Profiler.Managed.Loader.Startup")
        Instruction::ldloc_s(6),
        Instruction::ldstr(load_helper_token),
        Instruction::callvirt(assembly_create_instance_member_ref),
        Instruction::pop(),
        Instruction::ret(),
    ];

    let method = Method {
        address: 0,
        header: MethodHeader::fat(
            false,
            false,
            instructions.iter().map(|i| i.opcode.len as u16).sum(),
            instructions.iter().map(|i| i.len() as u32).sum(),
            locals_signature_token,
        ),
        instructions,
        sections: vec![],
    };

    let method_bytes = method.into_bytes();
    let allocated_bytes = allocator.alloc(method_bytes.len() as ULONG).map_err(|e| {
        log::warn!("generate_void_il_startup_method: failed to allocate memory for __ElasticVoidMethodCall__");
        e
    })?;

    let address = unsafe { allocated_bytes.into_inner() };
    unsafe {
        std::ptr::copy(method_bytes.as_ptr(), address, method_bytes.len());
    }
    log::trace!("generate_void_il_startup_method: write __ElasticVoidMethodCall__ body");
    let generate_startup_body_result =
        profiler_info.set_il_function_body(module_id, new_method, address as *const _);

    match generate_startup_body_result {
        Err(e) => log::warn!(
            "generate_void_il_startup_method: failed to set il for __ElasticVoidMethodCall__"
        ),
        Ok(r) => {
            log::info!( "generate_void_il_startup_method: sucessfully injected Elastic.Apm.Profiler.Managed.Loader.Startup");
        }
    }

    Ok(new_method)
}