成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

絕頂技術:斷點+內存映射組合的CLR超強Bug?

開發 前端
CLR調用C#入口Main的匯編代碼里面下的斷點,運行到.Ctor然后報了異常。這個異常的排查過程如上所示,但是依然有疑惑。就是為啥通過VS調試C#源代碼則不會報這個異常。難道VS直接運行C#源代碼跟CLR調用略有不同?

前言

你見過斷點+內存映射,制造了一個另類隱藏極深,強悍的BUG嗎?這是一個虛擬機CLR的BUG。不同于之前所遇見的BUG這次費時最多,但是問題已然清晰。本篇來看下。

友情提示:學會本篇,你就是絕級的高手,足可笑傲當世。

概括

1.問題說明

BUG的起因在后面,先看看問題的描述。假如說遇到這樣一個問題,在某個地址(以Addr1表示)下了一個斷點,程序繼續運行,就會某個地方拋出一個異常,首先確認的是這段運行的代碼是完全沒有問題的。也就是說這個異常只會在下了斷點之后,才會拋出。查看堆棧,這個異常非常清晰明了,那就是程序運行過程中某個字段(filed1)的值為0。而通過這個字段也就是field1的空值去訪問field1的成員變量,自然是報了異常。

問題很簡單,似乎馬上就找到了異常出錯的地方,也就是field1==0造成的。但為什么field1會為空?它在哪里被賦值的,導致它是空值?跟下斷點有什么關系?這些都沒解決。

問題一:field1在哪里被賦值的?

經過跟蹤發現,field1是通過Windows API的兩個函數MapViewOfFileEx,MapViewOfFile進行內存映射來賦值的。這兩個內存映射函數映射了兩個內存地址。

MapViewOfFileEx映射的是可讀,可寫,可執行的內存地址(以pRX來表示)。也即是:

FILE_MAP_EXECUTE | FILE_MAP_READ | FILE_MAP_WRITE

MapViewOfFile映射的是可讀,可寫的內存地址(以pRW來表示),也即是:

FILE_MAP_READ | FILE_MAP_WRITE

當往pRW內存地址寫入數值,pRX也同時寫入相應的數值,這就是內存映射。這里就是field1被賦值的地方。

問題二:為什么會導致field1空值?

上面說的是,在某個地址也就是上面說的Addr1這個地方下了一個斷點,跟蹤發現,如果不在Addr1處下斷點,那么field1不等于0,也就不會報異常,如果在Addr1處下斷點,那么field1等于0,導致了異常的發生。

這個BUG很詭異,難道是斷點造成的?

繼續跟蹤發現,如果在離Addr1偏移量很遠的地址下斷點,則不會導致了field1==0,如果在Addr1地址上下偏移的地方下斷點(也就是偏移比較近的位置),則會導致field1等于0。難道Addr1地址的上下偏移范圍跟field1有一定的關聯?
繼續跟蹤發現,field的值在Addr1地址的后面,它的值本身也是一個地址。每塊內存都有一個起始地址,姑且叫Base。那么filed,Addr1,Base的組成如下圖所示:

圖片圖片

可以看到Addr1和field1的起始地址都是Base,而Base則是被MapViewOfFileEx Windows API內存映射的起始地址。Addr1則是被映射的這塊內存里面的某個函數中的某個地址。這里假如說它是程序入口Main函數的函數頭地址,也可以是Main函數中間的某個地址。如下圖:

圖片圖片

因為實際上在Addr1處下了斷點,也即是在被MapViewOfFileEx映射的內存地址里面下了斷點。在內存映射里面下了斷點,就會導致了通過MapViewOfFile映射的內存pRW賦值的時候,pRX會被賦值不上的情況。

pRX和pRW如下圖所示:

圖片圖片

如果把這個斷點,下在MapViewOfFileEx映射的內存范圍之外,則不會存在賦值不上的情況。

這里可以確定的就是,在內存映射的范圍內下斷點,斷點會干擾內存映射范圍內的數值。

2.檢測上面結論是否正確

上面只是問題的分析,如果想要檢驗上面所述BUG問題是否正確。則需要代碼加以輔助證明。

下面是一段內存映射的代碼:

#include<stdio.h>
#include<Windows.h>


#define DPTR(type) type*
#define VAL32(x) x
#define HIDWORD(_qw)    ((ULONG)((_qw) >> 32))
#define LODWORD(_qw)    ((ULONG)(_qw))


