简体   繁体   中英

Why use a function parameter 'foo' in this way: *(&foo)?

A code snippet in Linux kernel 0.12 use a function parameter like this:

int do_signal(int signr, int eax /* other parameters... */) {
    /* ... */

    *(&eax) = -EINTR;

    /* ... */
}

The purpose of the code is to put -EINTR to the memory where eax lives, but I can not tell why it won't work if just assigning to eax:

eax = -EINTR

How would the compiler make a difference between eax and *(&eax) ?

The old Linux you posted was trying to perform an very fragile hack. The function was defined like this:

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)

The function arguments don't actually represent arguments to the function (except signr ), but the values that calling function (a kernel interrupt/exception handler written in assembly) preserved on the stack before calling do_signal . The *(&eax) = -EINTR statement is meant to modify the preserved value of EAX on the stack. Similarly the statement *(&eip) = old_eip -= 2 is meant to modify the return address of the calling handler. After do_signal returns the handler pops the first 9 "arguments" off the stack restoring them to the named registers. It then executes an IRETD instruction that pops the remaining arguments off the stack and return to user mode.

Needless to say this hack is incredibly unreliable. It's dependent on the compiler generating code exactly the way they expected it to. I'm surprised it even worked the GCC compiler of the era, I doubt it was long before GCC introduced an optimization that broke it.

One possible intention could be to keep the eax variable out of a register. If we look at the C99 draft standard we see that section 6.5.3.2 Address and indirection operators says ( emphasis mine ):

The unary & operator yields the address of its operand. [...]If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted , except that the constraints on the operators still apply and the result is not an lvalue.[...]

in footnote 87 it says ( emphasis mine going forward ):

Thus, &*E is equivalent to E (even if E is a null pointer), and &(E1[E2]) to ((E1)+(E2)). It is always true that if E is a function designator or an lvalue that is a valid operand of the unary & operator, *&E is a function designator or an lvalue equal to E .

we find the following constraint on & operator :

The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.

Which makes sense since we can not take the address of a register and so by performing an address of operation they may have been trying to prevent the compiler from performing the operations completely in registers and ensure that data in specific memory locations are modified.

As ouah points out this does not prevent the compiler from optimizing what is effectively a no-op away but as documented in GCC hacks in the Linux kernel . Linux has relied on many gcc extensions and considering that 0.12 is a very old kernel gcc may have guaranteed that behavior or may have by accident reliably worked that way but I can not find any documentation that says so.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM