女人自慰AV免费观看内涵网,日韩国产剧情在线观看网址,神马电影网特片网,最新一级电影欧美,在线观看亚洲欧美日韩,黄色视频在线播放免费观看,ABO涨奶期羡澄,第一导航fulione,美女主播操b

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

C++開發時遇到堆上的內存破壞怎么辦

開關電源芯片 ? 來源:一個程序員的修煉之路 ? 作者: 河邊一枝柳 ? 2021-08-23 10:18 ? 次閱讀

有一定C++開發經驗的同學大多數踩過內存破壞的坑,有這么幾種現象:

比如某個變量整形,在程序中只可能初始化或者賦值為1或者2, 但是在使用的時候卻發現其為0或者其他的情況。對于其他類型,比如字符串等,可能出現了一種出乎意料的值!

程序在堆上申請內存或者釋放內存的時候,在內存充足的情況下,居然出現了堆錯誤。

當出現以上場景的時候,你該思考一下,是不是出現了內存破壞的情況了。而本文主要通過展示和分析常見的三種內存破壞導致覆蓋相鄰變量的場景,讓讀者在碰到類似的場景,不至于束手無策。而對于堆上的內存破壞,很常見并且棘手的場景,本人將在后續的文章和大家分享。

1. 內存破壞之強制類型轉換

大家都知道不匹配的類型強制轉換會帶來一些bug,比如int和unsigned int互相轉換,又或者int和__int64強行轉換。是不是每次當讀起這類文章起來如雷貫耳,但是當自己去寫代碼的時候還是容易犯錯?這也就是為什么C++容易寫出坑的原因,明知可能有錯,還難以避免。這往往是因為真實的項目中復雜程度,往往讓人容易忽略這些細節。

不少老的工程代碼還是采用VC6編譯,為了安全問題或者使用C++新特性需要將VC6升級到更新的Visual Studio。接下來要介紹的一個樣例程序,就是隱藏于代碼中的一個問題,如果從VC6升級到VS2017的時候會帶來問題嗎?可以先找找看:

#include 《iostream》#include 《time.h》class DemoClass

{public:

DemoClass() : m_bInit(true), m_tRecordTime(0)

{

time((time_t *)(&m_tRecordTime));

};

void DoSomething()

{

if (m_bInit)

std::cout 《《 “Do Task!” 《《 std::endl;

}

private:

int m_tRecordTime;

bool m_bInit;

};

int main()

{

DemoClass testObj;

testObj.DoSomething();

return 0;

}

Do Task!這個字符串會不會打印出來呢? 可以發現這段程序在VC6中可以打印出來,但是在VS2017中卻打印不出來了。那是因為如下原因:

函數原型time_t time( time_t *destTime );,在VC6中time_t默認是32位,而在VS2017中默認是64位。早期程序以為32位中表達最大的時間是2038年,那時候完全夠用,但隨著計算機本身的發展64位逐漸成為主流time_t在最新的編譯器中也默認采用64位,這樣時間完全夠用以億年為單位了,那時候計算機發展超出我們想象了。

程序的問題所在m_tRecordTime采用的是int類型,默認為32位,那么其地址作為time_t time( time_t *destTime );函數實參后,在VC6中time_t本身為32位自然也不會出錯,但是在VS2017中因為time_t為64位,則time((time_t *)(&m_tRecordTime));后寫入了一個64位的值。結合下圖,看下這個對象的內存布局,m_bInit的值將會被覆蓋,而這里原先的m_bInit的值為1,被覆蓋為0,從而導致內存破壞,導致程序執行意想不到的結果。這里只是不輸出,那在真實程序中,可能會導致某個邏輯錯亂,發生嚴重的問題。

3783f2fc-02f6-11ec-9bcf-12bb97331649.png

這個問題修改自然比較簡單,將m_tRecordTime定義為time_t類型就可以了。如果有類似的問題發生的時候,比如這個變量的可疑的發生了不該有的變化的時候,你可以查看下這個變量定義的附近是否有內存的操作可能產生溢出,找到問題所在。因為內存上溢的比較多,一般可以查看下定義在當前出現問題的變量的低地址出的變量操作,是否存在可疑的地方。最后,針對這種場景,我們是不是也可以得到一些收獲呢,個人總結如下兩點:

