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\n",*((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\n",*((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\n",*((int*)p));

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

初步结论

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

#include <stdio.h>
int main() {
    int *p;
    printf("the result is %lu\n", 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\n",*((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\n",*((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++;
        }



参考



本文转自玄魂博客园博客,原文链接:http://www.cnblogs.com/xuanhun/p/6094660.html,如需转载请自行联系原作者
目录
相关文章
|
29天前
|
存储 缓存 算法
【CMake 基础教程 】深入理解CMake变量:类型、原理及最佳实践
【CMake 基础教程 】深入理解CMake变量:类型、原理及最佳实践
66 0
|
28天前
|
安全 程序员 编译器
【C/C++ 泛型编程 进阶篇 Type traits 】C++类型特征探究:编译时类型判断的艺术
【C/C++ 泛型编程 进阶篇 Type traits 】C++类型特征探究:编译时类型判断的艺术
169 1
|
29天前
|
设计模式 安全 编译器
介绍GCC8 中少数几个能用的C++20 特性
介绍GCC8 中少数几个能用的C++20 特性
16 4
|
5月前
|
设计模式 安全 机器人
学习Boost二:从附录3来看编码习惯
状态设计模式则是更加上一层,从具体的代码逻辑层次调升到了面对对象实现方式,它有更好的可扩展性(增加继承子类),更好的私密性(单独类内管理,彼此分割),更贴合低耦合、高内聚的方式。
24 0
|
IDE Unix 编译器
关于编译的重要概念总结
关于编译的重要概念总结
3333 0
关于编译的重要概念总结
|
安全 容器
Boost库学习笔记(四)any类型
Boost库学习笔记(四)any类型
137 0
Boost库学习笔记(四)any类型
|
前端开发 IDE 编译器
LLVM编译器前端 Clang 简介
昨天晚上安装rails的开发环境,被ruby的编译搞的有点崩溃。下载的ruby的源码不能用系统自带的gcc -4.21编译,也不能用系统自带的clang进行编译,必须下载并使用gcc -4.2进行编译才能通过。今天稍微看看编译器的一些背景。
489 0
LLVM编译器前端 Clang 简介
|
C++ 编译器 C语言
带你读《LLVM编译器实战教程》之二:外部项目
本书的前半部分将向您介绍怎么样去配置、构建、和安装LLVM的不同软件库、工具和外部项目。接下来,本书的后半部分将向您介绍LLVM的各种设计细节,并逐步地讲解LLVM的各个编译步骤:前段、中间表示(IR)、后端、即时编译(JIT)引擎、跨平台编译和插件接口。本书包含有大量翔实的示例和代码片段,以帮助读者平稳顺利的掌握LLVM的编译器开发环境。
|
Unix
深入研究Clang(九) Clang代码阅读之打log读流程2
继续上一篇,同样的hello.c,同样的执行过程,只不过继续添加了一些log信息,而且对代码进行了更近一步的挖掘。先看输入和输出的log信息(前半部分): shining@shining-VirtualBox:~/llvm-3.
1417 0