首页 > 代码库 > clang_intprt_t类型探究
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 testtest.c: In function ‘main’:test.c:4:13: warning: initialization makes integer from pointer without a cast [enabled by default] int p = &a;~$ ./testthe result is 1
虽然有警告,依然能运行成功正确输出,接下来试试32位g++
~$ g++ test.c -o testltest.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 testtest.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 testtest.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函数
- 从138行开始看,对应着代码int a = 1,将数字1赋值给rbp栈上的-0x10处,也就是在距离bp栈的16字节处(因为0x10=16);如下图1行,B(地址)处的为数字1,占四个字节,那么中间竖线就是[rbp-0xc]处
- 139行,将地址传给了rax寄存器,注意rax是16字节(对应题目中的指针大小),对应下图2行,rax存储的就是(A,B)
- 140行,对应下图3行指令中eax是rax的低位,存储的值就是B(注意B是地址)四个字节,赋值给[rbp-0xc]处四个字节B(注意B是地址),而[rbp-0xc]到[rbp-0x10]还是数字1四个字节
- 最主要的问题出在141行,也就是把[rbp-0xc]的值,也就是B,赋值给rax的低位,本来这个rax的低位8个字节就是B,这个没问题,问题出在64位系统的给eax(rax的低位)赋值,会影响rax的高位,高位全被置为0了. 具体论证在这里
- 这样在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_definedtypedef long int intptr_t;# define __intptr_t_defined# endiftypedef unsigned long int uintptr_t;#else# ifndef __intptr_t_definedtypedef int intptr_t;# define __intptr_t_defined# endiftypedef 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++; }
参考
- C---int和指针转换注意事项
- Disabling “cast from pointer to smaller type uint32_t” error in Clang
- Why is “cast from ‘X*’ to ‘Y’ loses precision” a hard error and what is suitable fix for legacy code
- C语言指针转换为intptr_t类型
- x86_64 registers rax/eax/ax/al overwriting full register contents [duplicate]
- CBW/CWDE/CDQE
clang_intprt_t类型探究
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。