Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Custom stack just for one invocation of a function

42 views
Skip to first unread message

Frederick Virchanza Gotham

unread,
Apr 23, 2023, 7:41:16 PM4/23/23
to
Let's say we have a function with the following signature:

void set_stack_pointer_and_invoke( char *, void (*)(void) );

The first parameter is a pointer to a chunk of memory which can be used as a stack, and the second parameter is a function to invoke. The idea is that the function gets invoked with the custom stack.

The assembler for this function would have to do the following:
save the current stack pointer
set the stack pointer to the first argument
invoke the second argument
restore the original stack pointer
return

The x86_64 assembler for this is as follows:

set_stack_pointer_and_invoke:
push r15 ; callee-saved
mov r15,rsp ; save the original stack pointer
mov rsp,rdi ; set the stack pointer
call rsi ; invoke the function pointer
mov rsp,r15 ; restore the original stack pointer
pop r15 ; restore the original value of r15
ret

Here it is working:

extern "C" void set_stack_pointer_and_invoke(char*,void (*)(void));

__asm("set_stack_pointer_and_invoke: .intel_syntax noprefix\n"
" push r15 \n" // r15 register is callee-saved
" mov r15,rsp \n" // save the original stack pointer
" mov rsp,rdi \n" // set the stack pointer
" call rsi \n" // invoke the function pointer
" mov rsp,r15 \n" // restore the original stack pointer
" pop r15 \n" // restore the original value of r15
" ret \n" // return
".att_syntax");

#include <iostream>
using std::cout, std::endl;

void Recursive(unsigned const arg)
{
if ( 0u == arg ) return;
cout << arg << endl;
Recursive(arg - 1u);
}

void Func(void)
{
Recursive(2000000u);
}

int main(void)
{
cout << "first line in main\n";
static char *const stack = new char[104857600];
// The stack grows negatively on x86_64
// so we must specify stack + 104857600u - 16u
set_stack_pointer_and_invoke(stack + 104857600u - 16u,Func);
delete [] stack;
cout << "last line in main\n";
}

I'm surprised I've never seen anything like this before.

Öö Tiib

unread,
Apr 24, 2023, 3:06:59 AM4/24/23
to
On Monday, 24 April 2023 at 02:41:16 UTC+3, Frederick Virchanza Gotham wrote:
> Let's say we have a function with the following signature:
>
> void set_stack_pointer_and_invoke( char *, void (*)(void) );
>
> The first parameter is a pointer to a chunk of memory which can be used as a stack, and the second parameter is a function to invoke. The idea is that the function gets invoked with the custom stack.
>
...
>
> I'm surprised I've never seen anything like this before.

Note that whatever it invokes may not throw ... otherwise
everything ends in spectacular manner.

Frederick Virchanza Gotham

unread,
Apr 24, 2023, 10:24:55 AM4/24/23
to
On Monday, April 24, 2023 at 8:06:59 AM UTC+1, Öö Tiib wrote:
>
> Note that whatever it invokes may not throw ... otherwise everything ends in spectacular manner.


I've written it to support exception handling:

https://godbolt.org/z/M9544Meqq

As far as I know, on Linux the max stack size is 100 MB. With this technique you can have gigabytes of stack. More importantly, you can temporarily de-allocate memory, and then allocate the stack, then de-allocate the stack, then re-allocate the original memory.

And here it is copy-pasted:

#include <cstddef> // size_t
#include <cstring> // memcpy
#include <exception> // exception_ptr, current_exception

namespace detail {

void Behind_The_Scenes(void (*const rdi)(void), char const *const rsi, std::exception_ptr *const rdx) noexcept
{
(void)rsi;

try
{
rdi();
}
catch(...)
{
*rdx = std::current_exception();
}
}

void set_stack_pointer_and_invoke_ASM(void (*rdi)(void), char *rsi, std::exception_ptr *rdx) noexcept;

#define mangled_set_stack_pointer_and_invoke_ASM "_ZN6detail32set_stack_pointer_and_invoke_ASMEPFvvEPcPNSt15__exception_ptr13exception_ptrE"
#define mangled_Behind_The_Scenes "_ZN6detail17Behind_The_ScenesEPFvvEPKcPNSt15__exception_ptr13exception_ptrE"

__asm(mangled_set_stack_pointer_and_invoke_ASM ":\n"
".intel_syntax noprefix \n"
" push r15 \n" // r15 register is callee-saved
" mov r15,rsp \n" // save the original stack pointer
" mov rsp,rsi \n" // set the stack pointer
" call " mangled_Behind_The_Scenes "\n" // invoke
" mov rsp,r15 \n" // restore the original stack pointer
" pop r15 \n" // restore the original value of r15
" ret \n" // return
".att_syntax");

} // close namespace 'detail'

