> ## Content Index
> Fetch the complete content index at: https://blog.hacking.pub/llms.txt
> Use this file to discover other available public pages before exploring further.

# clang intptr_t类型
- URL: https://blog.hacking.pub/2016/11/17/clang-intptr_tlei-xing/
- Published: 2016-11-17T13:41:37.000Z
- Updated: 2016-11-23T06:20:39.000Z
- Author: chainhelen
- Tags: c语言, 指针, objdump, 逆向

## 问题

这篇[手把手教你构建 C 语言编译器](http://lotabout.me/2015/write-a-C-interpreter-2/?ref=blog.hacking.pub)，里面有着这样的代码

```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插件一直报错

![clang-intptr_t](https://blog.hacking.pub/qiniu/clang%20intptr_t%E7%B1%BB%E5%9E%8B/clang-intptr_t.png)

那就举个例子

```
//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

```sh
~$ 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++

```sh
~$ 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

```c
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函数

![clang-intptr_t-assembly.png](https://blog.hacking.pub/qiniu/clang%20intptr_t%E7%B1%BB%E5%9E%8B/clang-intptr_t-assembly.png)

1. 从138行开始看，对应着代码int a = 1，将**数字1**赋值给rbp栈上的-0x10处，也就是在距离bp栈的16字节处(因为0x10=16)；如**下图１行**，**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\]还是**数字１**四个字节
4. 最主要的问题出在141行，也就是把\[rbp-0xc\]的值，也就是**B**，赋值给rax的低位，本来这个rax的低位８个字节就是**B**，这个没问题，**问题出在64位系统的给eax（rax的低位）赋值，会影响rax的高位，高位全被置为０了**. 具体论证在[这里](http://stackoverflow.com/questions/25455447/x86-64-registers-rax-eax-ax-al-overwriting-full-register-contents?ref=blog.hacking.pub)
5. 这样在143行，对应**下图5行**，尝试把rax寄存器的值当成地址，去该地址取值. rax寄存器的值是(0, B)(这里面是0, B各占8个字节，对应c代码里面的指针大小，16个字节)，而实际需要的地址值是(A, B).

![assembly-step2.png](https://blog.hacking.pub/qiniu/clang%20intptr_t%E7%B1%BB%E5%9E%8B/assembly-step222.png)

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

![assembly-step4](https://blog.hacking.pub/qiniu/clang%20intptr_t%E7%B1%BB%E5%9E%8B/assembly-step4.png)

> 有一点我上面并没有讲到，就是**上图４行**的 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++;
        }

```

**未经授权，禁止转载**  
**作者：chainhelen**  
[本文原地址 http://blog.hacking.pub/2016/11/17/clang-intptr\_tlei-xing/](https://blog.hacking.pub/2016/11/17/clang-intptr%5Ftlei-xing/)

#### 参考

- [C---int和指针转换注意事项](http://blog.csdn.net/tingyun%5Fsay/article/details/51439136?ref=blog.hacking.pub)
- [Disabling “cast from pointer to smaller type uint32\_t” error in Clang](http://stackoverflow.com/questions/21232460/disabling-cast-from-pointer-to-smaller-type-uint32-t-error-in-clang?ref=blog.hacking.pub)
- [Why is “cast from ‘X\*’ to ‘Y’ loses precision” a hard error and what is suitable fix for legacy code](http://stackoverflow.com/questions/21574449/why-is-cast-from-x-to-y-loses-precision-a-hard-error-and-what-is-suitable?ref=blog.hacking.pub)
- [C语言指针转换为intptr\_t类型 ](http://www.cnblogs.com/Anker/p/3438480.html?ref=blog.hacking.pub)
- [x86\_64 registers rax/eax/ax/al overwriting full register contents \[duplicate\]](http://stackoverflow.com/questions/25455447/x86-64-registers-rax-eax-ax-al-overwriting-full-register-contents?ref=blog.hacking.pub)
- [CBW/CWDE/CDQE](http://www.felixcloutier.com/x86/CBW:CWDE:CDQE.html?ref=blog.hacking.pub)