• 阿里笔试题-修改虚函数表指针


    https://www.zhihu.com/question/29256578/answer/43725188

    一道阿里实习生笔试题的疑惑?

    问题:
    #include <iostream> using namespace std; class animal { protected: int age; public: virtual void print_age(void) = 0; }; class dog : public animal { public: dog() {this -> age = 2;} ~dog() { } virtual void print_age(void) {cout<<"Wang, my age = "<<this -> age<<endl;} }; class cat: public animal { public: cat() {this -> age = 1;} ~cat() { } virtual void print_age(void) {cout<<"Miao, my age = "<<this -> age<<endl;} }; int main(void) { cat kitty; dog jd; animal * pa; int * p = (int *)(&kitty); int * q = (int *)(&jd); p[0] = q[0]; pa = &kitty; pa -> print_age(); return 0; }
    输出是:
    Wang, my age = 1
    

    今天线上笔试遇到的一道题,很好奇,这几句:
    int * p = (int *)(&kitty);
    int * q = (int *)(&jd);
    p[0] = q[0];
    

    这是为什么呢?
    --------------------------------------------------------------------------------
    确实是:
    p[0] = q[0];
    
    我没有记错。

    当时读题时,看到 基类,派生类,虚函数,,我就猜到肯定是要考 多态,虚函数表这些知识,
    当时就是对这句挺疑惑:
    p[0] = q[0];
    
    因为这句可能会修改虚函数表,但也不一定阿,因为时间比较紧,就赌了一把,认为虚函数表被修改了。
    所以笔试完了,就挺困惑,宿舍熄灯后,怎么也睡不着,就拿到知乎上,想问问各位。

    说实话,真的很感谢 @蓝色 大大,这么晚还回答了

    这个是昨天笔试完,晚上11点左右提的,不能算我笔试违规吧? T_T

    好吧,我也不匿名了,感觉匿名不好,不懂就问,为啥要匿名。
    我不是 陈浩大大,只是个普通院校 大三 计算机科班生。
     
    解答:
    作者:RednaxelaFX
    链接:https://www.zhihu.com/question/29256578/answer/43725188
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    首先

    大大的答案已经把思路说得很清楚了。这是个hack,不是C++语言规范所保证的行为,而是某些C++编译器采用的C++ ABI的行为。

    放个传送门:为什么bs虚函数表的地址(int*)(&bs)与虚函数地址(int*)*(int*)(&bs) 不是同一个? - RednaxelaFX 的回答

    其次,这代码不但依赖某些C++编译器的行为,还依赖平台的指针宽度是32位。
    int * p = (int *)(&kitty);
    int * q = (int *)(&jd);
    p[0] = q[0];
    
    这几句不应该用int*,而应该用intptr_t*才对。这样才能保证拷贝的是一个指针宽度的数据,而不是一个int宽度的数据。
    • 在32位平台上,int通常是32位,而指针是32位,所以正好匹配了,程序能正常运行;
    • 在64位平台上,如果是流行的LP64模型,int是32位而指针是64位,这里实际上只拷贝了指针的一半,程序能否正常运行就看运气了。

    如果是在一个64位且小端(little endian)的平台上,那这代码拷贝的是指针的低32位。很可能会运气好能正常运行,因为dog类与cat类的vtable可能正好在内存里处于很近的位置,它们的地址的高32位可能正好相同,地址不同的地方都在低32位,这样这个程序就运气好能正常运行。
    如果是在一个64位且大端(big endian)的平台上,那这段代码拷贝的是指针的高32位,那就完全达不到效果了。

    不知道谁出的这种题⋯
    或者题主把题目的细节记错了。
    后面有回答说原本的笔试题不是p[0] = q[0],而是p[1] = q[1]。如果是这样的话那仍然只能在32位平台上能行,在64位平台上就纱布了。

    再次,这种题还有很多玩法。例如说一种简单的玩法是像这样:
    #include <iostream>
    using namespace std;
    
    class animal
    {
    protected:
      int age_;
      animal(int age): age_(age) { }
    
    public:
      virtual void print_age(void) = 0;
      virtual void print_kind() = 0;
      virtual void print_status() = 0;
    };
    
    class dog : public animal
    {
    public:
      dog(): animal(2) { }
      ~dog() { }
    
      virtual void print_age(void) {
        cout << "Woof, my age = " << age_ << endl;
      }
    
      virtual void print_kind() {
        cout << "I'm a dog" << endl;
      }
    
      virtual void print_status() {
        cout << "I'm barking" << endl;
      }
    };
    
    class cat : public animal
    {
    public:
      cat(): animal(1) { }
      ~cat() { }
    
      virtual void print_age(void) {
        cout << "Meow, my age = " << age_ << endl;
      }
    
      virtual void print_kind() {
        cout << "I'm a cat" << endl;
      }
    
      virtual void print_status() {
        cout << "I'm sleeping" << endl;
      }
    };
    
    void print_random_message(void* something) {
      cout << "I'm crazy" << endl;
    }
    
    int main(void)
    {
      cat kitty;
      dog puppy;
      animal* pa = &kitty;
    
      intptr_t* cat_vptr = *((intptr_t**)(&kitty));
      intptr_t* dog_vptr = *((intptr_t**)(&puppy));
    
      intptr_t fake_vtable[] = {
        dog_vptr[0],         // for dog::print_age
        cat_vptr[1],         // for cat::print_kind
        (intptr_t) print_random_message
      };
      *((intptr_t**) pa) = fake_vtable;
    
      pa->print_age();    // Woof, my age = 1
      pa->print_kind();   // I'm a cat
      pa->print_status(); // I'm crazy
    
      return 0;
    }
    

    直接整个vtable伪造出来然后想往里面填啥就填啥。

    至于有没有实际应用使用了题主原本写的那种代码,还真有。(但这么用的都该拖出去打pp⋯

    例如说Oracle/Sun JDK / OpenJDK里的HotSpot VM,在PermGen Removal之前,有一类叫klassOopDesc的对象是由GC管理的,但里面还嵌套包含一个Klass的子类对象,而Klass类有vptr。为了能正确管理klassOopDesc里嵌套的Klass的vptr,就有了这么个奇葩的东西:
    class Klass_vtbl
    好同学们请不要学这种例子⋯这个奇葩的结构在PermGen Removal后就移除了嗯。
  • 相关阅读:
    bugku杂项—12-16题
    bugku杂项—1-11题
    symfony3 使用命令行工具生成Entity实体所踩的坑
    symfony2学习笔记——控制器
    symfony学习笔记——路由
    在window环境下安装symfony2框架注意事项
    window环境安装composer
    HTTP,FTP异常码大全【转载】
    关于session和cookie的区别
    关于mysql的查询优化
  • 原文地址:https://www.cnblogs.com/shihuvini/p/8517655.html
Copyright © 2020-2023  润新知