计算机玄学——主要是踩的C的坑
计算机没有玄学,因为它没有血肉。
但凡认为计算机玄学存在者皆是麻瓜。
——我
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;
}
朔源
运行的结果是:
两个entry
的地址中,箭头标记是return之后的entry
的值。可以看到return本身让值发生了变化。
这个问题十分玄学,按理说return syscalls[callno].entry
这句话不应该引起任何值本身的改变才对,毕竟如果不通过调用函数而是直接修改值是不会有任何问题的。于是我们来debug。
首先,在return的时候,函数get_syscall_entry
并没有做任何更改返回值(存放在rax
)中的事情。此时的rax
为:
但是,在返回后出鬼了!注意这里的rax
已经变成了奇奇怪怪的形状。然而元凶竟然是一个奇奇怪怪的cbqe
指令!
查证资料后发现其作用是对于EAX
的signed 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了。
留下评论