CLR运行时GCInfo的读取字段骚操作

在我们GC垃圾回收的时候,需要 GC root 遍历 Local Variable(本地变量,简称LV)。
找出当前寄存器或者栈中存储了几个LV,具体的操作代码如下(QQ群:676817308):

找出寄存存储变量个数

m_NumRegisters = (UINT32) reader.DecodeVarLengthUnsigned(NUM_REGISTERS_ENCBASE);

NUM_REGISTERS_ENCBASE=2,也就是寄存的偏移量为2.
我们再看DecodeVarLengthUnsigned函数

inline size_t DecodeVarLengthUnsigned( int base )
    {
    
    
        _ASSERTE((base > 0) && (base < (int)BITS_PER_SIZE_T));//寄存器的偏移量必须大于0 而小于64的时候,否则异常,BITS_PER_SIZE_T是8个字节,一个字节8个bit位,总共64个bit位
        size_t numEncodings = 1 << base;//这个是找出偏移量的数值,当1按照偏移量向左移动base位的结果
        size_t result = 0;//需要返回的结果
        for(int shift=0; ; shift+=base)
        {
    
    
            _ASSERTE(shift+base <= (int)BITS_PER_SIZE_T);
            
            size_t currentChunk = Read(base+1);
            result |= (currentChunk & (numEncodings-1)) << shift;//获取的结果与numEncodings-1相与,相与的结果就是取最后两位,刚好 NUM_REGISTERS_ENCBASE传入的是2,也就是只需要取两位。
            if(!(currentChunk & numEncodings))//这个地方currentChunk & numEncodings,比如读取两位,那么这两位前面的第三位必须为零,不然继续读取
            {
    
    
                // Extension bit is not set, we're done.
                return result;
            }
        }
    }

来看看Read函数

BitStreamReader( PTR_CBYTE pBuffer )
    {
    
    
        SUPPORTS_DAC;

        _ASSERTE( pBuffer != NULL );

        m_pCurrent = m_pBuffer = dac_cast<PTR_size_t>((size_t)dac_cast<TADDR>(pBuffer) & ~((size_t)sizeof(size_t)-1));
        m_RelPos = m_InitialRelPos = (int)((size_t)dac_cast<TADDR>(pBuffer) % sizeof(size_t)) * 8/*BITS_PER_BYTE*/;
    }

  __forceinline size_t Read( int numBits )
    {
    
    
        SUPPORTS_DAC;

        _ASSERTE(numBits > 0 && numBits <= BITS_PER_SIZE_T);

        size_t result = (*m_pCurrent) >> m_RelPos;
        int newRelPos = m_RelPos + numBits;
        if(newRelPos >= BITS_PER_SIZE_T)
        {
    
    
            m_pCurrent++;
            newRelPos -= BITS_PER_SIZE_T;
            if(newRelPos > 0)
            {
    
    
                size_t extraBits = (*m_pCurrent) << (numBits - newRelPos);
                result ^= extraBits;
            }
        }
        m_RelPos = newRelPos;
        result &= SAFE_SHIFT_LEFT(1, numBits) - 1;
        return result;
    }

read函数之前有个,BitStreamReader构造函数的实例化,这个类里面保存了,读取gcinfo 的起始地址,以及读取字段的位置信息
m_pCurrent = m_pBuffer = dac_cast<PTR_size_t>((size_t)dac_cast(pBuffer) & ~((size_t)sizeof(size_t)-1)); 这个操作实际上是把传入进来的地址值对其以便被8整除
m_RelPos = m_InitialRelPos = (int)((size_t)dac_cast(pBuffer) % sizeof(size_t)) * 8/BITS_PER_BYTE/;这个操作是对其能被8 整除之后,挪动了几位。

1.比较骚的地方:
size_t result = (m_pCurrent) >> m_RelPos;因为向左移动了N个字节起始指针,所以需要向右位移N8个bit位,这个地方有点意思,为啥向左挪动,而需要向右位移,因为x64是后面的地址放在前面,比如
1 2 3 4 5 6 7 8 总共八个字节,2字节的整数是 21 43 65 87 , 4字节的整数是 4321 8765 , 八字节的整数是 87654321 。 比如说 在 1 2 3 4 5 6 7 8 需要往前挪一位才能被8 整除,假设这个字节位0。 连接起来就是 0 1 2 3 4 5 6 7 8 ,因为8字节一读取, 实际上就是 76543210 ,通过(int)((size_t)dac_cast(pBuffer) % sizeof(size_t)) * 8计算出,挪一个字节8个bit位,n*8,n是字节数。这样(*m_pCurrent) >> m_RelPos实际上就是从1开始读取数字,因为0被挪位挪掉了。这个地方设计的非常巧妙。不仔细阅读,很难读懂它。

2.比较骚的地方
int newRelPos = m_RelPos + numBits;
if(newRelPos >= BITS_PER_SIZE_T)
{
m_pCurrent++;
newRelPos -= BITS_PER_SIZE_T;
if(newRelPos > 0)
{
size_t extraBits = (*m_pCurrent) << (numBits - newRelPos);
result ^= extraBits;
}
}
m_RelPos = newRelPos;
result &= SAFE_SHIFT_LEFT(1, numBits) - 1;

这段代码特别有意思,原有已经被用掉的位移+新读取的位移等于总共用掉的位移数。
按照代码逻辑,假如说 newRelPos< BITS_PER_SIZE_T。也就是小于64的时候,直接读取numBits位数,然后返回。SAFE_SHIFT_LEFT实际上就是把1向左位移numBits个位,减去1 ,然后&result,也就是上面的起始位置,可以得出需要读取的numBits个位数。但它经典的不在于此,而是当newRelPos >= BITS_PER_SIZE_T的时候,m_pCurrent++ 到了下一个8个字节, newRelPos -= BITS_PER_SIZE_T;也就是上一个字节多出来的位数,(*m_pCurrent) << (numBits - newRelPos);这句话是精华,就是在m_pCurrent++ 之前上一个字节剩余的位数,m_pCurrent++ 只有向左位移,然后异或与也就是符号^这个箭头的形状的,这样就可以得到连接了上一个8 个字节剩余的两个字节以及下一个字节的六个字节,导致了读取不间断。

猜你喜欢

转载自blog.csdn.net/tangyanzhi1111/article/details/113774293