#define VIRTUAL_ALLOC_RESERVE_GRANULARITY (64*1024) 


typedef DPTR(IMAGE_DOS_HEADER) PTR_IMAGE_DOS_HEADER;
typedef DPTR(IMAGE_NT_HEADERS) PTR_IMAGE_NT_HEADERS;
typedef long long int64_t;
typedef unsigned long long uint64_t;
static const uint64_t MaxDoubleMappedSize = 2048ULL * 1024 * 1024 * 1024;


typedef unsigned __int64 ULONG_PTR, * PULONG_PTR;
typedef ULONG_PTR TADDR;


extern "C" IMAGE_DOS_HEADER __ImageBase;
typedef UINT32  COUNT_T;


template <typename T> inline T ALIGN_UP(T val, size_t alignment)
{
    return (T)ALIGN_UP((size_t)val, alignment);
}


void* GetClrModuleBase()
{
    return (void*)&__ImageBase;
}


IMAGE_NT_HEADERS* FindNTHeaders(TADDR m_base)
{
    return PTR_IMAGE_NT_HEADERS(m_base + VAL32(PTR_IMAGE_DOS_HEADER(m_base)->e_lfanew));
}
COUNT_T GetVirtualSize(TADDR base)
{
    return FindNTHeaders(base)->OptionalHeader.SizeOfImage;
}
void main()
{
    size_t pMaxExecutableCodeSize = (size_t)MaxDoubleMappedSize;


    void* pHandle = CreateFileMapping(
        INVALID_HANDLE_VALUE,    // use paging file
        NULL,                    // default security
        PAGE_EXECUTE_READWRITE | SEC_RESERVE,  // read/write/execute access
        HIDWORD(MaxDoubleMappedSize),                       // maximum object size (high-order DWORD)
        LODWORD(MaxDoubleMappedSize),   // maximum object size (low-order DWORD)
        NULL);


    SIZE_T sizeOfLargePage = GetLargePageMinimum();
    int nCount = 10;
    PVOID pAddr = VirtualAlloc(NULL, sizeOfLargePage * nCount, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);


    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery(pAddr, &mbi, sizeof mbi);


    void* base = GetClrModuleBase();
    SIZE_T base1 = (SIZE_T)base;
    SIZE_T size = GetVirtualSize((TADDR)base1);
    SIZE_T reach = 0x7FFF0000u;
    BYTE* g_preferredRangeMin = (base1 + size > reach) ? (BYTE*)(base1 + size - reach) : (BYTE*)0;
    BYTE* g_preferredRangeMax = (base1 + reach > base1) ? (BYTE*)(base1 + reach) : (BYTE*)-1;
    
    BYTE* pStart;
    pStart = g_preferredRangeMin + (g_preferredRangeMax - g_preferredRangeMin) / 8;
    pStart += 0x1000 * 0x00000003;
    BYTE* tryAddr = pStart; //(BYTE*)ALIGN_UP((BYTE*)pStart, VIRTUAL_ALLOC_RESERVE_GRANULARITY);


    BYTE* pRX = (BYTE*)MapViewOfFileEx((HANDLE)pHandle,
        FILE_MAP_EXECUTE | FILE_MAP_READ | FILE_MAP_WRITE,
        HIDWORD((int64_t)0),
        LODWORD((int64_t)0),
        0x0000000000010000,
        g_preferredRangeMax);


    VirtualAlloc(pRX, 0x0000000000010000, MEM_COMMIT, PAGE_EXECUTE_READ);


        MEMORY_BASIC_INFORMATION mbInfo;
    VirtualQuery((LPCVOID)pRX, &mbInfo, sizeof(mbInfo));


    void* pRW = (BYTE*)MapViewOfFile((HANDLE)pHandle,
        FILE_MAP_READ | FILE_MAP_WRITE,
        HIDWORD((int64_t)0),
        LODWORD((int64_t)0),
        0x0000000000010000);


    VirtualAlloc(pRW, 0x0000000000010000, MEM_COMMIT, PAGE_READWRITE);


    char abc[] = "abc";
    memcpy(pRW, abc, 3);
    VirtualQuery((LPCVOID)pRX, &mbInfo, sizeof(mbInfo));
}

以上例子,進行了一個內存模擬映射。通過以上例子,觀察發現。當在pRX所在地址范圍內下斷點,則會導致當往pRW里面賦值的時候,pRX賦值不上的情況,如下pRX地址處匯編代碼:

