zoukankan      html  css  js  c++  java
  • clang_intprt_t类型探究

    作者:玄魂工作室-钱海龙

    问题

    这篇手把手教你构建 C 语言编译器,里面有着这样的代码

    void eval() {
        int op, *tmp;
        while (1) {
            if (op == IMM)       {ax = *pc++;}                                     // load immediate value to ax
            else if (op == LC)   {ax = *(char *)ax;}                               // load character to ax, address in ax
            else if (op == LI)   {ax = *(int *)ax;}                                // load integer to ax, address in ax
            else if (op == SC)   {ax = *(char *)*sp++ = ax;}                       // save character to address, value in ax, address on stack
            else if (op == SI)   {*(int *)*sp++ = ax;}                             // save integer to address, value in ax, address on stack
        }
        ...
        return 0;
    }
    

    只看op == LC这段代码,ax是一个int类型,存放的值是char *指针类型地址,取完该地址所在的值再赋给变量ax
    但是如此写代码,vim的youcomplete插件一直报错

    那就举个例子

    //test.c
    #include <stdio.h>
    int main() {
        int a = 1;
        int p = &a;
        printf("the result is %d
    ",*((int*)p));
    }
    

    32位linux gcc v4.8.4

    试试32位gcc

    ~$ gcc test.c -o test
    test.c: In function ‘main’:
    test.c:4:13: warning: initialization makes integer from pointer without a cast [enabled by default]
         int p = &a;
    ~$ ./test
    the result is 1
    

    虽然有警告,依然能运行成功正确输出,接下来试试32位g++

    ~$ g++ test.c -o test
    ltest.c: In function ‘int main()’:
    test.c:4:14: error: invalid conversion from ‘int*’ to ‘int’ [-fpermissive]
         int p = &a;
    

    直接抛出错误  

    64位linux gcc version 5.4.0

    试试64位gcc

    ch@ch-pc:~$ gcc test.c -o test
    test.c: In function ‘main’:
    test.c:4:13: warning: initialization makes integer from pointer without a cast [-Wint-conversion]
         int p = &a;
                 ^
    test.c:5:35: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
         printf("the result is %d
    ",*((int*)p));
                                       ^
    ch@ch-pc:~$ ./test 
    段错误 (核心已转储)
    

    运行时才出错,那么试试64位g++

    ch@ch-pc:~$ g++ test.c -o test
    test.c: In function ‘int main()’:
    test.c:4:14: error: invalid conversion from ‘int*’ to ‘int’ [-fpermissive]
         int p = &a;
                  ^
    test.c:5:41: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
         printf("the result is %d
    ",*((int*)p));
    

    编译不通过
    当然-m32这种参数,就不讨论了

    初步结论

    g++编译的时候就认为是个错误,gcc32位编译可以正常运行,64位运行时报错 我们探讨一下原因,32位和64的int类型都是4个字节的,但是指针类型的大小不一致

    #include <stdio.h>
    int main() {
        int *p;
        printf("the result is %lu
    ", sizeof(p));
    }
    

    分别在32位和64位编译器(注意是编译器,64位系统也有可能有32位编译器)编译后,运行
    32位结果为"the result is 4"
    64位结果为"the result is 8"

    本质原因

    64位,gcc编译后,拿到test可执行程序,程序执行会出现段错误,现在来反汇编一下

    //test.c
    #include <stdio.h>
    int main() {
        int a = 1;
        int p = &a;
        printf("the result is %d
    ",*((int*)p));
    }
    //编译
    ~$ gcc test.c -o test
    
    // objdump反汇编命令 
    //     -S 尽可能尽可能反汇编出源代码
    //     -M 因为个人习惯问题,不太会看AT&A的汇编,还是搞成intel的来看
    // nl只是把上面的结果显示一下行数,-b 行的显示方式
    //     -ba            //显示所有行号(包括空行)
    //     -bt            //显示所有行号(但不包括空行)
    ~$ objdump -S -M intel test | nl -ba
    

    主要看一下,main函数

    1. 从138行开始看,对应着代码int a = 1,将数字1赋值给rbp栈上的-0x10处,也就是在距离bp栈的16字节处(因为0x10=16);如下图1行B(地址)处的为数字1,占四个字节,那么中间竖线就是[rbp-0xc]处
    2. 139行,将地址传给了rax寄存器,注意rax是16字节(对应题目中的指针大小),对应下图2行,rax存储的就是(A,B)
    3. 140行,对应下图3行指令中eax是rax的低位,存储的值就是B(注意B是地址)四个字节,赋值给[rbp-0xc]处四个字节B(注意B是地址),而[rbp-0xc]到[rbp-0x10]还是数字1四个字节
    4. 最主要的问题出在141行,也就是把[rbp-0xc]的值,也就是B,赋值给rax的低位,本来这个rax的低位8个字节就是B,这个没问题,问题出在64位系统的给eax(rax的低位)赋值,会影响rax的高位,高位全被置为0了. 具体论证在这里
    5. 这样在143行,对应下图5行,尝试把rax寄存器的值当成地址,去该地址取值. rax寄存器的值是(0, B)(这里面是0, B各占8个字节,对应c代码里面的指针大小,16个字节),而实际需要的地址值是(A, B).

    我们来用edb调试的结果,也跟想得一样

    有一点我上面并没有讲到,就是上图4行的 rax 过渡到上图5行的时候高位并不一定是零,因为在142行的时候,有一个指令cdqe,这是eax拓展成rax的指令,所有要根据eax的正负性来判断.也就是说,如果eax表达出来是负数,rax的高位补出来的是全f;同理eax正数的情况下,rax高位补全的才是0

    解决方案

    在c99的标准库里面有一个结构体,intptr_t可以实现编译器位数兼容性

    //头文件stdint.h
    /* Types for `void *' pointers.  */
    #if __WORDSIZE == 64
    # ifndef __intptr_t_defined
    typedef long int               intptr_t;
    #  define __intptr_t_defined
    # endif
    typedef unsigned long int    uintptr_t;
    #else
    # ifndef __intptr_t_defined
    typedef int                    intptr_t;
    #  define __intptr_t_defined
    # endif
    typedef unsigned int        uintptr_t;
    #endif
    

    上述测试代码改成这样即可

    #include <stdio.h>
    #include <stdint.h>
    int main() {
        int a = 1;
        int p = &a;
        printf("the result is %d
    ",*((int*)(intptr_t)p));
    }
    

    原始代码改成下面即可

            if(op == IMM) {
                ax = *pc++;
            } else if(op == LC) {
                ax = *(char *)(intptr_t)ax;
            } else if(op == LI) {
                ax = *(int *)(intptr_t)ax;
            } else if(op ==SC) {
                ax = *(char *)(intptr_t)*sp++ = ax;
            } else if(op == SI){
                *(int *)(intptr_t)*sp++ = ax;
            } else if(op == PUSH) {
                *--sp = ax;
            } else if(op == JMP) {
                pc = (int *)(intptr_t)*pc;
            } else if(op == JZ) {
                pc = ax ? pc + 1 : (int *)(intptr_t)*pc;
            } else if(op == JNZ) {
                pc = ax ? (int *)(intptr_t)*pc : pc + 1;
            } else if(op == CALL) {
                *--sp = (int)(intptr_t)(pc + 1);
                pc = (int *)(intptr_t)*pc;
            } else if(op == ENT) {
                *--sp = (int)(intptr_t)bp;
                bp = sp;
                sp = sp - *pc++;
            } else if(op == ADJ) {
                sp = sp + (intptr_t)*pc++;
            }
    



    参考

  • 相关阅读:
    Open source cryptocurrency exchange
    Salted Password Hashing
    95. Unique Binary Search Trees II
    714. Best Time to Buy and Sell Stock with Transaction Fee
    680. Valid Palindrome II
    Java compiler level does not match the version of the installed Java project facet.
    eclipse自动编译
    Exception in thread "main" java.lang.StackOverflowError(栈溢出)
    博客背景美化——动态雪花飘落
    java九九乘法表
  • 原文地址:https://www.cnblogs.com/xuanhun/p/6094660.html
Copyright © 2011-2022 走看看