Re: Syscall changes registers beyond %eax, on linux-i386

Brian Gerst
2002-09-19T17:04:54Z

Richard B. Johnson wrote:
> On Thu, 19 Sep 2002, dvorak wrote:
>
>
>>Hi,
>>
>>recently i came across a situation were on linux-i386 not only %eax was
>>altered after a syscall but also %ebx. I tracked this problem down, to
>>gcc re-using a variable passed to a function.
>>
>>This was found on a debian system with a 2.4.17 kernel compiled with gcc
>>2.95.2 and verified on another system, kernel 2.4.18 compiled with 2.95.4
>>Attached is small program to test for this 'bug'
>>
>>a syscall gets his data off the stack, the stack looks like:
>>
>>saved(edx)
>>saved(ecx)
>>saved(ebx)
>>return_addres (somewhere in entry.S)
>>
>>When the syscall is called.
>>
>>the register came there through use of 'SAVE_ALL'.
>>
>>After the syscall returns these registers are restored using RESTORE_ALL
>>and execution is transferred to userland again.
>>
>>A short snippet of sys_poll, with irrelavant data removed.
>>
>>sys_poll(struct pollfd *ufds, .. , ..) {
>> ...
>> ufds++;
>> ...
>>}
>>
>>It seems that gcc in certain cases optimizes in such a way that it changes
>>the variable ufds as placed on the stack directly. Which results in saved(ebx)
>>being overwritten and thus in a changed %ebx on return from the system call.
>>
>
>
> The 'C' compiler must make room on the stack for any local
> variables except register types. If it was doing as you state, you
> couldn't even execute a "hello world" program. Further, the local
> variables are after the return address. It would screw up the return
> address and you'd go off into hyper-space upon return.
>
>
>
>>I don't know if this is considered a bug, and if it is, from whom.
>>If it's not a bug it means low-level userland programs need to be rewritten
>>to store all registers on a syscall and restore them on return.
>>
>
>
> No. Various 'C' implementers have standardized calling methods even
> though it's not part of the 'C' standard. gcc and others assume that
> a called procedure is not going to change any segments or index registers.
> There are various optimization things, like "-fcaller-saves" where the
> called procedure can destroy anything. You may be using something that
> was wrongly compiled using that switch.
>
>
>
>>It shouldn't be a bug in gcc, since the C-standard doesn't talk about how to
>>pass variables and stuff. So it seems like a kernel(-gcc-interaction) bug.
>>
>>To solve this issue 2 solutions spring to mind
>>1) add a flag to gcc to tell it that it shouldn't do this optimization, this
>> won't work with the gcc's already out there.
>>2) When calling a syscall explicitly push all variable an extra time, since
>> the code in entry.S doesn't know the amount of variables to a syscall it
>> needs to push all theoretical 6 parameters every time, a not so nice
>> overhead.
>>
>>
>
>
> There is a bug in some other code. Try this. It will show
> that ebx is not being killed in a syscall. You can prove
> that this code works by changing ebx to eax, which will
> get destroyed and print "Broken" before exit.

The bug is only with _some_ syscalls, and getpid() is not one of them,
so your example is flawed. It happens when a syscall modifies one of
it's parameter values. The solution is to assign the parameter to a
local variable before modifying it.

--
Brian Gerst