.NET8顶级调试lldb观察FOH堆字符串分配
前言
好久没有动用LLDB了,这种未来的下一代高性能调试器应该是用在Linux内核系统的Arm64/Riscv64/X64系统指令集上的,LLDB Debug .NET有点杀鸡用牛刀。本篇通过它来看下FOH也就是.NET8里面优化字符串,为了提高其性能增加的FOH堆分配过程。关于FOH可以参考:.NET8极致性能优化Non-GC Heap
详细
来看一个简单的例子:
public static string GetPrefix() => "https://";
static void Main(string[] args)
{
GetPrefix();
GC.Collect();
Console.ReadLine();
}
函数GetPrefix里面的字符串“https://”就是被分配到FOH堆里面的,如何验证呢?
首先通过LLDB把CLR运行到托管Main入口
(lldb) b RunMainInternal
Breakpoint 7: where = libcoreclr.so`RunMainInternal(Param*) at assembly.cpp:1257, address = 0x00007ffff6d43930
(lldb) r
Process 2697 launched: '/home/tang/opt/dotnet/debug_clr/clrrun' (x86_64)
Process 2697 stopped
* thread #1, name = 'clrrun', stop reason = breakpoint 6.1 7.1
frame #0: 0x00007ffff6d43930 libcoreclr.so`RunMainInternal(pParam=0x00007ffff7faaab6) at assembly.cpp:1257
1254 } param;
1255
1256 static void RunMainInternal(Param* pParam)
-> 1257 {
1258 MethodDescCallSite threadStart(pParam->pFD);
1259
1260 PTRARRAYREF StrArgArray = NULL;
(lldb)
然后把其运行到JIT前置入口
(lldb) b PreStubWorker
Breakpoint 8: where = libcoreclr.so`::PreStubWorker(TransitionBlock *, MethodDesc *) at prestub.cpp:1865, address = 0x00007ffff6ee6c10
(lldb) c
Process 2697 resuming
Process 2697 stopped
* thread #1, name = 'clrrun', stop reason = breakpoint 8.1
frame #0: 0x00007ffff6ee6c10 libcoreclr.so`::PreStubWorker(pTransitionBlock=0x00000000ffffcb38, pMD=0x0000000155608c70) at prestub.cpp:1865
1862 // returns a pointer to the new code for the prestub's convenience.
1863 //=============================================================================
1864 extern "C" PCODE STDCALL PreStubWorker(TransitionBlock* pTransitionBlock, MethodDesc* pMD)
-> 1865 {
1866 PCODE pbRetVal = NULL;
1867
1868 BEGIN_PRESERVE_LAST_ERROR;
此时可以看下当前JIT编译的函数是谁,这里需要先n命令单步一下
(lldb) n
Process 2697 stopped
* thread #1, name = 'clrrun', stop reason = step over
frame #0: 0x00007ffff6ee6c36 libcoreclr.so`::PreStubWorker(pTransitionBlock=0x00007fffffffc648, pMD=0x00007fff78f56b70) at prestub.cpp:1866:11
1863 //=============================================================================
1864 extern "C" PCODE STDCALL PreStubWorker(TransitionBlock* pTransitionBlock, MethodDesc* pMD)
1865 {
-> 1866 PCODE pbRetVal = NULL;
1867
1868 BEGIN_PRESERVE_LAST_ERROR;
然后通过微软提供的sos.dll Dump下当前的函数描述结构体MethodDesc,pMD是传过来的函数参数,也即是MethodDesc的变量
(lldb) sos dumpmd pMD
Method Name: ConsoleApp1.Test+Program.Main(System.String[])
Class: 00007fff78f97530
MethodTable: 00007fff78f56c08
mdToken: 0000000006000008
Module: 00007fff78f542d0
IsJitted: no
Current CodeAddr: ffffffffffffffff
Version History:
ILCodeVersion: 0000000000000000
ReJIT ID: 0
IL Addr: 00007ffff7faa2aa
CodeAddr: 0000000000000000 (MinOptJitted)
NativeCodeVersion: 0000000000000000
可以清晰的看到Method Name就是ConsoleApp1.Test+Program.Main,OK这一步确定了,我们下面继续寻找字符串分配到FOH,首先删掉前面所有的断点
(lldb) br del
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (3 breakpoints)
在TryAllocateObject 函数上下断,它是分配托管内存的函数
(lldb) b TryAllocateObject
Breakpoint 9: 2 locations.
运行到此处
(lldb) c
Process 2697 resuming
Process 2697 stopped
* thread #1, name = 'clrrun', stop reason = breakpoint 9.1
frame #0: 0x00007ffff70300c0 libcoreclr.so`FrozenObjectHeapManager::TryAllocateObject(this=0x00007fffffff80b8, type=0x00000008017fa948, objectSize=140737488322759, publish=false) at frozenobjectheap.cpp:22
19 // May return nullptr if object is too large (larger than FOH_COMMIT_SIZE)
20 // in such cases caller is responsible to find a more appropriate heap to allocate it
21 Object* FrozenObjectHeapManager::TryAllocateObject(PTR_MethodTable type, size_t objectSize, bool publish)
-> 22 {
23 CONTRACTL
24 {
25 THROWS;
这个函数就是字符串分配到FOH堆的地方,通过它的函数所在的类名即可看出FrozenObjectHeapManager,但是我们依然还是需要验证下。继续n单步这个函数的返回的地方,也就是如下代码:
202
-> 203 return object;
此时的这个object变量就是示例里面字符串的“https://”的对象地址,看下它的地址值
(lldb) p/x object
(Object *) $14 = 0x00007fffe6bff8c0
记住这个值:0x00007fffe6bff8c0,后面会把它和GC堆的范围进行一个比较。如果它不在GC堆范围,说明.NET8的字符串确实分配在了FOH堆里面。
我们继续单步向下,运行到这个对象被赋值字符串的地方
STRINGREF AllocateStringObject(EEStringData *pStringData, bool preferFrozenObjHeap, bool* pIsFrozen)
{
//此处省略
memcpyNoGCRefs(strDest, pStringData->GetStringBuffer(), cCount*sizeof(WCHAR));
//此处省略
}
然后看下它的内存:
(lldb) memory re 0x00007fffe6bff8c0
0x7fffe6bff8c0: 68 00 74 00 74 00 70 00 73 00 3a 00 2f 00 2f 00 h.t.t.p.s.:././.
0x7fffe6bff8dc: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
它确实是https字符串的对象地址。没有问题。
避免干扰,此时我们再次删除所有断点
(lldb) br del
About to delete all breakpoints, do you want to do that?: [Y/n] y
All breakpoints removed. (1 breakpoint)
然后在函数is_in_find_object_range处下断,它是在GC回收的时候,判断当前的对象地址是否在GC堆里面,如果是则进行对象标记,如果不是直接返回。可以通过这个获取GC堆的范围,运行到此处
(lldb) b is_in_find_object_range
(lldb) c
Process 2697 resuming
Process 2697 stopped
* thread #1, name = 'clrrun', stop reason = breakpoint 13.8
frame #0: 0x00007ffff72d881d libcoreclr.so`WKS::GCHeap::Promote(Object**, ScanContext*, unsigned int) [inlined] WKS::gc_heap::is_in_find_object_range(o=0x0000000000000000) at gc.cpp:7906:11
7903 inline
7904 bool gc_heap::is_in_find_object_range (uint8_t* o)
7905 {
-> 7906 if (o == nullptr)
7907 {
7908 return false;
7909 }
单步n
(lldb) n
Process 2697 stopped
* thread #1, name = 'clrrun', stop reason = step over
frame #0: 0x00007ffff72d8831 libcoreclr.so`WKS::GCHeap::Promote(Object**, ScanContext*, unsigned int) [inlined] WKS::gc_heap::is_in_find_object_range(o="@\x9b\xd6x\xff\U0000007f") at gc.cpp:7911:14
7908 return false;
7909 }
7910 #if defined(USE_REGIONS) && defined(FEATURE_CONSERVATIVE_GC)
-> 7911 return ((o >= g_gc_lowest_address) && (o < bookkeeping_covered_committed));
7912 #else //USE_REGIONS && FEATURE_CONSERVATIVE_GC
7913 if ((o >= g_gc_lowest_address) && (o < g_gc_highest_address))
7914 {
注意,此时我们看到了GC堆的一个范围,也就是变量g_gc_lowest_address和变量g_gc_highest_address,看下它们的地址范围
(lldb) p/x g_gc_lowest_address
(uint8_t *) $17 = 0x00007fbf68000000 ""
(lldb) p/x g_gc_highest_address
(uint8_t *) $18 = 0x00007fff68000000 "0"
上面很明显了,GC堆的范围起始地址:0x00007fbf68000000 ,结束地址:0x00007fff68000000 。而字符串“https://”的对象地址是0x00007fffe6bff8c0,很明显它不在GC堆的范围内。
以上通过分配一个字符串到FOH堆,后调用一个GC.Collect()查看GC堆的范围,对FOH对象地址和GC堆范围进行一个判断,为一个非常简单的FOH字符串分配验证。
欢迎加入C#12/.NET8最新技术交流群
结尾
作者:jianghupt
原文:.NET8顶级调试lldb观察FOH堆字符串分配
公众号:jianghupt,文章首发,欢迎关注
.NET8顶级调试lldb观察FOH堆字符串分配的更多相关文章
- windows程序员进阶系列:《软件调试》之Win32堆
win32堆及内部结构 Windows在创建一个新的进程时会为该进程创建第一个堆,被称为进程的默认堆.默认堆的句柄会被保存在进程环境块_PEB的ProcessHeap字段中. 要获得_PEB的地址, ...
- windows程序员进阶系列:《软件调试》之Win32堆的调试支持
Win32堆的调试支持 为了帮助程序员及时发现堆中的问题,堆管理器提供了以下功能来辅助调试. 1:堆尾检查(Heap Tail Check) HTC,在堆尾添加额外的标记信息,用于检测堆块是否溢出. ...
- java字符串池和字符串堆内存分配
1. String str=new String("abc")和String str="abc"的字符串“abc”都是存放在堆中,而不是存在 栈中. 2. 其实 ...
- Java中的对象都是在堆上分配的吗?
作者:LittleMagic https://www.jianshu.com/p/8377e09971b8 为了防止歧义,可以换个说法: Java对象实例和数组元素都是在堆上分配内存的吗? 答:不一定 ...
- 如何限制一个类只在堆上分配和栈上分配(StackOnly HeapOnly)
[本文链接] http://www.cnblogs.com/hellogiser/p/stackonly-heaponly.html [题目] 如何限制一个类只在堆上分配和栈上分配? [代码] C+ ...
- Unix系统编程()在堆上分配内存
在堆上分配内存:malloc和free 一般情况下,C程序使用malloc函数族在堆上分配和释放内存.较之brk和sbrk,这些函数具备不少优点: 属于C语言标准的一部分 更易于在多线程程序中使用 接 ...
- JVM系列(1)- JVM常见参数及堆内存分配
常见参数配置 基于JDK1.6 -XX:+PrintGC 每次触发GC的时候打印相关日志 -XX:+UseSerialGC 串行回收模式 -XX:+PrintGCDetails 打印更详细的GC日志 ...
- new 的对象如何不分配在堆而分配在栈上(方法逃逸等)
当能够明确对象不会发生逃逸时,就可以对这个对象做一个优化,不将其分配到堆上,而是直接分配到栈上,这样在方法结束时,这个对象就会随着方法的出栈而销毁,这样就可以减少垃圾回收的压力. 如方法逃逸. 逃逸分 ...
- Java中对象并不是都在堆上分配内存的
转(https://blog.51cto.com/13906751/2153924) 前段时间,给星球的球友们专门码了一篇文章<深入分析Java的编译原理>,其中深入的介绍了Java中的j ...
- 【性能优化】面试官:Java中的对象都是在堆上分配的吗?
写在前面 从开始学习Java的时候,我们就接触了这样一种观点:Java中的对象是在堆上创建的,对象的引用是放在栈里的,那这个观点就真的是正确的吗?如果是正确的,那么,面试官为啥会问:"Jav ...
随机推荐
- lea指令调用
lea指令(Load Effective Address)在x86汇编语言中的作用是将一个有效地址(即一个内存地址或寄存器地址的偏移量)加载到目标寄存器中,而不是加载一个实际的内存值. lea指令的使 ...
- do_fork(一)
fork 是linux创建进程的系统调用,相关的函数(不只是系统调用)还有 vfork,clone,sys_frok等.这些函数会整理不同参数,再调用到 do_fork 中. 本篇文章主要介绍do_f ...
- 交叉编译 Qt5.12 armv8(aarch64) 带 WebEngine - NVIDIA JETSON TX2
编译平台 Windows10 WSL2 Debian,目标平台 NVIDIA JETSON TX2 (注:Ubuntu <= 16.04 会出现 libclang < 3.8 的问题) 下 ...
- SQL Server用户的设置与授权
SQL Server用户的设置与授权 SSMS 登陆方式有两种,一是直接使用Windows身份验证,二是SQL Server身份验证.使用SQL Server用户设置与授权不仅可以将不同的数据库开放给 ...
- GO 中的时间操作(time & dateparse)【GO 基础】
〇.前言 日常开发过程中,对于时间的操作可谓是无处不在,但是想实现时间自由还是不简单的,多种时间格式容易混淆,那么本文将进行梳理,一起学习下. 官方提供的库是 time,功能很全面,本文也会详细介绍. ...
- 位图(bitmap)原理以及实现
大家好,我是蓝胖子,我一直相信编程是一门实践性的技术,其中算法也不例外,初学者可能往往对它可望而不可及,觉得很难,学了又忘,忘其实是由于没有真正搞懂算法的应用场景,所以我准备出一个系列,囊括我们在日常 ...
- 在线问诊 Python、FastAPI、Neo4j — 生成 Cypher 语句
目录 构建节点字典 构建Cypher CQL语句 Test 这边只是为了测试,演示效果和思路,实际应用中,可以通过NLP构建CQL 接上一篇的问题分类 question = "请问最近看东西 ...
- AI在人才测评领域的应用情况概述
目录 收起 一.AI技术的通俗理解 二.AI在人才测评领域的应用情况概述 三.AI在构建人才标准上的应用 1.人才标准构建 2.人才画像 3.人岗智能匹配 四.AI在人才测评实施方面的应用 1.AI面 ...
- 解决因对EFCore执行SQL方法不熟练而引起的问题
前言 本文测试环境:VS2022+.Net7+MySQL 因为我想要实现使用EFCore去执行sql文件,所以就用到了方法ExecuteSqlAsync,然后就产生了下面的问题,首先因为方法接收的参数 ...
- BizSpring在线商城常见问题
一.什么是BizSpring在线商城? BizSpring在线商城是一个用java语言开发的完全开源的网络商城平台.该项目已经经历多次迭代升级是一个的成熟的在线商城解决方案,它具有轻量级,易于维护,操 ...