前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >bug诞生记——const_cast引发只读数据区域写违例

bug诞生记——const_cast引发只读数据区域写违例

作者头像
方亮
发布2019-01-16 17:05:37
4540
发布2019-01-16 17:05:37
举报
文章被收录于专栏:方亮方亮

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

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

代码语言:javascript
复制
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"。

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

代码语言:javascript
复制
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属性引发的灾难。

代码语言:javascript
复制
class Base {
public:
    const char* get_name() const {
        return _name;
    }
private:
    const char* _name = "Base";
}

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

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

代码语言:javascript
复制
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指向的地址空间中。之后该方法返回

代码语言:javascript
复制
push    offset Source   ; "Derive"
mov     eax, [ebp+Dest]
push    eax             ; Dest
call    j_strcpy_0

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

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

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

代码语言:javascript
复制
.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这种试图绕过编译器的“小聪明”手段。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年09月11日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

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