【问题标题】:C - Stack allocation using only ntdllC - 仅使用 ntdll 的堆栈分配
【发布时间】:2017-04-28 22:11:59
【问题描述】:

是否存在仅使用ntdll.dll 的 API 调用或任何其他类似方式在堆栈上分配内存?

我知道alloca() 会这样做,但我不能使用它,因为我只能使用来自ntdll.dll 的函数。

谢谢!

【问题讨论】:

  • 没有这个功能。您可以轻松地自己编写。
  • 操作系统不提供堆栈分配,这是每个用户功能在其序言中所做的事情。你的编译器应该将alloca() 翻译成序言中的相同指令,不需要函数调用。如果没有,请查看内联汇编。
  • @DavidHeffernan - 存在,你错了。
  • @RbMm: _alloca_probe()__chkstk() 可以和栈上分配一起使用,但它不做栈分配。
  • 如果您解释为什么“只能使用来自ntdll.dll 的函数”,您可能会得到更好的答案。我至少能想到两个你可能需要这样做的充分理由——但它们都涉及做只有微软员工才能正确完成所有必要信息的事情,如果你在微软工作,你就不会问这个问题在这里。

标签: c winapi stack allocation ntdll


【解决方案1】:

alloca 是部分内在函数,由编译器实现。但在内部它调用_alloca_probe_16(对于x86)或__chkstk(x64)在堆栈上向下移动保护页。这个函数的实现存在于alloca16.objchkstk.obj 中,可以在VC 子文件夹中找到(这里完全依赖于VC 版本)——你可以为链接过程添加这个obj,甚至首先将它转换为lib。也在最新的 WDK 库中 - 存在 ntdllp.lib(不要与 ntdll.lib 混淆) - 它还包含所有实现需要(ntdll.dll export _chkstk(对于 x86)和 __chkstk(对于 x64))


再次详细说明:

当你在 src 代码中编写时

alloca(cb) CL x86 编译器生成

mov eax,cb
call _alloca_probe_16 ; do actual stack allocation and probe

在 x64 版本中

mov         ecx,eax 
add         rcx,0Fh 
cmp         rcx,rax 
ja          @@0
mov         rcx,0FFFFFFFFFFFFFF0h 
@@0:
and         rcx,0FFFFFFFFFFFFFFF0h 
mov         rax,rcx 
call        __chkstk ; probe only
sub         rsp,rax ; actual stack allocation

所以_alloca_probe_16 和/或__chkstk 必须在某处实现,否则您会遇到链接错误 - 未解析的外部符号。

在最新的 WDK 版本中存在包含此实现的 ntdllp.lib(注意 p - 不是 ntdll.lib)。在这种情况下,您的 PE 将从 ntdll.dll 导入 __chkstk_alloca_probe(此函数从 XP 导出的最小值 - 这两个函数都指向相同的代码,只是别名)

另一种解决方案 - 在 VC 文件夹中可以找到 alloca16.objchkstk.obj - 您可以将此 obj 用作链接输入(或将 alloca16.obj + chkstk.obj 合并到单个 lib 文件中)。在这种情况下,您的 PE 将毫无意义。

【讨论】:

    【解决方案2】:

    您不需要依赖于架构的东西,因为堆栈上的分配(通常)是独立于架构的。

    如果您使用的是 C99,您有一个标准的方法,使用可变长度数组:https://gcc.gnu.org/onlinedocs/gcc/Variable-Length.html

    你可以很简单地写这样的东西:

    char mybuffer[my_size];
    

    而且会在栈上分配。

    【讨论】:

    • 如果my_size unknown 和编译时间怎么办?这不是解决方案
    • @RbMm 好吧,VLA 是专门为该场景设计的
    • @RbMm:这就是使它成为可变长度数组的原因。如果您有 C99 编译器,这是一个解决方案。大多数 Windows 编译器都不是。
    • @RbMM VLA是“当my_size在编译时未知时”的定义
    • 如果我们使用 windows 这也有效吗?如果我想使用 CL 而不是 gcc?
    【解决方案3】:

    因为alloca 操纵堆栈指针,所以它不是一个“真正的”函数,而是一个“编译器内在函数”。如果您将使用alloca 的函数编译为汇编语言,您应该看到它被直接翻译为sub esp, NNN 而不是call alloca。 (除了sub esp, NNN 之外,可能还会调用一个函数。在这种情况下,您需要找出该函数的作用,它通常在哪里定义,并安排您的应用程序提供替代品。你已经跳过了各种不寻常的圈子,只使用 NTDLL,这只是另外一个。)

    如果您看到看到call alloca 而没有看到sub esp, NNN,这很可能意味着您的编译器只有一个虚假的alloca 实现不是 给你从堆栈分配的内存,你根本不应该使用它。

    【讨论】:

    • 除了调整堆栈指针之外,在函数体内使用alloca 或 VLA 会强制编译器使用长序言和结语,例如push ebp; mov ebp, esp; sub esp, NNN .... mov esp, ebp; pop ebp 而不是简单的sub esp, NNN ... add esp, NNN
    • 我们需要 ckeck 堆栈分配 - CL 通过调用 __chkstk 完成,这已经不是“编译器内在” - 你忘记了堆栈中的保护页
    • @zwol - 你在自己的代码中使用堆栈分配,有这方面的经验吗?
    • @RbMm 是的。不是特别在 OP 所询问的环境中,但堆栈就是堆栈,只有几种方法可以实现 alloca。我相信这个答案基本上与您的一致;我没有给出可能被调用的函数的名称,因为它完全取决于编译器它的名称是什么,或者是否有 is 函数调用,但我在想同样的事情你是。
    • @RbMm 如果您从未听说过 alloca 实现是假的,请参阅 git.savannah.gnu.org/gitweb/?p=gnulib.git;a=blob;f=lib/alloca.c
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-29
    • 2011-06-11
    • 2011-12-01
    • 2017-12-23
    • 2017-01-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多