为什么以这种方式使用函数参数’foo’:*(&foo)?

Linux内核0.12中的代码片段使用如下函数参数:

int do_signal(int signr, int eax /* other parameters... */) { /* ... */ *(&eax) = -EINTR; /* ... */ } 

代码的目的是将-EINTR放到eax所在的内存中,但是我不知道为什么如果只分配给eax它将不起作用:

 eax = -EINTR 

编译器如何在eax*(&eax)之间产生差异?

一种可能的意图是将eax变量保持在寄存器之外。 如果我们看一下C99草案标准,我们会看到第6.5.3.2地址和间接运营商 所说的( 强调我的 ):

一元&运算符产生其操作数的地址。 […]如果操作数是一元*运算符的结果,那么运算符和&运算符都不会被计算,结果就好像都被省略了 ,除了对运算符的约束仍然适用且结果不是一个左值。[…]

在脚注87中说( 强调我的前进 ):

因此,&* E等效于E(即使E是空指针),并且&(E1 [E2])等于((E1)+(E2))。 如果E是函数指示符或左值是一元&运算符的有效操作数,则总是如此,*&E是函数指示符或等于E的左值

我们在& operator上找到以下约束:

一元&运算符的操作数应该是函数指示符,[]或一元*运算符的结果,或者是一个左值,它指定一个不是位字段的对象,并且不用寄存器存储类说明符声明。

这是有道理的,因为我们不能获取寄存器的地址,因此通过执行操作地址,他们可能一直试图阻止编译器完全在寄存器中执行操作并确保修改特定存储器位置中的数据。

正如ouah所指出的,这并不妨碍编译器优化实际上无操作的东西 ,但正如Linux内核中的GCC hacks中所记录的那样。 Linux依赖于许多gcc扩展,并且考虑到0.12是一个非常古老的内核, gcc可能已经保证了这种行为,或者可能意外地以这种方式可靠地工作但是我找不到任何说明的文档。

您发布的旧Linux试图执行非常脆弱的黑客攻击。 该函数定义如下:

 int do_signal(long signr,long eax,long ebx, long ecx, long edx, long orig_eax, long fs, long es, long ds, long eip, long cs, long eflags, unsigned long * esp, long ss) 

函数参数实际上并不代表函数的参数( signr除外),但调用函数(在程序集中编写的内核中断/exception处理程序)的值在调用do_signal之前保留在堆栈中。 *(&eax) = -EINTR语句用于修改堆栈上EAX的保留值。 类似地,语句*(&eip) = old_eip -= 2意味着修改调用处理程序的返回地址。 在do_signal返回之后,处理程序从堆栈中弹出前9个“参数”,将它们恢复到指定的寄存器。 然后它执行一个IRETD指令,从堆栈中弹出剩余的参数并返回用户模式。

毋庸置疑,这种黑客行为令人难以置信地不可靠。 它依赖于编译器生成完全符合预期的代码。 我很惊讶它甚至在这个时代的GCC编译器中工作,我怀疑它早在GCC引入了一个破坏它的优化之前。