前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >.Net8 CLR跨代(card_table)续

.Net8 CLR跨代(card_table)续

作者头像
江湖评谈
发布2023-10-26 17:58:54
1390
发布2023-10-26 17:58:54
举报
文章被收录于专栏:天下风云天下风云

1.前言

card_table是CLR的核心技术之一,它的位标记循环遍历老年代堆(oldest_gen),找出老年堆对象对于新生代的引用,从CLR和GC里面萃取是一个较为复杂的工程,以.Net8为蓝本,本篇化繁为简,继续研看。

2.概述 老年代一般的都是指2代,也即是max_generation。循环遍历这个找出这个代里面的堆段(heap segment),通过位标记循环遍历每个堆段的引用对象的地址(老年代地址)范围,对被引用的对象进行存活标记。先上一张图,用以了解大致。

card_word和card_word_end范围遍历每一个2代堆段,找出2代堆段里引用的新生代对象。 3.论证 通过lldb查看下这一过程,首先是找到老年代对象(n1)和新生代对象(n2)的地址,两者处于前者引用后者。

代码语言:javascript
复制
    Name n1 = new Name("1111");
    GC.Collect();GC.Collect();
    Name n2 = new Name("3333");
    n1.selfName= n2;GC.Collect(0);

在托管Main的JIT_WriteBarrier代码处:

代码语言:javascript
复制
0x7fff78d85511: lea    rdi, [rdi + 0x10]
0x7fff78d85515: mov    rsi, qword ptr [rbp - 0x18]
0x7fff78d85519: call   0x7ffff730f6e0            ; JIT_WriteBarrier

两个对象地址如下:

代码语言:javascript
复制
(lldb) c
(lldb) register read rdi rsi
     rdi = 0x00007fbf6a808b08
     rsi = 0x00007fbf6cc00028

看下JIT_WriteBarrier干了啥

代码语言:javascript
复制
libcoreclr.so`JIT_WriteBarrier:
->  0x7ffff730f6e0 <+0>:   mov    qword ptr [rdi], rsi
    0x7ffff730f6e3 <+3>:   mov    r8, rdi
    0x7ffff730f6e6 <+6>:   movabs rax, 0x7fbee54ff2a0
    0x7ffff730f6f0 <+16>:  shr    rdi, 0x16
    0x7ffff730f6f4 <+20>:  cmp    byte ptr [rdi + rax], 0x0
    0x7ffff730f6f8 <+24>:  jne    0x7ffff730f6fe            ; <+30>
    0x7ffff730f6fa <+26>:  rep    ret
    0x7ffff730f6fc <+28>:  nop    
    0x7ffff730f6fe <+30>:  movabs r9, 0x7fbf68000000
    0x7ffff730f708 <+40>:  cmp    rsi, r9
    0x7ffff730f70b <+43>:  jae    0x7ffff730f70e            ; <+46>
    0x7ffff730f70d <+45>:  ret    
    0x7ffff730f70e <+46>:  movabs r9, 0x7fff68000000
    0x7ffff730f718 <+56>:  cmp    rsi, r9
    0x7ffff730f71b <+59>:  jb     0x7ffff730f71f            ; <+63>
    0x7ffff730f71d <+61>:  rep    ret
    0x7ffff730f71f <+63>:  shr    rsi, 0x16
    0x7ffff730f723 <+67>:  mov    dl, byte ptr [rsi + rax]
    0x7ffff730f726 <+70>:  cmp    dl, byte ptr [rdi + rax]
    0x7ffff730f729 <+73>:  jb     0x7ffff730f72e            ; <+78>
    0x7ffff730f72b <+75>:  rep    ret
    0x7ffff730f72d <+77>:  nop    
    0x7ffff730f72e <+78>:  movabs rax, 0x7faedb5ff040
    0x7ffff730f738 <+88>:  mov    ecx, r8d
    0x7ffff730f73b <+91>:  shr    r8, 0xb
    0x7ffff730f73f <+95>:  shr    ecx, 0x8
    0x7ffff730f742 <+98>:  and    ecx, 0x7
    0x7ffff730f745 <+101>: mov    dl, 0x1
    0x7ffff730f747 <+103>: shl    dl, cl
    0x7ffff730f749 <+105>: test   byte ptr [r8 + rax], dl
    0x7ffff730f74d <+109>: je     0x7ffff730f751            ; <+113>
    0x7ffff730f74f <+111>: rep    ret
    0x7ffff730f751 <+113>: lock   
    0x7ffff730f752 <+114>: or     byte ptr [r8 + rax], dl
    0x7ffff730f756 <+118>: movabs rax, 0x7fbedf4ef500
    0x7ffff730f760 <+128>: shr    r8, 0xa
    0x7ffff730f764 <+132>: cmp    byte ptr [r8 + rax], -0x1
    0x7ffff730f769 <+137>: jne    0x7ffff730f76d            ; <+141>
    0x7ffff730f76b <+139>: rep    ret
    0x7ffff730f76d <+141>: mov    byte ptr [r8 + rax], -0x1
    0x7ffff730f772 <+146>: ret

代码有点长,这里用C模拟下:

代码语言:javascript
复制
n1.selfName=n2;
r8=n1.selfName;
rax=cardw_card_bundle//在card_table基础上进一步限制范围
n1.selfName=n1.selfName>>0x16
if((cardw_card_bundle+n1.selfName)==0)
{
  return;
}
else
{
  if(短暂堆起始地址<=n2<短暂堆结束地址)
  {
    n2=n2<<0x16;
  rax=0x7faedb5ff040;
  *(rax+n1.selfName>>0xB)=8;//这个地方windows是0xFF
  }
}

大致的意思就是把2代对象n1.selfName右移

0x0B+card_table首地址,它的值赋值为0x8.

继续:

代码语言:javascript
复制
br del
b gc.cpp:38448
c
38445              limit = min (end, card_address (end_card));
   38446  #endif // FEATURE_CARD_MARKING_STEALING
   38447          }
-> 38448          if (!foundp || (last_object >= end) || (card_address (card) >= end))
   38449          {
   38450              if (foundp && (cg_pointers_found == 0))
   38451              {

limit上一篇说过,它是2代堆段里面的一个结束范围,还有一个起始范围,这两个变量构成了从这个范围内查找引用了新生代的老年代对象。看下这个limit的结束地址:

代码语言:javascript
复制
(lldb) p/x limit
(uint8_t *) $81 = 0x00007fbf6a808b18 ""

上面的n1.selfName的地址是:0x00007fbf6a808b08

而limit的结束范围地址是: 0x00007fbf6a808b18 可以此循环包含了2代对象的地址。那么它就可以找出来 4.find_card 在limit之前,CLR调用了find_card来查找这个2代对象引用新生代对象的地址范围。 在find_card函数里面去看下

代码语言:javascript
复制
b gc.cpp:37953
c
   37952      last_card_word = &card_table [card_word (card)];
-> 37953      bit_position = card_bit (card);
   37954  #ifdef CARD_BUNDLE
   37955      // if we have card bundles, consult them before fetching a new card word
   37956      if (bit_position == 0)

card_table就是上面第三步论证里面的n1.selfName右移之后加上的地址

代码语言:javascript
复制
(lldb) p/x card_table
(uint32_t *) $88 = 0x00007faedb5ff040

通过gc_heap::find_card_dword把2代堆段的结尾赋给card_word索引

代码语言:javascript
复制
   37970          size_t lcw = card_word(card) + (bit_position != 0);
-> 37971          if (gc_heap::find_card_dword (lcw, card_word_end) == FALSE)
   37972          {
   37973              return FALSE;
   37974          }

通过BitScanForward函数,获取位标记从右至左有几个0

代码语言:javascript
复制
  DWORD bit_index;
   38006          uint8_t res = BitScanForward (&bit_index, card_word_value);
-> 38007          assert (res != 0);
   38008          card_word_value >>= bit_index;
   38009          bit_position += bit_index;

这样就可以确定上面第三步论证里面的老年代n1.selfName位移之后加上card_table所在的地址里面的值是多少,然后计算出它的card

代码语言:javascript
复制
card = (last_card_word - &card_table[0]) * card_word_width + bit_position;

这样card到card_end,整个一个范围就确认了。然后循环遍历这个范围找出二代对象引用新生代对象的范围。对它进行标记。

这里还有一个要注意的地方就是除了card_table的循环,还有一个堆段的循环,前者在里小循环,后者在外大循环

代码语言:javascript
复制
            if (seg)
            {
#ifdef BACKGROUND_GC
                should_check_bgc_mark (seg, &consider_bgc_mark_p, &check_current_sweep_p, &check_saved_sweep_p);
#endif //BACKGROUND_GC
                beg = heap_segment_mem (seg);
#ifdef USE_REGIONS
                end = heap_segment_allocated (seg);
#else
                end = compute_next_end (seg, low);
#endif //USE_REGIONS
#ifdef FEATURE_CARD_MARKING_STEALING
                card_word_end = 0;
#else // FEATURE_CARD_MARKING_STEALING
                card_word_end = card_of (align_on_card_word (end)) / card_word_width;

5.标记

找到之后如何标记的呢?通过mark_object_simple

代码语言:javascript
复制
b mark_object_simple
c
n.....
   24875  
-> 24876          o = mark_queue.queue_mark (o);
   24877          if (o != nullptr)
   24878          {
   24879              m_boundary (o);

qeeue_mark查找标记队列,如果这个对象已经被标记了,那么就不需要再标记。

看下n2是否被标记

代码语言:javascript
复制
(lldb) x/8gx 0x00007fbf6cc00028
0x7fbf6cc00028: 0x00007fff7911c471 0x00007fffe6bff8e0
0x7fbf6cc00038: 0x0000000000000000 0x0000000000000000
0x7fbf6cc00048: 0x0000000000000000 0x0000000000000000
0x7fbf6cc00058: 0x0000000000000000 0x0000000000000000

因为是跟对象已经被标记,所以这里不需要再次标记,以上大致跨代引用的操作,细节之处依然值得继续探究

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 江湖评谈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档