Answer to Dynamic function calls
Since we are copying data to the top of the stack while still keeping the
old esp, we are actually exceeding our stack space for a while. This
would be fine as long as no other code depended on the esp to be correct
(we know what we're doing, we can handle it). However, since we call
memcpy to do the copying for us, memcpy will use the space "above" esp,
the same space memcpy will use for its stack frame. Bad.
Why is the limit 3?
To understand why the limit is 3 elements, we should take a look at the
generated machine code. The line memcpy(to, params, pSize) will be
compiled to something like this:
push pSize; push params; push to; call memcpy; add esp, 12;
So when calling memcpy, we will consume 3`sizeof(void)` bytes on the stack
for parameters. What happens next is that call actually does push eip (the
instruction pointer) before jumping to the function. So the first 3 elements on
the stack are occupied by parameters to memcpy, and the fourth is occupied by
the return address of memcpy.
When calling a function with one parameter, memcpy will only overwrite the pSize parameter. This is possibly bad since we will write into too much memory, but it works for some reason, possibly memcpy keeps its own copy deeper down the stack. Two and three parameters and it will overwrite the 'src' and 'dest' pointers. This is not a problem since it will keep temporary pointers somewhere deeper down the stack, which are not overwritten.
The fourth parameter will however cause the return address to be overwritten, which will cause a crash when memcpy returns (if nothing else breaks before). And that is where I saw the bug.
Solution
One possible solution is to simply implement memcpy inline by doing for example:
for (nat i = 0; i < count; i++) to[i] = params[i];
This does not depend on any extra memory above esp (well, it depends on how the
compiler treats new scopes, but most compilers allocate memory for all scopes
at once).
Another idea would be to adjust esp in the first asm-block, so that the memory
used by the call to memcpy does not collide with the memory we want to use
for our call. The reason I did not do that is because I have no idea about how
the compiler refers to local variables. It might choose to do so by using the esp
pointer, and in that case things would be really bad. I have not tried this solution
though.
Comments
New comment
You can use GitHub flavored markdown here. Parsed by Parsedown, which does not support all of GitHub's features. For example, specifying the language of code listings is not supported.