bug诞生记——const_cast引发只读数据区域写违例(代码片段)

breaksoftware breaksoftware     2022-10-21     676

关键词:

        对于C++这种强类型的语言,明确的类型既带来了执行的高效,又让错误的发生提前到编译期。所以像const这类体现设计者意图的关键字,可以隐性的透露给我们它描述的对象的使用边界。它是我们的朋友,我们要学会和它相处,而不是改变它。(转载请指明出于breaksoftware的csdn博客)

        我们来看一个试图改变这个好朋友的案例

class Base 
public:
    Base() : _name("Base") 
    

    const char* get_name() const 
        return _name;
    
private:
    const char _name[32];
;

        Base类有个get_name方法返回了一个const char*。它返回的是该类的成员变量_name——即类名"base"。

        现在有个同学试图继承该类,于是他增加了如下代码

class Derive : public Base 
public:
    Derive() 
        const char* base_name = Base::get_name();
        strcpy(const_cast<char*>(base_name), "Derive");
    
;

        Derive的构造函数调用了Base的get_name方法获取了一个const char*变量base_name。他试图去修改这个地址空间的数据,让其填充自己类的名称"Derive"。由于strcpy需要第一个参数是char*型的,于是他使用const_cast关键字“剥夺”了base_name的const属性,让编译通过。

        目前这段代码还可以正确执行,但是之后一个同学对Base类的优化,将彻底触发修改const属性引发的灾难。

class Base 
public:
    const char* get_name() const 
        return _name;
    
private:
    const char* _name = "Base";

        这样编译出来的代码,最终在执行期会崩溃。

        先分析Base的get_name方法的反汇编代码

push    ebp
mov     ebp, esp
sub     esp, 0CCh
push    ebx
push    esi
push    edi
push    ecx
lea     edi, [ebp+var_CC]
mov     ecx, 33h
mov     eax, 0CCCCCCCCh
rep stosd
pop     ecx
mov     [ebp+var_8], ecx
mov     eax, [ebp+var_8]
mov     dword ptr [eax], offset aBase ; "Base"
mov     eax, [ebp+var_8]
pop     edi
pop     esi
pop     ebx
mov     esp, ebp
pop     ebp
retn

        第15行将"Base"字符串的地址保存到寄存器eax指向的地址空间中。之后该方法返回

push    offset Source   ; "Derive"
mov     eax, [ebp+Dest]
push    eax             ; Dest
call    j_strcpy_0

        调用了strcpy方法,其中eax还是"Base"字符串的首地址。

        崩溃出现在strcpy方法中,出错的地址也是“Base"字符串的首地址。

        为什么写这个地址会出错,我们看下get_name中aBase的地址

.rdata:0041DB30 aBase           db 'Base',0             ; DATA XREF: sub_412460+26↑o
.rdata:0041DB35                 db    0
.rdata:0041DB36                 db    0
.rdata:0041DB37                 db    0
.rdata:0041DB38                 db    0

        可以见到这个地址保存在rdata区域。rdata是readonly-data的意思,即这块地址空间是只读的,所以往其中写数据会报错。

        由于我们在修改后的Base类中,让成员变量_name指向了一个字面量。这个字面量作为常量,它会保存在PE/ELF文件的只读数据区域。关于什么信息会保存在只读区域,以及还有什么其他区域,大家可以在网上搜索PE/ELF文件格式的说明。

        最后从语义角度来说,可以认为Derive的作者违背了编译器对关键字const的约束,即违背了一种约定导致的。所以我们尽量别用const_cast这种试图绕过编译器的“小聪明”手段。

bug诞生记——不定长参数隐藏的类型问题(代码片段)

    这个bug的诞生源于项目中使用了一个开源C库。由于对该C库API不熟悉,一个不起眼的错误调用,导致一系列诡异的问题。最终经过调试,我们发现发生了内存覆盖问题。为了直达问题根节,我将问题代码简化... 查看详情

bug诞生记——不定长参数隐藏的类型问题(代码片段)

    这个bug的诞生源于项目中使用了一个开源C库。由于对该C库API不熟悉,一个不起眼的错误调用,导致一系列诡异的问题。最终经过调试,我们发现发生了内存覆盖问题。为了直达问题根节,我将问题代码简化... 查看详情

bug诞生记——信号(signal)处理导致死锁(代码片段)

    这个bug源于项目中一个诡异的现象:代码层面没有明显的锁的问题,但是执行时发生了死锁一样的表现。我把业务逻辑简化为:父进程一直维持一个子进程。(转载请指明出于breaksoftware的csdn博客)   ... 查看详情

bug诞生记——信号(signal)处理导致死锁(代码片段)

    这个bug源于项目中一个诡异的现象:代码层面没有明显的锁的问题,但是执行时发生了死锁一样的表现。我把业务逻辑简化为:父进程一直维持一个子进程。(转载请指明出于breaksoftware的csdn博客)   ... 查看详情

