locked
64-bit instrumentation throws exception when 32-bit doesn't.

    Question

  • I am rewriting System.AppDomain.OnAssemblyResolveEvent to add essentially this at the start of the method:

    if (assemblyFullName.StartsWith("MyAssemblyName,"))
    {
        return AppDomain.GetRuntimeAssembly(Assembly.LoadFrom(@"Full\Path\To\MyAssemblyName.dll"));
    }

    The IL for that looks like this:

    IL_0000: ldarg.2 
    IL_0001: ldstr STRING 70036898
    IL_0006: call TOKEN a000918
    IL_000b: brfalse.s IL_001d
    IL_000d: ldstr STRING 700368c2
    IL_0012: call TOKEN a000919
    IL_0017: call TOKEN a00091a
    IL_001c: ret 
    IL_001d: nop 

    The string tokens are added to the metadata through IMetaDataEmit::DefineUserString and the method tokens come from IMetaDataEmit::DefineMemberRef.  I do not change the method header except for changing the code size to match the new IL code byte count (the default header has a max stack size of 4 and is FAT which is more than I need for my code).

    When I compile my profiler as 32-bit and build my C# application that is being profiled as 32-bit only this all works.  System.AppDomain.OnAssemblyResolveEvent is instrumented and it loads my assembly from the path I supply.  However, when I compile the profiler as 64-bit and my C# application is set to 64-bit only, when the method is JIT compiled I get the following exception:

    System.MissingMethodException: Method not found: 'System.Runtime.InteropServices.ICustomQueryInterface System.Reflection.Assembly.LoadFrom(System.String)'

    When I look at the method token I get back from DefineMemberRef when running in 64-bit it matches the method token I get in the 32-bit build.  This suggests to me that the error isn't with retrieving the method nor is it a problem with method signatures in 64-bit vs 32-bit.

    If I comment out the call to LoadFrom I get the same exception but with different details:

    'System.MissingMethodException': Method not found: 'System.Reflection.AssemblyCopyrightAttribute System.AppDomain.GetRuntimeAssembly(System.Runtime.InteropServices.ICustomQueryInterface)'

    Interestingly, the call to String.StartsWith seems to work without any problems in 64-bit mode.  The most obvious difference between that and the other two is that the other two are static methods while StartsWith is an instance method.  Also, in the 32-bit version (64-bit is harder to debug since 64-bit SOS doesn't work in Visual Studio) I notice that the method token for AppDomain.GetRuntimeAssembly that is in the originial method's IL is different than the method token that I am getting for AppDomain.GetRuntimeAssembly.  I am assuming this is because I am getting an unscoped method token whereas the original method is using a scoped method.

    Last bit of potentially useful information, looking at the byte array (in memory) I pass to SetILFunctionBody in both the 32-bit and 64-bit versions there are some slight differences in tokens, but the string tokens and not the method tokens:
    64-bit:
    04 /*ldarg.0*/
    72 /*ldstr*/ 60 68 03 70
    28 /*call*/ 18 09 00 0a
    2c /*brfalse.s*/ 10
    72 /*ldstr*/ 8a 68 03 70
    28 /*call*/ 19 09 00 0a
    28 /*call*/ 1a 09 00 0a
    2a /*ret*/

    32-bit:
    04 /*ldarg.0*/
    72 /*ldstr*/ 98 68 03 70
    28 /*call*/ 18 09 00 0a
    2c /*brfalse.s*/ 10
    72 /*ldstr*/ c2 68 03 70
    28 /*call*/ 19 09 00 0a
    28 /*call*/ 1a 09 00 0a
    2a /*ret*/


    • Edited by Micah Zoltu Friday, December 07, 2012 11:48 PM
    Friday, December 07, 2012 11:34 PM

All replies

  • I tried changing the tokens that the two failing CALL instructions were getting passed to scoped tokens.  I grabbed the token value from ILDASM running on mscorlib and one of the tokens matched the token being called by the same method later on (GetRuntimeAssembly).  Once again, I built for 32-bit and the application works flawlessly.

    When I build for 64-bit however I receive a new exception: System.InvalidProgramException

    I have checked the bytecode being written by the 32-bit and 64-bit versions of my application and both of them are passing mostly the same set of bytes to CorProfilerInfo::SetILFunctionBody, allocated by GetILFunctionBodyAllocator.  The one noticeable difference I saw was the string tokens I am getting point to different places, but this doesn't surprise me since I am pointing at a new thing that I created.

    In all cases SetILFunctionBody returns S_OK.


    • Edited by Micah Zoltu Saturday, December 08, 2012 2:25 AM
    Saturday, December 08, 2012 2:24 AM
  • Could this be a memory alignment issue?  I am allocating enough space in bytes but not guaranteeing that the allocation is DWORD/QWORD aligned nor am I doing any such alignment elsewhere.
    Saturday, December 08, 2012 2:30 AM
  • Hi Micah,

    Welcome to the MSDN Forum.

    Would you like to upload a whole test project here?

    (You can upload a project on skydrive or any other network storage, and post the download link here)

    Thank you very much.

    Best regards,


    Mike Feng
    MSDN Community Support | Feedback to us
    Develop and promote your apps in Windows Store
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Monday, December 10, 2012 12:03 PM
    Moderator
  • Hi, Micah.

    This is complicated enough that we'd probably need a reproducing project or at least a dump to really understand much of what's going on.  But I can give you a couple pieces of info:

    1) Just in case you're doing this... never hard-code token values in your profiler.  If you need to rewrite IL to call methods, always look up those methods by name every time you run, convert to tokens, and then use those tokens in your rewritten IL.  (Use ModuleLoadFinished as a convenient time to look up tokens by name, store the tokens somewhere, and then use those tokens in the IL you pass to SetILFunctionBody later on.)

    2) Profilers adding strings to metadata at run-time is not well tested.  I don't know how well it works, or if it causes other kinds of problems.  You might try using existing string tokens (rather than adding new ones) just to see if that has an effect, to try to narrow down the problem.

    Thanks,
    Dave

    Thursday, December 20, 2012 5:16 PM
    Owner
  • I currently look up method tokens by name and add strings to the string pool.  I have been temporarily re-tasked away from this project for a couple weeks but once I am back on it I will try using existing strings as a test.

    I will also see if I can distill out a simple test case that doesn't include any proprietary code.  Right now my test case is a short circuit at the start of our larger instrumentation code base so it shouldn't be very difficult to extract into its own project.

    Thursday, December 20, 2012 5:40 PM