跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • Online Tools
  • 用户
  • 群组
折叠
品牌标识

D2Learn Forums

妈耶厥了妈

妈耶厥了

@妈耶厥了
关于
帖子
4
主题
3
群组
0
粉丝
1
关注
0

帖子

最新 最佳 有争议的

  • 离散数学-0x00
    妈耶厥了妈 妈耶厥了

    本系列博客作为我学习的一个笔记,注重于代码实现,本人非数学专业,证明能力非常弱,本文掺杂着大量我自己的理解,如有数学大佬光顾,请尽情指正,谢谢
    本系列默认大家都学会了C/C++基本语法

    集合

    书上的定义

    集合是指具有某种特定性质的不同对象无序聚集成的一个整体。
    集合中的每一个对象称为集合的元素;
    通常用大写字母表示集合;
    用小写字母表示集合中的元素。
    

    也就是说把一些东西放到一起叫做集合(个人理解)

    代码实现

    #include <string>
    #include <vector>
    #include <stdint.h>
    #include <iostream>
    
    // 抽象基类,定义集合元素的基本接口
    class elmBase
    {
    public:
        // 将元素转换为字节数组的纯虚函数(序列化基础)
        virtual std::vector<uint8_t> getUint8Array() const = 0;
    
        // 默认比较运算符,通过字节数组逐字节比较
        virtual int operator==(const elmBase &other)
        {
            std::vector<uint8_t> thisDate = getUint8Array();
            std::vector<uint8_t> otherDate = other.getUint8Array();
            if (thisDate.size() != otherDate.size())
                return 0;
            for (int i = 0; i < thisDate.size(); i++)
            {
                if (thisDate[i] != otherDate[i])
                    return 0;
            }
            return 1;
        }
    
        // 转换为字符串表示的纯虚函数
        virtual std::string toString() = 0;
    
        // 虚析构函数确保正确释放派生类对象
        virtual ~elmBase()
        {
        }
    };
    
    // 特殊元素类型,始终返回比较不成立
    class alwaysFalse : public elmBase
    {
    public:
        std::vector<uint8_t> getUint8Array() const override
        {
            return std::vector<uint8_t>(); // 返回空数组
        }
    
        std::string toString() override
        {
            return {}; // 返回空字符串
        }
    
        // 重载运算符始终返回false,用于占位符场景
        virtual int operator==(const elmBase &other) override
        {
            return 0;
        }
        ~alwaysFalse()
        {
        }
    };
    
    // 哈希表节点结构,使用链表法解决冲突
    using elmNode = struct elmNode
    {
        elmBase *elm;  // 元素指针
        elmNode *next; // 下一个节点指针
    };
    
    // 节点工厂函数,创建新节点并初始化元素
    elmNode *elmNodeFactory(elmBase *elm)
    {
        elmNode *newNode = new elmNode();
        newNode->elm = elm;
        newNode->next = nullptr;
        return newNode;
    }
    
    // 自定义集合类,基于哈希表实现
    class set
    {
    private:
        uint32_t prime;   // 哈希表大小(通常选择质数)
        elmNode **setMap; // 哈希桶数组
    
        // 初始化哈希表结构
        void inline _init_(uint32_t prime)
        {
            this->prime = prime;
            this->setMap = new elmNode *[prime];
            // 初始化每个桶为alwaysFalse哨兵节点
            for (uint32_t i = 0; i < prime; ++i)
            {
                this->setMap[i] = elmNodeFactory(new alwaysFalse());
            }
        }
    
    protected:
        // 查找元素对应的哈希桶
        elmNode **__find__(elmBase *elm)
        {
            uint32_t hash = rotatingHash(elm->getUint8Array(), this->prime);
            return &(this->setMap[hash]); // 返回桶的指针
        }
    
        /**
         * 通用链表操作函数
         * @param link 链表头指针的指针
         * @param elm 目标元素
         * @param cmp 自定义比较函数,返回true时停止遍历
         * @param finally 遍历结束后的处理函数(插入等操作)
         * @return 找到的节点或操作结果节点
         */
        elmNode *__link__(elmNode **link, elmBase *elm,
                          bool (*cmp)(elmNode *, elmBase *),
                          elmNode *(*finally)(elmNode **, elmNode *, elmBase *) = nullptr)
        {
            elmNode *linkPriv = *link;
            elmNode *linkCurrent = linkPriv;
    
            // 遍历链表查找元素
            while (linkCurrent != nullptr)
            {
                if (cmp(linkCurrent, elm))
                {
                    return linkCurrent;
                }
                linkPriv = linkCurrent;
                linkCurrent = linkCurrent->next;
            }
    
            // 执行最终处理函数(如插入新节点)
            if (finally != nullptr)
            {
                return finally(link, linkPriv, elm);
            }
            return nullptr;
        }
    
    public:
        // 构造函数初始化哈希表
        set(uint32_t prime) { _init_(prime); }
        ~set()
        {
            for (uint32_t i = 0; i < this->prime; ++i)
            {
                elmNode *link = this->setMap[i];
                while (link != nullptr)
                {
                    elmNode *tmp = link;
                    link = link->next;
                    if (tmp != nullptr)
                    {
                        if (tmp->elm != nullptr)
                        {
                            std::cout << "delete " << tmp->elm->toString() << std::endl;
                            delete tmp->elm;
                        }
                        delete tmp;
                    }
                }
            }
        }
        // 旋转哈希算法实现
        static uint32_t rotatingHash(std::vector<uint8_t> key, uint32_t prime)
        {
            uint32_t hash = key.size();
            for (uint32_t i = 0; i < key.size(); ++i)
                hash = (hash << 4) ^ (hash >> 28) ^ (uint32_t)(key[i]);
            return (hash % prime);
        }
    
        // 添加元素到集合
        bool add(elmBase *elm, bool force = false)
        {
            elmNode **node = __find__(elm);
            auto finally = [](elmNode **node, elmNode *linkPriv, elmBase *elm) -> elmNode *
            {
                // 插入新节点到链表末尾
                if (linkPriv->next == nullptr)
                {
                    linkPriv->next = elmNodeFactory(elm);
                }
                return nullptr;
            };
    
            // 使用lambda作为比较函数
            elmNode *targetNode = this->__link__(node, elm, [](elmNode *node, elmBase *tag) -> bool
                                                 { return *(node->elm) == *tag; }, finally);
    
            return targetNode == nullptr; // 返回是否插入成功
        }
    
        // 检查元素是否存在
        bool get(elmBase *elm)
        {
            elmNode **node = __find__(elm);
            elmNode *targetNode = this->__link__(node, elm,
                                                 [](elmNode *node, elmBase *tag) -> bool
                                                 {
                                                     return *(node->elm) == *tag;
                                                 });
            return targetNode != nullptr;
        }
    
        // 移除集合中的元素
        bool remove(elmBase *elm)
        {
            elmNode **node = __find__(elm);
            elmNode *ret = this->__link__(node, elm,
                                          [](elmNode *current, elmBase *tag) -> bool
                                          {
                                              // 查找前驱节点执行删除
                                              if (current->next != nullptr && *(current->next->elm) == *tag)
                                              {
                                                  elmNode *tmp = current->next;
                                                  current->next = current->next->next;
                                                  delete tmp->elm; // 释放元素内存
                                                  delete tmp;      // 释放节点内存
                                                  return true;
                                              }
                                              return false;
                                          });
            return ret != nullptr;
        }
    
        // 调试用打印函数,显示哈希表结构
        void print()
        {
            std::cout << "[HashTable Structure]" << std::endl;
            for (int i = 0; i < this->prime; i++)
            {
                std::cout << "Bucket " << i << ": ";
                elmNode *node = this->setMap[i];
                while (node != nullptr)
                {
                    std::cout << node->elm->toString() << " -> ";
                    node = node->next;
                }
                std::cout << "NULL" << std::endl;
            }
        }
    };
    
    // 数值类型元素实现
    class number : public elmBase
    {
        int data;
    
    public:
        number(int data) : data(data) {}
    
        std::vector<uint8_t> getUint8Array() const override
        {
            // 将整型转换为字节数组
            return std::vector<uint8_t>(
                reinterpret_cast<const uint8_t *>(&data),
                reinterpret_cast<const uint8_t *>(&data) + sizeof(data));
        }
    
        std::string toString() override
        {
            return std::to_string(data);
        }
        ~number()
        {
        }
    };
    
    // 字符串类型元素实现
    class string : public elmBase
    {
        std::string data;
    
    public:
        string(std::string data) : data(data) {}
    
        std::vector<uint8_t> getUint8Array() const override
        {
            // 将字符串内容转换为字节数组
            return std::vector<uint8_t>(data.begin(), data.end());
        }
    
        std::string toString() override
        {
            return data;
        }
        ~string()
        {
        }
    };
    

    集合与元素的关系

    若A表示一个集合,a是集合A中的元素,记作aA,读作a属于A;
    若a不是集合A中的元素,则记作aA,读作a不属于A。
    

    未完待续


  • C 0x01(C语言是如何进行函数调用的)
    妈耶厥了妈 妈耶厥了

    在上一篇文章中我说错了一件事,其实没有pc寄存器,pc全称是program counter (程序计数器),他是一个抽象的概念,在x86/x64下是ip的数值(也不是很准确,在以后的文章中我们不必纠结这些事情都管他叫做PC)

    • rbp 寄存器大小
    • pc 寄存器大小

    在函数调用的时候会在栈中记录下函数调用后下一个指令的地址(pc),还会记录栈基址(rbp),这些数据记录在哪里?肯定就是内存里啦!我们再仔细看一眼汇编代码

    //C
    int testfunc(){
        int a=0;
    }
    
    int testfunc2()
    {
        testfunc();
    }
    
    
    //asm
    testfunc:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR -4[rbp], 0
            nop
            pop     rbp
            ret
    testfunc2:
            push    rbp
            mov     rbp, rsp
            mov     eax, 0
            call    testfunc
            nop
            pop     rbp
            ret
    

    看汇编代码我们是不是看不哪里压栈了,这时候就要介绍一下call指令的执行过程

    • 首先call指令会将PC入栈
    • 然后会有一个jmp跳转到要执行的地址
      说起来可能很抽象,下面用一张图来说明他的过程
      192f617c-e6cb-4f33-9408-d58f907624f4-图片.png
      这张图片应该就可以很清晰地描绘call指令都干了些啥了吧!
      由此可见,在函数调用的过程中程序的返回地址存在栈栈中,也就是栈上的数据会影响程序的运行过程.试想如果我们修改了栈上特定位置的数值,并修改成特定的数值,那么我们就可以让程序走到意想不到的地方(栈溢出).

    32位(x86情况下)

    先上代码

    int f=0;
    int c=0;
    
    int testfunc(){
        int a=0;
        f=1;
        return c;
    }
    
    int testfunc2()
    {
        testfunc();
    }
    
    f:
            .zero   4
    c:
            .zero   4
    testfunc:
            push    ebp
            mov     ebp, esp
            sub     esp, 16
            call    __x86.get_pc_thunk.ax
            add     eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
            mov     DWORD PTR -4[ebp], 0
            mov     DWORD PTR f@GOTOFF[eax], 1
            mov     eax, DWORD PTR c@GOTOFF[eax]
            leave
            ret
    testfunc2:
            push    ebp
            mov     ebp, esp
            call    __x86.get_pc_thunk.ax
            add     eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
            call    testfunc
            nop
            pop     ebp
            ret
    __x86.get_pc_thunk.ax:
            mov     eax, DWORD PTR [esp]
            ret
    

    我们发现多了一个这个函数 __x86.get_pc_thunk.ax 这个是干什么的呢?我们来分析一下
    在testfunc2中有这样一条指令

    call    __x86.get_pc_thunk.ax
    

    首先call指令会将PC寄存器入栈,然后在 __x86.get_pc_thunk.ax 函数中进行了

      eax, DWORD PTR [esp]
    

    这个操作,esp是栈指针指向的是栈顶部也就是刚刚push的数据也就是PC寄存器的数值了,所以说这个函数是获取下一条指令的地址的,原因是因为x86架构下没有获取PC寄存器的指令只能用这种方式获取了.

    PS:为什么要这么做

    因为有一个功能叫做PIE(地址随机化)这个可以有效增加栈溢出导致REC风险
    接着来看

    add     eax, OFFSET FLAT:_GLOBAL_OFFSET_TABLE_
    

    这个 FLAT:GLOBAL_OFFSET_TABLE 是全局静态变量表所对应add指令的偏移量,这个是在编译时期确定的,所以这个代码的意思是找到全局静态变量表的地址并储存到eax寄存器中,为了方便我在下文分析函数调用的时候会关闭pie得到相对简单的汇编代码

    int testfunc(int g){
        g=8;
    }
    
    int testfunc2()
    {
        testfunc(2);
    }
    
    
    testfunc:
            push    ebp
            mov     ebp, esp
            mov     DWORD PTR [ebp+8], 8
            nop
            pop     ebp
            ret
    testfunc2:
            push    ebp
            mov     ebp, esp
            push    2
            call    testfunc
            add     esp, 4
            nop
            leave
            ret
    

    可以看到在x86下只有栈传参没有寄存器传参,其余的和x64下的是相同的

    PS:如果你想复现的话记得加入-m32 --no-pie这两个编译参数 一个是目标平台是32位的,一个是不使用pie


  • C 0x00(C语言是如何进行函数调用的)
    妈耶厥了妈 妈耶厥了

    @sunrisepeak 有道理,完善了


  • C 0x00(C语言是如何进行函数调用的)
    妈耶厥了妈 妈耶厥了

    C语言是如何进行函数调用的,换句话说C语言进行函数调用时都发生了些什么

    先写一段简单的C语言程序(不传参)

    #include <iostream>
    int test(){
        int a=0;
        return 0;
    }
    int main()
    {
        test();
    }
    

    这里我们简单的调用了一个函数test,我们对其进行反汇编,查看汇编代码,我用的编译器是gcc,使用clang会有不同的结果但都是大同小异

    test():
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR -4[rbp], 0
            mov     eax, 0
            pop     rbp
            ret
    main:
            push    rbp
            mov     rbp, rsp
            call    test()
            mov     eax, 0
            pop     rbp
            ret
    

    首先我们知道,rbp是栈的基指针,rsp是栈的顶部的指针。人话说rsp、rbp这两个指针夹着栈。
    在调用的时候执行了一条指令

    push rbp
    

    也就是把rbp入栈,就是先要把rbp的值暂时存起来,以后要用到。接着是

    mov rbp, rsp
    

    将rsp赋值给rbp,也就是将rbp指针移动至rsp。这样做是为了给test函数开辟一个属于他自己的栈。
    我会搞一个动画、可能会直观一些

    首先说明32位、64位下的函数传参有一些不同

    传参的情况下(64位)

    int test(int b,int c,int d,int e,int f, int g,int h,int i){
        b=1;
        c=2;
        d=3;
        e=4;
        f=5;
        g=6;
        h=7;
        i=8;
    }
    int main()
    {
        int b=0;   
        test(1,2,3,4,5,6,7,8);
    }
    

    汇编如下

    test:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR -4[rbp], edi
            mov     DWORD PTR -8[rbp], esi
            mov     DWORD PTR -12[rbp], edx
            mov     DWORD PTR -16[rbp], ecx
            mov     DWORD PTR -20[rbp], r8d
            mov     DWORD PTR -24[rbp], r9d
            mov     DWORD PTR -4[rbp], 1
            mov     DWORD PTR -8[rbp], 2
            mov     DWORD PTR -12[rbp], 3
            mov     DWORD PTR -16[rbp], 4
            mov     DWORD PTR -20[rbp], 5
            mov     DWORD PTR -24[rbp], 6
            mov     DWORD PTR 16[rbp], 7
            mov     DWORD PTR 24[rbp], 8
            nop
            pop     rbp
            ret
    main:
            push    rbp
            mov     rbp, rsp
            sub     rsp, 16
            mov     DWORD PTR -4[rbp], 0
            push    8
            push    7
            mov     r9d, 6
            mov     r8d, 5
            mov     ecx, 4
            mov     edx, 3
            mov     esi, 2
            mov     edi, 1
            call    test
            add     rsp, 16
            mov     eax, 0
            leave
            ret
    

    可以看到传参的顺序是从右到左的,至于为什么要这样传参是因为栈的性质,先进后出第一个参数最后入栈,那么取出的时候是第一个取出的,这样设计的话就可以支持可变参数了。
    然后在看这一段

            mov     r9d, 6
            mov     r8d, 5
            mov     ecx, 4
            mov     edx, 3
            mov     esi, 2
            mov     edi, 1
    

    这是主调函数(就是调用其他函数的那个函数)把参数放到了寄存器里,是这样的,在64位C程序中函数调用优先使用寄存器(快)64位CPU的寄存器很多的可以看到顺序

    • edi(rdi)
    • esi(rsi)
    • edx(rdx)
    • ecx(rcx)
    • r8d
    • r9d
      如果参数更多的话,使用栈传参。
      但是即使他用了寄存器传参他也一定会放到栈里的,这个操作是在test函数里实现的
    test:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR -4[rbp], edi
            mov     DWORD PTR -8[rbp], esi
            mov     DWORD PTR -12[rbp], edx
            mov     DWORD PTR -16[rbp], ecx
            mov     DWORD PTR -20[rbp], r8d
            mov     DWORD PTR -24[rbp], r9d
    

    这一段。到这里你一定会感到疑惑🤔,为什么

            mov     DWORD PTR 16[rbp], 7
            mov     DWORD PTR 24[rbp], 8
    

    这两行和其他两行不一样的说
    都是int 前面的占4个字节,这两个占8个字节,并且前面的是减,后面的是加。
    然后还是从16开始的,好奇怪!

    思考

    • rbp寄存器的大小.
    • pc寄存器大小.
    • 再想想为什么函数调用后能回到调用它的地方.

    如果想通了这两件事,问题就迎刃而解了,如果没想明白,下一篇文章将会解答
    至于为什么下面是占据8个字节全因为push指令,他一次会压入8个字节(64位cpu),所以就要配合人家
    然后为什么上面是4个字节,因为我这个编译器下int占据4个字节。有可能其他编译器64位程序中int 占据8个字节

    传参的情况下(32位)

    未完待续。。。

  • 登录

  • 没有帐号? 注册

  • 登录或注册以进行搜索。
d2learn forums Powered by NodeBB
  • 第一个帖子
    最后一个帖子
0
  • 版块
  • 最新
  • 标签
  • 热门
  • Online Tools
  • 用户
  • 群组