在定義類型的時候,盡量和原始類型一致,比如這里的time_t有些程序員可能慣性的認為就是32位,那就定義一個時間戳的時候就定義為int了,而我們要做的應該是和原始類型匹配(也就是函數的輸入類型),將其定義為time_t,于此類似的還有size_t等,這樣可以避免未來在數據集變化或者做平臺遷移的時候造成不必要的麻煩。

在有一些復雜的場景的下,也許你不得不做類型轉換,而這個時候就格外的需要注意或者了解清楚,轉換帶來的情況和后果,保持警惕,否則就可能是一個潛在的bug。這和開車一樣,當你開車的時候如果看到前方車輛忽然產生一個不合常理的變道行為,首先要做的不是噴那輛車,而是集中注意力,看看是否更前方有障礙物或者事故放生,做出相應的反應。

2. 字符串拷貝溢出

這種情況應該是最常見了,我們來看一看樣例程序:

#include 《iostream》#define BUFER_SIZE_STR_1 5#define BUFER_SIZE_STR_2 8class DemoClass

{public:

void DoSomething()

{

strcpy(m_str1, “Hi Coder!”);

std::cout 《《 m_str1 《《 std::endl;

std::cout 《《 m_str2 《《 std::endl;

}

private:

char m_str1[BUFER_SIZE_STR_1] = { 0 };

char m_str2[BUFER_SIZE_STR_2] = { 0 };

};

int main()

{

DemoClass testObj;

testObj.DoSomething();

return 0;

}

這種情況下肉眼可以分析的,輸出結果為:

在m_str1的空間為5,但是Hi Coder!包含是10個字符,在調用strcpy(m_str1, “Hi Coder!”);的時候超過了m_str1的空間,于是覆蓋了m_str2的內存,從而導致內存破壞。內存溢出這種尤其字符串溢出,程序崩潰可能是小事兒,如果是一個廣為流傳的軟件,那么就很有可能會被黑客所利用。

這種字符串場景如何分析呢,如果程序崩潰了,可以收集Dump先看看被覆蓋的地方是什么樣的字符串,然后聯想看看自己的程序哪里有可能對這個字符串的操作,從而找到原因。別小看這種方法,簡單粗暴很有用,曾經就用這種方式分析過Linux驅動模塊的內存泄露問題。

那如果還找不到問題呢?如果問題還能重現,那還是有調試手法的,下一節將會進行講解。

當然最差最差的還是不要放棄代碼審查。尤其在這個內存被破壞的附近的邏輯。對于這種場景的建議,比較簡單就是使用微軟安全函數strcpy_s,注意這里雖然列出了返回值errno_t不過對于微軟的實現來說,如果是目標內存空間不夠的情況下,在Relase版本下會調用TerminateProcess, 并且要注意的是這個時候抓Dump有時候并不是完整的Dump。

至于微軟為什么要這樣做,有可能是安全的考慮比崩潰優先級更高,于是在內存溢出不夠的時候,直接讓程序結束。

errno_t strcpy_s( char *dest, rsize_t dest_size, const char *src);

3. 隨機性的內存被修改

這一個一聽都快崩潰了,C++的坑能不能少一點呢。但是確實是會有各種各樣的場景讓你落入坑內。上一節的程序我稍作修改:

#include 《iostream》#define BUFER_SIZE_STR_1 5#define BUFER_SIZE_STR_2 8class DemoClass

{public:

void DoSomething()

{

strcpy_s(m_str2, BUFER_SIZE_STR_2, “Coder”);

strcpy_s(m_str1, BUFER_SIZE_STR_1, “Test”);

//Notice this line:

m_str1[BUFER_SIZE_STR_2 - 1] = ‘’;

std::cout 《《 m_str1 《《 std::endl;

std::cout 《《 m_str2 《《 std::endl;

}

private:

char m_str1[BUFER_SIZE_STR_1] = { 0 };

char m_str2[BUFER_SIZE_STR_2] = { 0 };

};

int main()

{

DemoClass testObj;

testObj.DoSomething();

return 0;

}