Address:00007ff739180000() //pRX Address
00007FF73917FFFC  ?? ?????? 
00007FF73917FFFD  ?? ?????? 
00007FF73917FFFE  ?? ?????? 
00007FF73917FFFF  ?? ?????? 
00007FF739180000  add         byte ptr [rax],al  
00007FF739180002  add         byte ptr [rax],al  
00007FF739180004  add         byte ptr [rax],al  
00007FF739180006  add         byte ptr [rax],al

這里來到了pRX的地址00007ff739180000處,在pRX地址向后偏移2個字節處下斷點,也即00007FF739180002。

然后在pRW地址處進行賦值,如下pRW處內存展示:

Address:0x000001BEE1610000 //pRW Memory
0010000000000000 0010000000000000 0000000000000000 0000000000000000 
0000000000000000 0000000000000000

這里的pRW地址是0x000001BEE1610000。

往它的第一個八字節賦值了:0010000000000000。然后看下pRX的的內存,如下:

Addres:0x00007FF739180000  //pRX Memory
0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000

可以看到在被MapViewOfFileEx映射的內存范圍內下斷點之后,pRW的賦值并不能更改pRX的值。這就導致了開頭的異常BUG。

3.代碼還原

通過以上理論分析和代碼分析,基本上確定了,這個BUG就是斷點+內存映射造成的。如果把斷點下在內存映射的范圍內的某個一個地址上,則會導致內存賦值的失敗。如果不下斷點,或者斷點不在內存映射范圍內,則不存在這種情況。這應該是微軟Windows內核的一個BUG。以上就是全部用戶態的BUG展示了,如果想要更深一些,則需要追蹤Windows內核,這個有時間再研究。

這個BUG起因于,CLR調用C#入口Main的匯編代碼里面下的斷點,運行到.Ctor然后報了異常。這個異常的排查過程如上所示,但是依然有疑惑。就是為啥通過VS調試C#源代碼則不會報這個異常。難道VS直接運行C#源代碼跟CLR調用略有不同?

責任編輯:武曉燕 來源: 江湖評談
相關推薦

2018-10-10 14:14:51

Linux內存映射

2009-10-22 17:39:34

CLR內存管理

2024-03-13 00:00:01

可視化技術氣泡圖

2021-04-27 13:56:49

內存.映射地址

2021-07-21 09:02:44

開發技能代碼

2023-03-01 10:37:51

2018-09-05 17:14:36

戴爾

2013-10-12 13:01:51

Linux運維內存管理

2009-07-24 10:00:38

.NET 4.0內存映

2019-11-11 13:40:45

Python 開發編程語言

2021-11-11 05:00:02

JavaMmap內存

2023-07-24 10:54:58

CLR優化空間

2011-04-25 17:15:39

MongodbMMAP

2012-06-20 14:16:36

Java內存映射

2009-09-18 13:05:59

.NET CLR

2022-08-02 09:02:17

虛擬內存操作系統

2020-09-17 08:28:08

內存映射反向

2009-10-19 14:25:16

靜態構造函數

2009-09-18 09:59:39

C# CLR

2009-09-18 10:18:30

CLR Via
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产91久久精品一区二区 | 国产精品1区2区3区 欧美 中文字幕 | 欧美性video| 在线观看中文字幕 | 精品久久一 | 国产精品久久在线 | 亚洲欧美日韩中文在线 | 日韩视频一区二区三区 | 成人免费观看男女羞羞视频 | 成人国产精品久久 | 一级片aaa | 久久高潮 | 亚洲国产精品日韩av不卡在线 | 精品毛片在线观看 | 亚洲欧美日韩精品久久亚洲区 | 国产伦一区二区三区久久 | 日日夜夜精品视频 | 亚洲狠狠丁香婷婷综合久久久 | 精品久久久久久一区二区 | 一级黄色片日本 | 99久久久久 | 欧美成年黄网站色视频 | 在线亚州 | 男人天堂99 | 国产精品国产自产拍高清 | 国产精品视频一区二区三区不卡 | 成人福利网| 久久精品国产99国产精品 | 久久亚洲精品久久国产一区二区 | 久久91视频| 亚洲xxxxx | 日韩精品一区二区久久 | 精品国产乱码久久久久久闺蜜 | 亚洲福利一区 | 久草精品视频 | 国产精品一区二区不卡 | 日韩一区在线观看视频 | 久久久久久美女 | 国产亚洲欧美在线视频 | 欧美日韩高清 | av网站在线播放 |