前言

好久没有动用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堆字符串分配的更多相关文章

  1. windows程序员进阶系列:《软件调试》之Win32堆

     win32堆及内部结构 Windows在创建一个新的进程时会为该进程创建第一个堆,被称为进程的默认堆.默认堆的句柄会被保存在进程环境块_PEB的ProcessHeap字段中. 要获得_PEB的地址, ...

  2. windows程序员进阶系列:《软件调试》之Win32堆的调试支持

    Win32堆的调试支持 为了帮助程序员及时发现堆中的问题,堆管理器提供了以下功能来辅助调试. 1:堆尾检查(Heap Tail Check) HTC,在堆尾添加额外的标记信息,用于检测堆块是否溢出. ...

  3. java字符串池和字符串堆内存分配

    1. String str=new String("abc")和String str="abc"的字符串“abc”都是存放在堆中,而不是存在 栈中. 2. 其实 ...

  4. Java中的对象都是在堆上分配的吗?

    作者:LittleMagic https://www.jianshu.com/p/8377e09971b8 为了防止歧义,可以换个说法: Java对象实例和数组元素都是在堆上分配内存的吗? 答:不一定 ...

  5. 如何限制一个类只在堆上分配和栈上分配(StackOnly HeapOnly)

    [本文链接] http://www.cnblogs.com/hellogiser/p/stackonly-heaponly.html [题目] 如何限制一个类只在堆上分配和栈上分配? [代码]  C+ ...

  6. Unix系统编程()在堆上分配内存

    在堆上分配内存:malloc和free 一般情况下,C程序使用malloc函数族在堆上分配和释放内存.较之brk和sbrk,这些函数具备不少优点: 属于C语言标准的一部分 更易于在多线程程序中使用 接 ...

  7. JVM系列(1)- JVM常见参数及堆内存分配

    常见参数配置 基于JDK1.6 -XX:+PrintGC 每次触发GC的时候打印相关日志 -XX:+UseSerialGC 串行回收模式 -XX:+PrintGCDetails 打印更详细的GC日志 ...

  8. new 的对象如何不分配在堆而分配在栈上(方法逃逸等)

    当能够明确对象不会发生逃逸时,就可以对这个对象做一个优化,不将其分配到堆上,而是直接分配到栈上,这样在方法结束时,这个对象就会随着方法的出栈而销毁,这样就可以减少垃圾回收的压力. 如方法逃逸. 逃逸分 ...

  9. Java中对象并不是都在堆上分配内存的

    转(https://blog.51cto.com/13906751/2153924) 前段时间,给星球的球友们专门码了一篇文章<深入分析Java的编译原理>,其中深入的介绍了Java中的j ...

  10. 【性能优化】面试官:Java中的对象都是在堆上分配的吗?

    写在前面 从开始学习Java的时候,我们就接触了这样一种观点:Java中的对象是在堆上创建的,对象的引用是放在栈里的,那这个观点就真的是正确的吗?如果是正确的,那么,面试官为啥会问:"Jav ...

随机推荐

  1. Programming abstractions in C阅读笔记: p118-p122

    <Programming Abstractions In C>学习第49天,p118-p122,总结如下: 一.技术总结 1.随机数 (1)seed p119,"The init ...

  2. 【测试】自定义配置 RocksDB 进行 YCSB 测试

    目录 简介 编译 RocksDB 编译 YCSB 修复报错 自定义配置 RocksDB 进行 YCSB 测试 参考资料 本文主要记录在利用 YCSB 使用配置文件测试 RocksDB 的过程中遇到的一 ...

  3. tailwindcss -原子化 CSS 框架

    原子化 CSS 框架 我记得很久之前有时候为了少写些css,我们通常会有如下的样板代码 .block { display: block; } .flex { display:flex } .flex- ...

  4. 程序员:你如何写可重复执行的SQL语句?

    上图的意思: 百战百胜,屡试不爽. 故事 程序员小张: 刚毕业,参加工作1年左右,日常工作是CRUD 架构师老李: 多个大型项目经验,精通各种开发架构屠龙宝术: 小张注意到,在实际的项目开发场景中,很 ...

  5. 使用GPU搭建支持玛雅(Maya)和Adobe AI,DW,PS的职校云计算机房

    背景 学校为职业学校,计算机教室需要进行Maya.Adobe Illustrator.Adobe Dreamweaver.Adobe PhotoShop等软件的教学.每个教室为35用户.资源需求为4核 ...

  6. SQL Server更改表字段顺序和表结构

    1.首先打开SqlServer,SSMS可视化工具.点击工具,再点选项. 2.在弹出的选项窗口中,点击Desinners,点击表设计和数据库设计器,将阻止保护勾去掉.点"确定" 3 ...

  7. html页面底部添加版权信息

    话不多说,直接上代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http:/ ...

  8. UM 百度富文本编辑器自定义图片上传路径

    UM 百度富文本编辑器自定义图片上传路径 因为公司要做图文编辑,选择了UM,但是直接存入Tomcat根目录下,不满足业务需求需要存入服务器上. 一.需要注意的是在um的JSP目录下已经存在了Uploa ...

  9. java中有哪些并发的List?只知道一种的就太逊了

    java中有很多list,但是原生支持并发的并不多,我们在多线程的环境中如果想同时操作同一个list的时候,就涉及到了一个并发的过程,这时候我们就需要选择自带有并发属性的list,那么java中的并发 ...

  10. SpringBoot + Redis + Token 解决接口幂等性问题

    前言 SpringBoot实现接口幂等性的方案有很多,其中最常用的一种就是 token + redis 方式来实现. 下面我就通过一个案例代码,帮大家理解这种实现逻辑. 原理 前端获取服务端getTo ...