程序本意是m_str2賦值為Coder, m_str1賦值為Test, 在編程中很多字符串拷貝或者操作中有些是在字符串末尾補有的可能不補, 而在本例中實際上strcpy_s會自動補0,但是有的程序員防止萬一,字符串靠背后,在數組的最后一位設置為’’。這種有時候就變成了好心辦壞事。

比如這里的m_str1[BUFER_SIZE_STR_2 - 1] = ‘’; ,大家注意到沒,這里應該改寫為m_str1[BUFER_SIZE_STR_1 - 1] = ‘’; ,也就是說程序員可能拷貝代碼或者不小心寫錯了BUFER_SIZE_STR_2和BUFER_SIZE_STR_1因為兩者宏差不多。只要是人寫代碼,就有可能會犯這種錯誤。這個程序的輸出變為:

這個程序是比較簡單,一目了然,但是在大型程序中呢,這個數組的位置跳躍的訪問到了其他變量的位置,你首先得判斷這個被跳躍式修改的變量,是不是程序本意造成的,因為混合了這么多的猜想,可能會導致分析變的異常復雜。那么有什么好的方法嗎?只要程序能偶爾重現這個問題,那就是有方法的。

通過Windbg調試命令ba可以在指定的內存地址做操作的時候進入斷點。假設目前已經知道m_str2的第四個字符,總是被某個地方誤寫,那么我們可以在這個地址處設置一個ba命令: 當寫的這個內存地址的時候進入斷點。不過這樣還是有個問題,那就是程序中有可能有很多次對這塊內存的寫操作,有時候是正常的寫操作,如果一直進入斷點,人工分析將會非常累,不現實。

這個時候有個方法,同時也是一個workaround,就是當你還沒找到程序出錯的根本原因的時候在被誤踩的內存前面加上一個足夠大的不使用的空間。比如下面的代碼, m_str2總是被誤寫,于是在m_str2的前面加上一個100個字節的不使用的內存m_strUnused(因為一般程序內存溢出是上溢,當然也可以在m_str2的后面同樣加上)。

這樣我們被踩的內存就很容易落在m_strUnused空間里面了,這個時候我們在其空間里設置寫內存操作的斷點,就容易捕獲到問題所在了。

#include 《iostream》#define BUFER_SIZE_STR_1 5#define BUFER_SIZE_STR_2 8#define BUFFER_SIZE_UNUSED 100class DemoClass

{public:

void DoSomething()

{

strcpy_s(m_str2, BUFER_SIZE_STR_2, “Coder”);

strcpy_s(m_str1, BUFER_SIZE_STR_1, “Test”);

//Notice this line:

m_str1[BUFER_SIZE_STR_2 - 1] = ‘’;

std::cout 《《 m_str1 《《 std::endl;

std::cout 《《 m_str2 《《 std::endl;

}

private:

char m_str1[BUFER_SIZE_STR_1] = { 0 };

char m_strUnused[BUFFER_SIZE_UNUSED] = { 0 };

char m_str2[BUFER_SIZE_STR_2] = { 0 };

};

int main()

{

DemoClass testObj;

testObj.DoSomething();

return 0;

}

下面完整的展示一下分析過程:

第一步 用Windbg啟動(有的情況下可能是Attach,根據情況而定)到調試進程,設置main的斷點

0:000》 bp ObjectMemberBufferOverFllow!main

*** WARNING: Unable to verify checksum for ObjectMemberBufferOverFllow.exe

0:000》 g

Breakpoint 0 hit

eax=010964c0 ebx=00e66000 ecx=00000000 edx=00000000 esi=75aae0b0 edi=0109b390

eip=003a1700 esp=00defa00 ebp=00defa44 iopl=0 nv up ei pl nz na pe nc

cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206

ObjectMemberBufferOverFllow!main:

003a1700 55 push ebp

第二步 使用p命令單步執行代碼到testObj.DoSomething();

第三步 找到testObj的地址為00def984

0:000》 dv /t /v

00def984 class DemoClass testObj = class DemoClass

第四步 設置斷點到testObj相對偏移的位置,這個位置即&m_str1+BUFER_SIZE_STR_2 - 1 = &m_str1+7。并且繼續執行代碼:

0:000》 ba w1 00def984+7

0:000》 g

