This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
Re: [PATCH v2 1/3] posix: Remove dynamic memory allocation from execl{e,p}
- From: Rasmus Villemoes <rv at rasmusvillemoes dot dk>
- To: libc-alpha at sourceware dot org
- Date: Sun, 07 Feb 2016 22:28:16 +0100
- Subject: Re: [PATCH v2 1/3] posix: Remove dynamic memory allocation from execl{e,p}
- Authentication-results: sourceware.org; auth=none
- References: <1454343665-1706-1-git-send-email-adhemerval dot zanella at linaro dot org> <1454343665-1706-2-git-send-email-adhemerval dot zanella at linaro dot org> <alpine dot DEB dot 2 dot 10 dot 1602011648030 dot 2674 at digraph dot polyomino dot org dot uk> <20160202163335 dot GJ9349 at brightrain dot aerifal dot cx>
On Tue, Feb 02 2016, Rich Felker <dalias@libc.org> wrote:
> On Mon, Feb 01, 2016 at 04:52:15PM +0000, Joseph Myers wrote:
>> On Mon, 1 Feb 2016, Adhemerval Zanella wrote:
>>
>> > + char *argv[argc+1];
>> > + va_start (ap, arg);
>> > + argv[0] = (char*) arg;
>> > + for (i = 1; i < argc; i++)
>> > + argv[i] = va_arg (ap, char *);
>> > + argv[i] = NULL;
>>
>> I don't see how you're ensuring this stack allocation is safe (i.e. if
>> it's too big, it doesn't corrupt memory that's in use by other threads).
>
> There's no obligation to. If you pass something like a million
> arguments to a variadic function, the compiler will generate code in
> the caller that overflows the stack before the callee is even reached.
> The size of the vla used in execl is exactly the same size as the
> argument block on the stack used for passing arguments to execl from
> its caller, and it's nobody's fault but the programmer's if this is
> way too big. It's not a runtime variable.
This is true, and maybe it's not worth the extra complication, but if
we're willing to make arch-specific versions of execl and execle we can
avoid the double stack use and the time spent copying the argv
array. That won't remove the possible stack overflow, of course, but
then it'll in all likelihood happen in the user code and not glibc.
On i386 it's pretty straight-forward since all args are already passed
on the stack, while on x86-64 we can, roughly speaking, make a local
change of the ABI by just stashing the return address somewhere else so
that we can prepend the few passed-by-register to the stack-passed
parameters. I suppose something similar is possible on most other
architectures.
Something like this passes some quick testing (nevermind the z_ prefix;
I just used that to avoid any clashes with the actual functions).
i386:
.globl z_execl ; .align 4,0x90 ; z_execl:
pushl z_environ
lea 0x0c(%esp), %eax
push %eax
pushl 0x0c(%esp)
call z_execve
add $0x0c, %esp
ret
.type z_execl, @function ; .size z_execl, .-z_execl
.globl z_execle ; .align 4,0x90 ; z_execle:
lea 0x0c(%esp), %eax // eax = address of first vararg (&argv[1])
movl (%eax), %edx // we'll test this
1: add $0x4, %eax // move on to the next word
test %edx,%edx // have we found NULL?
movl (%eax), %edx // load the next word into edx
jnz 1b
pushl %edx
lea 0x0c(%esp), %eax // same offset as above, but because of the push this is now &argv[0]
push %eax
pushl 0x0c(%esp)
call z_execve
add $0x0c, %esp
ret
.type z_execle, @function ; .size z_execle, .-z_execle
x86-64:
.globl z_execl ; .align 4,0x90 ; z_execl:
movq (%rsp),%rax // rax is caller-saved and dead at this point
sub $0x28, %rsp // Only allocates room for five pointers, we store six!
movq %rax, (%rsp) // Stash the return address here.
movq %rsi, 0x8(%rsp) // Put the paramaters passed via registers
movq %rdx, 0x10(%rsp) // at the bottom of the array.
movq %rcx, 0x18(%rsp)
movq %r8, 0x20(%rsp)
movq %r9, 0x28(%rsp) // Overwrites return address, adjacent to stack-passed parameters (arg 7+)
lea 0x8(%rsp),%rsi // Put the address of our argv array in rsi.
movq z_environ, %rdx // Third argument is the global environment.
call z_execve // %rdi is unchanged since the beginning
// If all goes well, this never returns. Otherwise, we need to clean up.
// %rax should be preserved, but we're free to use any other caller-saved register.
movq (%rsp), %r8 // Fetch the return address.
add $0x28, %rsp // Clean up the stack.
movq %r8, (%rsp) // Put the return address back where it belongs.
ret
.type z_execl, @function ; .size z_execl, .-z_execl
.globl z_execle ; .align 4,0x90 ; z_execle:
movq (%rsp),%rax
sub $0x28, %rsp
movq %rax, (%rsp)
movq %rsi, 0x8(%rsp)
movq %rdx, 0x10(%rsp)
movq %rcx, 0x18(%rsp)
movq %r8, 0x20(%rsp)
movq %r9, 0x28(%rsp)
// Find the env argument; it is the array element after the
// argv NULL pointer, which cannot be located before argv[1].
lea 0x10(%rsp),%r8
movq (%r8), %rdx
1: add $0x8, %r8
test %rdx, %rdx
movq (%r8), %rdx
jnz 1b
lea 0x8(%rsp),%rsi
call z_execve
movq (%rsp), %r8
add $0x28, %rsp
movq %r8, (%rsp)
ret
.type z_execle, @function ; .size z_execle, .-z_execle