Alloca执行情况
-
23-08-2019 - |
题
一个人如何实现alloca()使用内联x86汇编中的语言,如D、C、C++?我想创建一个略微的修改版本,但首先我需要知道如何的标准版本执行。阅读的拆卸从汇编者没有帮助,因为他们执行这么多的优化,我只想的规范的形式。
编辑:我猜硬的部分是,我想这具有正常功能呼叫法,即使用一个裸体的功能或者什么东西,让它看起来像正常alloca().
编辑#2:啊,到底什么,你可以假设,我们不忽略框架的指针。
解决方案
实施 alloca
实际上 需要编译器帮助. 。这里有一些人说这很简单:
sub esp, <size>
不幸的是,这只是图片的一半。是的,这会“在堆栈上分配空间”,但有一些问题。
如果编译器发出了代码,以引用其他变量相对于其他变量
esp
代替ebp
(如果没有框架指针,则典型的编译)。然后需要调整这些参考。即使使用帧指针,编译器有时也会这样做。更重要的是,根据定义,分配的空间
alloca
当功能退出时,必须“释放”。
最重要的是第 2 点。因为你 需要 编译器发出对称相加的代码 <size>
到 esp
在函数的每个出口点。
最可能的情况是编译器提供了一些内部函数,允许库编写者向编译器请求所需的帮助。
编辑:
事实上,在 glibc(GNU 的 libc 实现)中。实施 alloca
就是这样:
#ifdef __GNUC__
# define __alloca(size) __builtin_alloca (size)
#endif /* GCC. */
编辑:
经过考虑之后,我认为编译器至少需要 总是 在任何使用帧指针的函数中使用 alloca
, ,无论优化设置如何。这将允许通过引用所有当地人 ebp
安全地,帧清理将通过将帧指针恢复到来处理 esp
.
编辑:
所以我做了一些这样的实验:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#define __alloca(p, N) \
do { \
__asm__ __volatile__( \
"sub %1, %%esp \n" \
"mov %%esp, %0 \n" \
: "=m"(p) \
: "i"(N) \
: "esp"); \
} while(0)
int func() {
char *p;
__alloca(p, 100);
memset(p, 0, 100);
strcpy(p, "hello world\n");
printf("%s\n", p);
}
int main() {
func();
}
不幸的是 不起作用 正确。分析 gcc 的汇编输出后。看来优化是有障碍的。问题似乎在于,由于编译器的优化器完全不知道我的内联汇编,因此它习惯于以意想不到的顺序执行操作,并且 仍然 通过引用事物 esp
.
这是生成的 ASM:
8048454: push ebp
8048455: mov ebp,esp
8048457: sub esp,0x28
804845a: sub esp,0x64 ; <- this and the line below are our "alloc"
804845d: mov DWORD PTR [ebp-0x4],esp
8048460: mov eax,DWORD PTR [ebp-0x4]
8048463: mov DWORD PTR [esp+0x8],0x64 ; <- whoops! compiler still referencing via esp
804846b: mov DWORD PTR [esp+0x4],0x0 ; <- whoops! compiler still referencing via esp
8048473: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048476: call 8048338 <memset@plt>
804847b: mov eax,DWORD PTR [ebp-0x4]
804847e: mov DWORD PTR [esp+0x8],0xd ; <- whoops! compiler still referencing via esp
8048486: mov DWORD PTR [esp+0x4],0x80485a8 ; <- whoops! compiler still referencing via esp
804848e: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
8048491: call 8048358 <memcpy@plt>
8048496: mov eax,DWORD PTR [ebp-0x4]
8048499: mov DWORD PTR [esp],eax ; <- whoops! compiler still referencing via esp
804849c: call 8048368 <puts@plt>
80484a1: leave
80484a2: ret
正如您所看到的,事情并不那么简单。不幸的是,我坚持我最初的主张,即您需要编译器帮助。
其他提示
这将是棘手的做到这一点 - 事实上,除非你有在编译器的代码生成不能完全安全进行足够的控制。您的例程将不得不操纵堆栈,以使得当它返回一切都被清洗,但堆栈指针留在这样一个位置,所述存储器块留在那个地方。
问题是,除非你能通知堆栈指针已经在你的函数调用修改的编译器,它可以决定它可以继续参考其他当地人(或其他)通过堆栈指针 - 但偏移量将不正确。
C和C++的标准,不指定 alloca()
已经使用堆,因为 alloca()
是不是在C或C++的标准(或POSIX对于这个问题)1中。
一个编译器,也可以实现 alloca()
使用堆。例如,手臂Arm(RVCT)编译器 alloca()
使用 malloc()
分配的缓冲区(引用他们的网站在这里),并且导致编译器,以这些代码,可以释放缓冲功能返回。这不需要玩堆的指针,但是仍然需要编译器的支持。
微软视C++有一个 _malloca()
功能,使用堆如果没有足够的房间在堆,但它要求呼叫者使用 _freea()
, 不像 _alloca()
, 这并不需要/要明确的释放。
(C++析构在您的处置,则显然可以做清理没有编译器的支持,但是你不能宣布的局部变量内部的一个任意的表达,所以我不认为你能写 alloca()
宏使用RAII.再说,显然你不能使用 alloca()
在某些表现形式(喜欢 功能参数)。)
1是的,它是法律的编写 alloca()
那只是电话 system("/usr/games/nethack")
.
ALLOCA在汇编代码直接实现。 那是因为你不能从高级语言直接控制栈布局。
另外请注意,大多数实施将执行一些额外的优化校准一样堆叠性能方面的原因。 上X86分配堆栈空间的标准方式是这样的:
sub esp, XXX
鉴于XXX是字节数到allcoate
修改强>结果 如果你想看看实现(和你使用的MSVC)看到alloca16.asm和chkstk.asm。结果 在第一文件中的代码基本上对齐所需的分配大小为16字节的边界。在第2个文件中的代码实际上走这将属于新的堆栈区,触动他们的所有页面。这可能会引发其中使用由OS中生长的堆PAGE_GAURD异常。
的继续传递风格ALLOCA 强>
变长数组中的纯ISO C ++ 即可。验证的概念的实现。
用法
void foo(unsigned n)
{
cps_alloca<Payload>(n,[](Payload *first,Payload *last)
{
fill(first,last,something);
});
}
核心理念
template<typename T,unsigned N,typename F>
auto cps_alloca_static(F &&f) -> decltype(f(nullptr,nullptr))
{
T data[N];
return f(&data[0],&data[0]+N);
}
template<typename T,typename F>
auto cps_alloca_dynamic(unsigned n,F &&f) -> decltype(f(nullptr,nullptr))
{
vector<T> data(n);
return f(&data[0],&data[0]+n);
}
template<typename T,typename F>
auto cps_alloca(unsigned n,F &&f) -> decltype(f(nullptr,nullptr))
{
switch(n)
{
case 1: return cps_alloca_static<T,1>(f);
case 2: return cps_alloca_static<T,2>(f);
case 3: return cps_alloca_static<T,3>(f);
case 4: return cps_alloca_static<T,4>(f);
case 0: return f(nullptr,nullptr);
default: return cps_alloca_dynamic<T>(n,f);
}; // mpl::for_each / array / index pack / recursive bsearch / etc variacion
}
可以检查一个开源C编译器的源,如打开WATCOM 和自己发现它
如果你不能使用C99的可变长度数组,可以用一个复合文字浇铸到一个空指针。
#define ALLOCA(sz) ((void*)((char[sz]){0}))
这也适用于-ansi(作为一个gcc扩展名),即使它是一个函数参数;
some_func(&useful_return, ALLOCA(sizeof(struct useless_return)));
的缺点是,当作为C ++编译,克++> 4.6会给你一个错误:服用临时数组的地址 ...铛和ICC不抱怨虽然
我们想要做的是这样的事情:
void* alloca(size_t size) {
<sp> -= size;
return <sp>;
}
在大会(Visual Studio中2017年,64位),它看起来像:
;alloca.asm
_TEXT SEGMENT
PUBLIC alloca
alloca PROC
sub rsp, rcx ;<sp> -= size
mov rax, rsp ;return <sp>;
ret
alloca ENDP
_TEXT ENDS
END
不幸的是退货指针在栈上的最后一个项目,我们不希望将其覆盖。此外,我们需要照顾的定位,即圆形的尺寸的高达8多所以我们必须做到这一点:
;alloca.asm
_TEXT SEGMENT
PUBLIC alloca
alloca PROC
;round up to multiple of 8
mov rax, rcx
mov rbx, 8
xor rdx, rdx
div rbx
sub rbx, rdx
mov rax, rbx
mov rbx, 8
xor rdx, rdx
div rbx
add rcx, rdx
;increase stack pointer
pop rbx
sub rsp, rcx
mov rax, rsp
push rbx
ret
alloca ENDP
_TEXT ENDS
END
ALLOCA是容易,你只移动堆栈指针向上;然后产生所有的读/写指向这个新的块
sub esp, 4
我推荐的“输入”指令。可在286个更新的处理器(可已经可以在186为好,我不记得随便,但那些尚未得到广泛应用反正)。