1.前言
.Net8里面的字符串何时被初始化的,全局变量内存模型怎么样的呢?本篇看下
2.概述
一:例子
先上例子
static void Main(string[] args)
{
string str = "abcdefg";
}
看下它的汇编
string str = "abcdefg";
00007FFE40B506DB 48 B9 68 20 40 1A D6 01 00 00 mov rcx,1D61A402068h
00007FFE40B506E5 48 8B 09 mov rcx,qword ptr [rcx]
00007FFE40B506E8 48 89 4D 38 mov qword ptr [rbp+38h],rcx
它这里直接是str实例的MethodTable指针的指针,也即rcx。它这指向指针的指针是哪里来的呢?
二:全局字符串map表
实际上,在托管Main入口之前,
System.Private.CoreLib.dll里面的函数
System.AppContext.setup。对这些字符串进行了实例化。填充MethodTable。同时它拥有一个全局字符串表:m_pGlobalStringLiteralMap
STRINGREF *StringLiteralMap::GetStringLiteral(EEStringData *pStringData, BOOL bAddIfNotFound, BOOL bIsCollectible, void** ppPinnedString)
{
StringLiteralEntryHolder pEntry(SystemDomain::GetGlobalStringLiteralMap()->GetStringLiteral(pStringData, dwHash, bAddIfNotFound, preferFrozenObjectHeap));
}
GetStringLiteral调用如下
StringLiteralEntry *GlobalStringLiteralMap::GetStringLiteral(EEStringData *pStringData, DWORD dwHash, BOOL bAddIfNotFound, BOOL bPreferFrozenObjectHeap)
{
pEntry = AddStringLiteral(pStringData, bPreferFrozenObjectHeap);
}
AddStringLiteral如下
StringLiteralEntry *GlobalStringLiteralMap::AddStringLiteral(EEStringData *pStringData, bool preferFrozenObjHeap)
{
STRINGREF strObj = AllocateStringObject(pStringData, preferFrozenObjHeap, &isFrozen);
}
他这个里面进行了AllocateStringObject,也就是分配字符串和填充MethodTable。那么字符串实际上就是在这里被实例化的。
三:如何赋值给JIT
字符串在托管Main之前被填充了,JIT操作IL的时候需要这个填充的数据。它是怎么获取的呢?
InfoAccessType iat =
info.compCompHnd->constructStringLiteral(tree->AsStrCon()->gtScpHnd, tree->AsStrCon()->gtSconCPX, &pValue);
以上这段代码 info.compCompHnd里面附带的信息足够填充JIT操作IL进行汇编编译的时候给前面值给它赋值上。
四:变体全局变量内存模型
给上面的示例代码变下,假如说变成如下:
public static readonly List<int> list = new List<int>() { 1, 2, 3, 4 };
static void Main(string[] args)
{
}
那么这个list的内存模型应该如何追踪呢?实际上,Main里面没有任何汇编代码,所以无法查看蛛丝马迹。所以这里可以把list实例在Main里面加一行一代码,让其汇编显示,就可以跟踪了。如下:
public static readonly List<int> list = new List<int>() { 1, 2, 3, 4 };
static void Main(string[] args)
{
list.Add(5);
}
看下它的汇编
00007FFDF4D606B0 55 push rbp
00007FFDF4D606B1 57 push rdi
00007FFDF4D606B2 56 push rsi
00007FFDF4D606B3 48 83 EC 30 sub rsp,30h
00007FFDF4D606B7 48 8B EC mov rbp,rsp
00007FFDF4D606BA 33 C0 xor eax,eax
00007FFDF4D606BC 48 89 45 28 mov qword ptr [rbp+28h],rax
00007FFDF4D606C0 48 89 4D 50 mov qword ptr [rbp+50h],rcx
00007FFDF4D606C4 83 3D DD C8 0B 00 00 cmp dword ptr [7FFDF4E1CFA8h],0
00007FFDF4D606CB 74 05 je Test_.Program.Main(System.String[])+022h (07FFDF4D606D2h)
00007FFDF4D606CD E8 3E 73 C3 5F call 00007FFE54997A10
00007FFDF4D606D2 90 nop
00007FFDF4D606D3 48 B9 00 D1 E1 F4 FD 7F 00 00 mov rcx,7FFDF4E1D100h
最后一行rcx里是静态存放的地址,里面存放着List,int32类型。它的内存如下:
0x00007FFDF4E1D100: 000001cdccc65bc0 0000000000000000 0000000000000000 0000000000000000 000001cdcec01ec0
第五个八字节000001cdcec01ec0如下:
0x000001CDCEC01EC0: 000001cdd140a598 0000000000000000 0000000000000000
第一个八字节000001cdd140a598如下:
0x000001CDD140A598: 00007ffdf4e53df0 000001cdd140a5f8 0000000500000005
它就是list的对象地址,第一个八字节00007ffdf4e53df0是list的MethodTable,第二个八字节000001cdd140a5f8是Int32的MethodTable,第三个八字节0000000500000005第一个00000005代表例子里面的list.add(5)添加的那个5,第二个00000005代表的是list里面总共有5个元素。
继续看第二个八字节00007ffdf4e53df0
0x000001CDD140A5F8 00007ffdf4d4bf38 0000000000000008 0000000200000001 0000000400000003 0000000000000005
可以看到list里的总共分配了8个int32长度的位置,后面跟着{1, 2, 3, 4,5}。