Explore CLR World with Windbg [8] InternalCall

xiaoxiao2021-03-06  106

Original: http://www.blogcn.com/User8/flier_lu/index.html? ID = 3270482

When you use the Reflector.net or the ROTOR source code to view the implementation of the BCL library, some methods that are labeled as INTERNALL are often encountered. If system.string is used to get the length attribute of the string length, the implementation is the String.InternalLength method that is marked as INTERNALL:

The following is a program code: namespace System {[Serializable] public sealed class String: ... {[MethodImpl (MethodImplOptions.InternalCall)] private int InternalLength (); public int Length {get {return this.InternalLength ();}} }

These methods provide implementation code by performing efficiency, security or different reasons such as simplicity, etc., through the Native Code other than IL code. However, it is different from the inter gage method defined by the DLLIMPORT. They do not need to be defined as the Static Extern method, and there is no need to be implemented by a separate DLL export function. As one of the many internal call modes of the CLR, they are encapsulated in a box that seems to be unsatisfactory, and the function final user is separated from the function function provider through an interNalCall function.

However, in practice, in order to analyze CLR operating mechanisms and debugging, we often need to understand and analyze such functions. Below will be used internally from the CLR to achieve different angles of the INTERNALCALL function to make a rough analysis.

As a BCL function, the function defined as an internalCall is used without any difference with the normal IL function. Like I

"Using Windbg Explore the JIT Process of the CLR World [3] Tracking Method, they are in MethodTable, the initial entry address is also pointed to Mscorwks! Prestubworker, you can view it through SOS:

The following is quoted:

0: 003>! Name2ee mscorlib.dll system.string

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

MethodTable: 79b7daf8

EECLASS: 79B7DE44

Name: system.string

0: 003>! Dumpmt -md 79b7daf8

EECLASS: 79B7DE44

Module: 79b66000

Name: system.string

MDTOKEN: 0200000F (E: WindowsMicrosoft.Net Rameworkv1.1.4322mscorlib.dll)

MethodTable Flags: 2000000

Number of elements in Array: 2

Number of iFaces in iFacemap: 4

Interface Map: 79b7de24

Slots in vTable: 190

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

MethodDesc TABLE

Entry MethodDesc Jit Name

799917C0 79B7EBC8 prejit [default] [hasthis] String system.string.toString ()

...

79b7e253 79b7e258 none [default] [hasthis] i4 system.string.internalLength () ...

0: 003>! Dumpmd 79b7e258

Method name: [default] [hasthis] i4 system.string.internalLength ()

MethodTable 79B7DAF8

Module: 79b66000

MDTOKEN: 060000B1 (E: WindowsMicrosoft.Net Rameworkv1.1.4322mscorlib.dll)

Flags: 1

IL RVA: 0073000B

Through the above command, we can see that the String.internalLength method defaults without JIT compilation, and its entry address is 79b7E253. Anti-assembly of this address of this address, and you can find it all the way, it is actually finally calling mscorwks! PrestubWorker method:

The following is quoted:

0: 003> u 79b7e253

Mscorlib_79980000 0x1FE253:

79B7E253 E8287ffeff Call Mscorlib_79980000 0x1e6180 (79b66180)

79b7e258 4D Dec EBP

...

Mscorlib_79980000 0x1e6180:

79b66180 e9eb805e86 jmp 0014e270

79B66185 0000 Add [EAX], Al

...

0: 003> U 0014E270

0014E270 52 PUSH EDX

...

0014E290 56 PUSH ESI

0014E291 E8B4870879 CALL MSCORWKS! PRESTUBWORKER (791D6A4A)

0014E296 897B08 MOV [EBX 0x8], EDI

...

This PRESTUBWORKER function (VM / PRESTUB.CP: 574) can be said to be all IL functions for jit, responsible for compiling the IL code to generate a Native code and install the JIT generated code entry to the corresponding MD (METHODESC):

The following program code is: extern "C" const BYTE * __stdcall PreStubWorker (PrestubMethodFrame * pPFrame) {MethodDesc * pMD = pPFrame-> GetFunction (); MethodTable * pDispatchingMT = NULL; if (pMD-> IsVtableMethod () && pMD-! > GetClass () -> IsValueClass ()) {oBJECTREF curobj = GetActiveObject (pPFrame); if (curobj = 0) pDispatchingMT = curobj-> GetMethodTable ();!} return pMD-> DoPrestub (pDispatchingMT);}

The parameter of the PRESTUBWORKER function is a method frame that can get the MD of the current function, and then call the DopResub function of this method to complete the actual work. In the MethodDesc: Oprestub method (VM / PRESTUB.CP: 590), when actual code generation is performed, various special circumstances are performed according to the type of method:

The following is the program code: const byte * methoddesc: [img] /images/bigrin.gif [/ img] Oprestub (MethodTable * pdispatchingmt) {stub * pstub = null; // ... if (Isspecialstub ()) {/ / ...} else if (isil ()) {// ...} else ///! Isspecialstub () &&! Isil () Case {if (ISECALL ()) {// see if it is an an fcall and alreadyady "jitted", which for fcall // means that its m_CodeOrIL is not already set. We explicitly // check for the mcECall bit since IsECall is really // IsRuntimeGenerated and so includes array also if (IsJitted () && (mcECall == GetClassification ()) PSTUB = (stub *) getaddrofjittedcode (); else pstub = (stub *) FindimplFormethod (this);} if (pstub! = 0) {_asserte (ISECALL () ||! () -> IsAnyDelegateClass ()); If (!! "() -> isanydElegateClass ()) {// backpatch the main slot. Pmt-> getVTable () [getSlot ()] = (slot) Pstub;} bbashcall = Biscode = True; Else {// ...}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}: ieSecall () {return mcecall == getClassification () || mcarray == getClassification ()

This is actually judged by getClassification () acquisition method type by getClassification () acquisition method. And this method type is the compiler to write to Metadata at compiler according to MethodImplattribute, etc. For MethodImploptions.InternalCall, this is actually corresponding to this type of MCECALL. Other CLR internal call types, there will be a chance to introduce again.

For getClassification () Returns MCECALL this, it is actually done by the FINDIMPLMMETHOD () function. In the case of RVA 0, this function calls FINDECFUNCFORMETHOD to find the implementation code of INTERNALL in a global ECALL registry. The following is program code: void * findImplforMethod (MethodDesc * pmdofcall) {dword_ptr rva = pmdofcall-> getrva (); // ... if (rva == 0) {RET = FINDECFUNCFORMETHOD (PBASEMD);} // .. }

However, with the implementation of Rotor is that .NET Framework 1.1 has made a lot of additional optimization work for efficiency. As shown in the previous dumpmd command, the method of INTERNALL in CLR V1.1 is also RVA, just what they point to the IL instruction that directly returns. FindimplFormethod's processing method for ECALL types, but also because RVA is not 0, and from each call, the FINDECFUNCFORMETHOD function matches the lookup in the global ECALL registry, changed to the Mscorwks! ECALL :: EmiteCallMetHodstub () method, Generate a calling STUB code for ECALL implementation code. In this way, only the ECALL implementation code positioning of the string matching property when the ECALL code is called for the first time, and it can be called for all the JIT code.

You can track initialization work of each interNalCall type function by following the FindimplFormethod method, the invocation initialization work is initialized, such as:

The following is quoted:

0: 000> bp mscorwks! FINDIMPLFORMETHOD

0: 000> g

Breakpoint 0 Hit

EAX = 00000001 EBX = 00000001 ECX = 79ba9e68 EDX = C0000000 ESI = 79ba9E68 EDI = 00000000

EIP = 791D8D5B ESP = 0012F084 EBP = 0012F158 IOPL = 0 NV UP EI PL NZ NA PE NC

CS = 001B SS = 0023 DS = 0023 ES = 0023 fs = 0038 GS = 0000 EFL = 00000202

Mscorwks! FindImplforMethod:

791D8D5B 55 PUSH EBP

0: 000>! Dumpmd ECX

Method name: [Default] void system.Runtime.Interopservices.Marshal.copy (SzaRray Char, I4, I4, I4)

MethodTable 79ba916c

Module: 79b66000

MDToken: 060020D3 (E: WindowsMicrosoft.Net Rameworkv1.1.4322mscorlib.dll)

Flags: 11

IL rva: 00460008

You can know which function is currently called by the Dumpmd command to see the MethodDesc * PMDOFCALL parameter that the ECX register. After the FINDIMPLFORMETHOD function is running, then the method of just being processed will find that IL RVA: 00460008 has become Method VA: 7921B264, and this entrance address is just the Marshal, which is just in the overall ECALL table. .Copy correspondence code COPYTONATIVE. The following is quoted:

0: 000>! Dumpmd 79ba9e68

Method name: [Default] void system.Runtime.Interopservices.Marshal.copy (SzaRray Char, I4, I4, I4)

MethodTable 79ba916c

Module: 79b66000

MDToken: 060020D3 (E: WindowsMicrosoft.Net Rameworkv1.1.4322mscorlib.dll)

Flags: 11

Method VA: 7921B264

0: 000> u 7921b264

Mscorwks! CopytonATIVE:

7921B264 55 PUSH EBP

...

After all, Rotor is just a reference implementation of a non-commercial use, and it is impossible to delete optimized code to reduce program complexity.

In the understanding of the INTERNALL method, how to be dynamically called in Rotor, and after the .NET Framework 1.1 is dynamically parsed, let's take a look at these calls and parsing, how to position the method of implementing the function entry.

The FINDECFUNCFORMETHOD function (VM / ECALL.CPP: 1886) in Rotor is the most explained:

The following is quoted:

Static ECFUNC * FINDECFUNCFORMETHOD (METHODDESC * PMD)

{

// check the cache

ECFUNC ** Cachentry = GetCachentry (PMD);

ECFUNC * CUR = * cacheentry;

IF (Cur! = 0 && Cur-> m_pmd == PMD)

Return (Cur);

Cur = FINDIMPLSFORCLASS (PMD-> getMethodTable ());

IF (Cur == 0)

Return (0);

Cur = GetecForIndex (PMD, Cur), CUR)

IF (Cur == 0)

Return (0);

* cachentry = cur; // put in the cache

Return Cur;

}

Static ECFUNC * GETECFORINDEX (Ushort Index, ECFUNC * IMPLS)

{

IF (index == (ushort) -1)

Return NULL;

Else

Return Impls Index;

}

ECFUNC * FINDIMPLSFORCLORCLASS (MethodTable * PMT)

{

Return GetImplsForIndex (PMT)) and PETIMPLSINDEXFORCLASS

}

ECFUNC * GetImplsForindex (Ushort Index)

{

IF (index == (ushort) -1) Return NULL;

Else

Return gecclasses [index] .m_pecfunc;

}

The FINDECFUNCFORMETHOD function first tries to get the method specified by the PMD parameter from the cache. If there is no cache, the FindImplSindexForClass function and the FINDECINDEXFORMETHOD function are called, respectively, from the classes and methods, respectively, find the appropriate ECALL implementation. To understand the implementation of these two functions, you must first see how the global ECALL mapping table is defined:

The following is quoted:

Struct ECFUNC {

Bool isfcall () {return true;}

Corinfointrinsics Intrinsicid () {Return CorinfoIntrinsics (M_InTrinsici);

LPCUTF8 m_WSZMETHODNAME;

LPHARDCODEDMETASIG M_WSZMETHODSIG;

LPVOID M_PIMPLEMEMENTATION;

MethodDesc * m_pmd; // for reverse mapping

INT M_INTRINSICID: 8;

ECFUNC * m_pnext; // linked list for hash table

}

Static ECFUNC GSTRINGFUNCS [] =

{

...

{FCFuncelement ("GetHashcode", NULL, (LPVOID) COMSTRING :: GetHashcode)},

{Fcintrinsic ("InternalLength", NULL, (LPVOID) COMSTRING :: Length, ca Info_intrinsic_stringlength},

...

}

First, for each class's INTERCALL method, you need to define a global array of ECFUNC types in ECALL.CPP, and each row corresponds to a function. The content includes the name of the function, Signature, implement function portfolio, etc.

The following is quoted:

Struct Ecclass

{

LPCUTF8 M_WSZCLASSNAME;

LPCUTF8 m_WSZNAMESPACE;

ECFUNC * m_pecfunc;

}

Static ECCLASS gecclasses [] =

{

...

{EcClasselement ("String", "System", GStringFuncs},

...

}

Then, for each type with the INTERNALL method, you need to add an expression in a global ECALL registry. Each item includes a name, namespace, and function mapping table. These mapping tables are predefined when compiling, and then compile them into the program. Although this symbol is not exported in the release, we can find this mapping table in the runtime environment through the implementation code of the GetImplsForIndex function:

The following is quoted:

0: 003> U Mscorwks! GetImplsForIndex

Mscorwks! GetImplsForIndex:

791D711F 55 Push EBP

791D7120 8bec MOV EBP, ESP

791D7122 66837D08FF CMP Word PTR [EBP 0x8], 0xffff791d7127 0f840eb30200 Je Mscorwks! GetImplsForIndex 0xB (7920243B)

791D712D 0FB74508 MOVZX EAX, Word PTR [EBP 0x8]

791D7131 8D0440 Lea Eax, [EAX EAX * 2] // Index * = 3

791D7134 8B048548D13D79 MOV EAX, [Mscorwks! GSTRINGBUFFERFUNCS 0X5CD8 (793DD148) EAX * 4]

791D713B 5D POP EBP

791D713C C20400 RET 0x4

791D713F 33C0 XOR EAX, EAX

791D7141 40 Inc EAX

791D7142 5E POP ESI

791D7143 C3 RET

0: 008> DD 793DD140

793DD140 791D7C40 791D71C4 793E0420 791D7C30

793DD150 791D71C4 793E03F0 791D86F8 791D71C4

...

0: 008> DB 791D7C40

791D7C40 41 70 70 44 6F 6D 61 69-6e 00 90 90 4C 6F 67 00 Appdomain ... log.

...

0: 008> DB 791D71C4

791D71C4 53 79 73 74 65 6D 00 90-5F 5F 4B 65 79 48 61 6e system ..__ keyhan

GetImplsForIndex function statements gECClasses [index] .m_pECFunc is compiled into ADDRESS_OF (gECClasses) index * sizeof (ECClass) OFFSET_OF (ECClass, m_pECFunc), exactly corresponds to mscorwks! GetImplsForIndex function in two lines, the reverse analysis The post 793DD140 address is gecclasses. And the view of this address is proved.

The following is quoted:

0: 008> DD 793E0420

793E0420 7923DB2C 00000000 7933957F 00000000

793E0430 000000FF 00000000 7923DB18 00000000

...

0: 008> DB 7923DB2C

7923DB2C 43 72 65 61 74 65 42 61-73 69 63 44 6F 6D 61 69 CREATEBASICDOMAI

7923DB3C 6E 00 8D 41 14 C3 55 8B-EC 51 51 53 56 57 8B 7D N..a..u..QQSVW.

...

0: 008> u 7933957f

Mscorwks! AppdomainNative :: CreateBasicdomain:

7933957F 55 Push EBP

Method for further reviewing System.Appdomain is also the same as we expected.

After knowing the global mapping table of ECALL, it is easy to understand the FindImplsindexForClass function and the FindecIndexFormethod function. The former traverses the gecclasses table, finds the same function mapping table entry with the MethodTable point name and namespace; the latter traverses the function mapping table to find the same implementation function in the function name. At this point, the use and implementation of the INTERNALCALL type function is about it. By understanding this internal principles, we can easily implement some underlying functions that cannot be implemented on the IL level.

转载请注明原文地址:https://www.9cbs.com/read-124463.html

New Post(0)