少于 1 分钟读完

计算机没有玄学,因为它没有血肉。
但凡认为计算机玄学存在者皆是麻瓜。
——我

cdqe

现象

和儿子debug的时候遇到了计算机玄学问题:

    syscallnr = rec->nr;
    printf("syscallnr=%d\n", syscallnr);
    //WL: debug
    printf("In renderxxx\nsyscall table address: %p\n", syscalls);

    // cause seg fault
    entry = get_syscall_entry(syscallnr, false);

    // executed normally
    // entry = syscalls[syscallnr].entry;

    printf("entry: %p\n", entry);

    printf("syscall name: %s\n", entry->name);
    printf("arg1name: %s\n", entry->arg1name);

在执行上面这段代码的时候,get_syscall_entry会引起segmentation fault,然而把那一行注释掉执行的话则不会报错。然而这个函数的实现和被注释掉的那句话相比没有半毛钱的区别!实现如下:

struct syscallentry * get_syscall_entry(unsigned int callno, bool do32)
{

    //WL: do32 is useless

    printf("In getxxx\nsyscall table address: %p\n", syscalls);
    printf("In getxxx\nentry: %p\n", syscalls[callno].entry);

    return syscalls[callno].entry;
}

朔源

运行的结果是: img

两个entry的地址中,箭头标记是return之后的entry的值。可以看到return本身让值发生了变化。
这个问题十分玄学,按理说return syscalls[callno].entry这句话不应该引起任何值本身的改变才对,毕竟如果不通过调用函数而是直接修改值是不会有任何问题的。于是我们来debug。

img

首先,在return的时候,函数get_syscall_entry并没有做任何更改返回值(存放在rax)中的事情。此时的rax为: img

但是,在返回后出鬼了!注意这里的rax已经变成了奇奇怪怪的形状。然而元凶竟然是一个奇奇怪怪的cbqe指令! img

查证资料后发现其作用是对于EAXsigned extension。这也就意味着RAX的高位会被抹去,和我们观测到的现象一样。

刨根

既然知道了这是由cbqe引起的,接下来就需要看看为什么我没写的指令会被编译出来?

在查阅了大量资料后,我发现了Intel的一篇博文。其讲述了自己在Linux 64开发中踩得各种大坑。感觉其中的Example 7和我们遇到的问题十分相似。

In C, you can use functions without preliminary declaration. Let’s look at an interesting example of a 64-bit error related to this feature.

就是说C里面函数声明也能用呗。但是由于C很不知道你是怎么用的,于是就给你做了signed extension。解决方案很简单,就是在header里面重新定义这个函数即可。

如何彻底避免这种问题再次出现呢?别用C了

留下评论