void set_stack_pointer_and_invoke(void (*const rdi)(void) noexcept(false), char *const rsi) noexcept(false)
{
std::exception_ptr ep = nullptr;
detail::set_stack_pointer_and_invoke_ASM(rdi,rsi,&ep);
if ( nullptr != ep ) std::rethrow_exception(ep);
}

// And a simplified version for 'noexcept(true)'
void set_stack_pointer_and_invoke(void (*const rdi)(void) noexcept, char *const rsi) noexcept
{
detail::set_stack_pointer_and_invoke_ASM(rdi,rsi,nullptr);
}

// =================== And now the test code ===============================================

#include <memory> // unique_ptr
#include <iostream> // cout, endl
using std::cout, std::endl;

void Recursive(unsigned const arg)
{
if ( 0u == arg ) throw 8.2;
cout << arg << endl;
Recursive(arg - 1u);
}

void Func(void)
{
Recursive(2000000u);
}

int main(void)
{
cout << "first line in main\n";

try
{
std::unique_ptr<char[]> stack(new char[1048576000]);
// The stack grows negatively on x86_64
// so we must specify stack + 104857600u - 16u
set_stack_pointer_and_invoke(Func,stack.get() + 1048576000u - 16u);
}
catch(int const x)
{
cout << "=============================== caught an int : " << x << endl;
}
catch(double const x)
{
cout << "=============================== caught a double : " << x << endl;

Scott Lurndal

unread,
Apr 24, 2023, 10:38:28 AM4/24/23
to
Frederick Virchanza Gotham <cauldwel...@gmail.com> writes:
>On Monday, April 24, 2023 at 8:06:59=E2=80=AFAM UTC+1, =C3=96=C3=B6 Tiib wr=
>ote:
>>
>> Note that whatever it invokes may not throw ... otherwise everything ends=
> in spectacular manner.
>
>
>I've written it to support exception handling:
>
> https://godbolt.org/z/M9544Meqq
>
>As far as I know, on Linux the max stack size is 100 MB.

The primary thread stack size is controlled by the 'setrlimit(2)'
system call. The maximum value is dependent upon a number of factors,
primarily the maximum size of the virtual address space (4GB (less 1 or 2GB for kernel)
on 32-bit architectures, 128TB on 64-bit architectures).

Bonita Montero

unread,
Apr 24, 2023, 11:27:51 AM4/24/23
to
Which purpose does this serve ?

Christian Gollwitzer

unread,
Apr 24, 2023, 5:38:03 PM4/24/23
to
Am 24.04.23 um 16:24 schrieb Frederick Virchanza Gotham:
> On Monday, April 24, 2023 at 8:06:59 AM UTC+1, Öö Tiib wrote:
>>
>> Note that whatever it invokes may not throw ... otherwise everything ends in spectacular manner.
>
>
> I've written it to support exception handling:
>
> https://godbolt.org/z/M9544Meqq

I remember doing a similar hack back in the days on MSDOS, where stack
space really was an issue - the required amount was embedded into the
EXE file format and fixed at link time.
>
> As far as I know, on Linux the max stack size is 100 MB. With this technique you can have gigabytes of stack. More importantly, you can temporarily de-allocate memory, and then allocate the stack, then de-allocate the stack, then re-allocate the original memory.

On Linux, you can have unlimited stack space. I once used up all the
stack space by a recursive algorithm, getting the system down via disk
thrashing. For these reasons, most Linux distros set the limits to some
reasonable size:

root@h2015100:~# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 4125377
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 62987
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

But you can use "ulimit" to increase that as you wish:

root@h2015100:~# ulimit -s 2000000
root@h2015100:~# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 4125377
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 2000000
cpu time (seconds, -t) unlimited
max user processes (-u) 62987
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
root@h2015100:~#

Christian

0 new messages