第五步 你會發現程序運行進入斷點,這個時候查看對應的函數調用棧即可。這個斷點不一定在一個非常精確的位置,但是當你按照函數調用棧去閱讀附近的代碼,便比較容易找出問題所在了。

0:000》 k

# ChildEBP RetAddr

00 00def97c 003a1720 ObjectMemberBufferOverFllow!DemoClass::DoSomething+0x41 [。..。..strcpybufferoverflow.cpp @ 16]

01 00def9fc 003a1906 ObjectMemberBufferOverFllow!main+0x20 [。..。..strcpybufferoverflow.cpp @ 30]

02 (Inline) -------- ObjectMemberBufferOverFllow!invoke_main+0x1c [d:agent\_work3ssrcvctoolscrtvcstartupsrcstartupexe_common.inl @ 78]

03 00defa44 75818494 ObjectMemberBufferOverFllow!__scrt_common_main_seh+0xfa [d:agent\_work3ssrcvctoolscrtvcstartupsrcstartupexe_common.inl @ 288]

04 00defa58 770a40e8 KERNEL32!BaseThreadInitThunk+0x24

05 00defaa0 770a40b8 ntdll!__RtlUserThreadStart+0x2f

06 00defab0 00000000 ntdll!_RtlUserThreadStart+0x1b

總結

以上對三種內存破壞場景做了分析,在實際應用中將會變的更加復雜。在寫代碼的時候要注意避開其中的坑,有個叫做墨菲定律,你感覺可能會出問題的地方,那它一定會在某個時刻出現,當你對某個地方有所疑慮的時候一定要多加考慮,否則這個坑可能查找的時間,比寫代碼的時間要長的許多,更可怕的是可能會帶來意想不到的后果。同樣的分析問題要保持足夠的耐心,相信真相總會出現,這樣的底氣也是來自于自己平時不斷的學習和實踐。

內存破壞問題不區分棧上還是堆上,我們在產品中離不開使用堆開間,而且由多個模塊核心功能模塊組成,而這些模塊通常是公用一個進程默認堆的。所以也有人推薦在這些關鍵模塊中,各自創建一個獨立的堆,從而降低一個堆內存的使用對另一個堆中內存的影響。雖然不是完全隔離,但是也是一個聊勝于無的操作了。

責任編輯:haq

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 內存
    +關注

    關注

    8

    文章

    3108

    瀏覽量

    74986
  • C++
    C++
    +關注

    關注

    22

    文章

    2117

    瀏覽量

    74778

原文標題:C++常見的三種內存破壞場景和分析