记一个windows命令行引发的bug

    文章来源:http://www.51testing.com/html/30/n-3724330.html?  2018年刚开始就踩了一个很懵逼的雷,居然还是关于Windows的命令行的。  背景  背景是这样的,在开发Lavascli的过程中,需要通过开发者选定的模版类型... 查看详情

bug诞生记——隐蔽的指针偏移计算导致的数据错乱(代码片段)

    C++语言为了兼容C语言,做了很多设计方面的考量。但是有些兼容设计产生了不清晰的认识。本文就将讨论一个因为认知不清晰而导致的bug。(转载请指明出于breaksoftware的csdn博客)classBasepublic:Base()=defau... 查看详情

iOS:将数据添加到 BLE 特征引发错误要求只读特征

】iOS:将数据添加到BLE特征引发错误要求只读特征【英文标题】:iOS:AddingdatatoaBLEcharacteristicthrowingerroraskingforread-onlycharacteristic【发布时间】:2015-09-2415:09:01【问题描述】:我有一个iOSBLE服务可以做广告并且可以连接,但现在我... 查看详情

leafage诞生记(二nuxt.js如何在组件和页面请求数据)

本文个人博客地址:https://www.abeille.top/posts/detail/213489UI上一篇创建了前端工程,接下来开始写代码。因为初始化项目,只是最小的一些基本依赖,再开发中,需要一些第三方的库来支持,比如样式/组件库。在开发Leafage网站的过... 查看详情

我为什么还要造轮子?monk.ui诞生记

...端数据交互的问题,导致数据绑定非常难!,所以Monk.UI诞生了。 Monk.UI目前只是一个表单美化工具,非常小巧,适合PC使用 预览地址:http://www.baisoft.org/public/monkui 查看详情

apachedolphinscheduler诞生记

ApacheDolphinScheduler诞生记DolphinScheduler,简称”DS”,中文名“小海豚调度”(海豚聪明、人性化,又左右脑可互相换班,终生不用睡觉)。希望DolphinScheduler就像它的名字一样,成为一个“开箱即用”的灵活易... 查看详情

javascript世界万物诞生记

JavaScript世界万物诞生记来源 https://zhuanlan.zhihu.com/p/22989691一.无中生有起初,什么都没有。造物主说:没有东西本身也是一种东西啊,于是就有了null: 现在我们要造点儿东西出来。但是没有原料怎么办?有一个声音说:... 查看详情

内存的使用:栈区堆区静态区只读区

内存的使用感觉好乱啊,需要整理一下!于是参考C++primer与网上资源,整理如下:一、综述:内存中的栈区分配的是局部变量空间;堆区是向上增长的用于分配程序员申请的内存空间(比如new申请的动态内存),注意它与数据结... 查看详情

javascript世界万物诞生记

作者:manxisuo链接:https://zhuanlan.zhihu.com/p/22989691来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。一.无中生有起初,什么都没有。造物主说:没有东西本身也是一种东西啊,于是就有了null... 查看详情

mpeg诞生记

点击上方“LiveVideoStack”关注我们作者|LeonardoChiariglione翻译|Alex技术审校 | 李忠、张贤国MPEG 视 野#004#纯从逻辑上来讲,MPEG本不应该存在。 20世纪80年代,媒体标准化这个“银河帝国”被牢牢掌握在ITU(视频通信和... 查看详情

rtp诞生记

...者之一RonFrederick将为我们讲述这个如此重要的协议是如何诞生的。01—前因1992年10月,我开始试验SunVideoPix的图像采集卡,因为我打算基于IP组播写一个网络视频会议工具。该工具以vat(由L 查看详情

漫画|揭密微信诞生记

据民间流传微信的诞生源于张小龙因得一扫地高僧指点,才有了微信的诞生,而这其中也少不了程序员们的付出,一起来看看它的故事吧~本期漫画灵感来源于《微信第1行代码曝光!》文章,部分情节仅为民间流传或虚构。篇... 查看详情

linux诞生记

大家好,我叫Linux,是一名计算机的“大管家”,日常工作是指挥CPU、内存、硬盘、键盘、鼠标、显示器等设备有条不紊地运行起来。形象地讲,我是底层硬件与用户沟通的桥梁。用户可以通过我输入命令,我负责对命令... 查看详情

(转载)javascript世界万物诞生记

一.无中生有起初,什么都没有。造物主说:没有东西本身也是一种东西啊,于是就有了null:现在我们要造点儿东西出来。但是没有原料怎么办?有一个声音说:不是有null嘛?另一个声音说:可是null代表无啊。造物主说:那就... 查看详情