文章出處:【微信號:gh_3980db2283cd,微信公眾號:開關電源芯片】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦
    熱點推薦

    主流的 MCU 開發語言為什么是 C 而不是 C++

    在單片機的地界兒里,C語言穩坐中軍帳,C++想分杯羹?難嘍。咱電子工程師天天跟那針尖大的內存空間較勁,C++那些花里胡哨的玩意兒,在這兒真玩不轉。先說
    的頭像 發表于 05-21 10:33 ?266次閱讀
    主流的 MCU <b class='flag-5'>開發</b>語言為什么是 <b class='flag-5'>C</b> 而不是 <b class='flag-5'>C++</b>?

    使用C++中的CyAPI編寫的應用程序上遇到了問題,求解決

    我在使用 C++ 中的 CyAPI 編寫的應用程序上遇到了問題。 我將 XferData() 方法與其他所有端點類型一起使用,沒有遇到任何問題。 但是,將其與 Endpoint0 一起使用會引發
    發表于 05-13 06:11

    源代碼加密、源代碼防泄漏c/c++與git服務器開發環境

    源代碼加密對于很多研發性單位來說是至關重要的,當然每家企業的業務需求不同所用的開發環境及開發語言也不盡相同,今天主要來講一下c++及git開發環境的源代碼防泄密保護方案。企業源代碼泄密
    的頭像 發表于 02-12 15:26 ?457次閱讀
    源代碼加密、源代碼防泄漏<b class='flag-5'>c</b>/<b class='flag-5'>c++</b>與git服務器<b class='flag-5'>開發</b>環境

    Spire.XLS for C++組件說明

    開發人員可以快速地在 C++ 平臺上完成對 Excel 的各種編程操作,如根據模板創建新的 Excel 文檔,編輯現有 Excel 文檔,以及對 Excel 文檔進行轉換。 Spire.XLS
    的頭像 發表于 01-14 09:40 ?514次閱讀
    Spire.XLS for <b class='flag-5'>C++</b>組件說明

    C7000優化C/C++編譯器

    電子發燒友網站提供《C7000優化C/C++編譯器.pdf》資料免費下載
    發表于 10-30 09:45 ?0次下載
    <b class='flag-5'>C</b>7000優化<b class='flag-5'>C</b>/<b class='flag-5'>C++</b>編譯器

    路由器內存使用率過高怎么辦

    路由器內存使用率過高是一個常見的問題,它可能會導致網絡速度變慢、連接不穩定甚至設備崩潰。 路由器內存的作用和重要性 路由器是網絡通信的核心設備,負責將數據包從一個網絡傳輸到另一個網絡。路由器內存
    的頭像 發表于 10-15 14:36 ?3033次閱讀

    使用OpenVINO GenAI API在C++中構建AI應用程序

    許多桌面應用程序是使用 C++ 開發的,而將生成式AI(GenAI)功能集成到這些應用程序中可能會很具有挑戰性,尤其是因為使用像 Hugging Face 這樣的 Python 庫的復雜性。C++
    的頭像 發表于 10-12 09:36 ?993次閱讀
    使用OpenVINO GenAI API在<b class='flag-5'>C++</b>中構建AI應用程序

    usb主機控制器設備破壞怎么辦

    當你遇到USB主機控制器設備損壞的情況時,可能會感到非常沮喪,因為這意味著你的計算機可能無法識別或使用USB設備。在這種情況下,你需要采取一系列步驟來診斷問題、確定原因,并嘗試修復或更換損壞的硬件
    的頭像 發表于 09-25 09:21 ?1007次閱讀

    信號噪聲太大怎么辦

    我用一個TMR磁場傳感器,后面接一個儀表放大器,測出來的信號的噪聲特別大,如圖所示。這種情況怎么辦
    發表于 09-06 11:09

    ddos造成服務器癱瘓后怎么辦

    在服務器遭受DDoS攻擊后,應立即采取相應措施,包括加強服務器安全、使用CDN和DDoS防御服務來減輕攻擊的影響。rak小編為您整理發布ddos造成服務器癱瘓后怎么辦
    的頭像 發表于 08-15 10:08 ?469次閱讀

    盛顯科技:投影融合處理器出現顏色失真或偏色,該怎么辦

    我們在使用投影融合處理器的過程中,因種種原因,有時候會遇到出現顏色失真或偏色的情況。此種情況的出現,會對視覺效果、信息傳遞和設備性能產生負面影響。因此,需要我們及時采取措施解決問題,以確保投影設備的正常運行和良好的展示效果表現。那么您知道投影融合處理器出現顏色失真或偏色,該怎么辦
    的頭像 發表于 07-31 17:09 ?489次閱讀
    盛顯科技:投影融合處理器出現顏色失真或偏色,該<b class='flag-5'>怎么辦</b>?

    大電流一體成型電感有噪音怎么辦

    電子發燒友網站提供《大電流一體成型電感有噪音怎么辦.docx》資料免費下載
    發表于 07-30 12:30 ?0次下載

    OpenVINO2024 C++推理使用技巧

    很多人都使用OpenVINO新版的C++ 或者Python的SDK,都覺得非常好用,OpenVINO2022之后的版本C++ SDK做了大量的優化與整理,已經是非常貼近開發的使用習慣與推理方式。與OpenCV的Mat對象對接方式
    的頭像 發表于 07-26 09:20 ?1435次閱讀

    C++語言基礎知識

    電子發燒友網站提供《C++語言基礎知識.pdf》資料免費下載
    發表于 07-19 10:58 ?8次下載

    C++中實現類似instanceof的方法

    函數,可實際上C++中沒有。但是別著急,其實C++中有兩種簡單的方法可以實現類似Java中的instanceof的功能。 在 C++ 中,確定對象的類型是編程中實際需求,使開發人員
    的頭像 發表于 07-18 10:16 ?859次閱讀
    <b class='flag-5'>C++</b>中實現類似instanceof的方法