4

栈大小可以怎么改?

 3 years ago
source link: https://bianchengnan.gitee.io/articles/interesting-exception-0xc00000fd-continue-how-to-increase-stack-size/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前一段日子,遇到了好几个栈溢出异常,相关文章可以点击 这里这里这里。解决栈溢出,一般可以从两个方面进行处理。简而言之就是:开源节流!

开源 就是增大栈尺寸。节流 就是减少不必要的内存占用。解决栈溢出,正统的方法是减少内存占用(比如,尽量按引用传递参数)。但是有些情况下了解如何增大线程栈尺寸也非常有必要,可以应急!本文介绍几种增大栈尺寸的方法。

1. 修改 API 参数

可以在创建线程的时候指定栈保留大小。创建线程常用的 APICreateThread,函数原型如下:

1
2
3
4
5
6
7
8
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
__drv_aliasesMem LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);

可以通过参数 dwStackSize 指定栈大小,单位是字节。如果该参数是 0 的话,会使用 PE 文件头中的值。

2. 修改工程配置

可以在 vs 中设置默认的栈保留大小和栈提交大小,相应的值会记录在生成的 PE 头中。

在工程上右键 -> 属性 -> 配置属性 -> 链接器 -> 系统 -> 堆栈保留大小堆栈提交大小。即可设置。具体位置如下图:

stack-reverse-setting

说明:这里设置的单位是字节。而且是 10 进制的。

3. 手动修改PE文件头

如有由于某些原因,未能在开发的时候调整默认值,可以通过二进制编辑工具直接修改已经生成好的 PE 文件头中的字段来修改栈保留大小和栈提交大小。

stack-reverse-size-in-image-header

SizeOfStackReserve 指定了栈保留大小,默认值是 1MB,也就是这里的 00100000

SizeOfStackCommit 指定了栈提交大小,默认值是 4KB,也就是这里的 00001000

说明:这里的值是用 16 进制表示的。

线程栈默认大小是由哪个 PE 头决定的?主程序还是动态库?还是在主程序中创建的线程由主程序决定,在动态库中创建的线程由动态库决定?

为了明确这个问题,特意逆向了一下 CreateThread,内部会调用 CreateRemoteThread。在逆向的过程中突然想到大名鼎鼎的 ReactOS,于是赶紧翻看 CreateRemoteThread 的源码,CreateRemoteThread 内部会调用 BaseCreateStack 创建线程栈。我摘录了一段关键代码(为了突出重点,代码做了调整删减):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
NTSTATUS WINAPI BaseCreateStack(_In_ HANDLE hProcess,
_In_opt_ SIZE_T StackCommit,
_In_opt_ SIZE_T StackReserve,
_Out_ PINITIAL_TEB InitialTeb
)
{
/* Get the Image Headers */
PIMAGE_NT_HEADERS Headers = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress);
if (!Headers) return STATUS_INVALID_IMAGE_FORMAT;

if (StackReserve == 0)
StackReserve = Headers->OptionalHeader.SizeOfStackReserve;

if (StackCommit == 0)
{
StackCommit = Headers->OptionalHeader.SizeOfStackCommit;
}
/* Check if the commit is higher than the reserve */
else if (StackCommit >= StackReserve)
{
/* Grow the reserve beyond the commit, up to 1MB alignment */
StackReserve = ROUND_UP(StackCommit, 1024 * 1024);
}

/*省略N行*/
}

从上述代码可知,如果传入的 StackReserveStackCommit )是 0 的话,那么会取 NtCurrentPeb()->ImageBaseAddres 对应的文件头中的相关字段。而 NtCurrentPeb() 一个进程只有一份,对应着主程序。由此可以得到结论:线程栈保留(提交)大小是根据主程序文件头中的相关字段来决定的,而不是根据动态链接库中的字段。

  • 可以在创建线程的时候指定栈大小,也可以在 vs 中设置默认的线程大小,还可以通过修改 PE 文件头中的对应字段来改变栈大小。
  • 不论是在 vs 中设置,还是手动修改 PE 文件头中的对应字段,都需要修改主程序对应的字段,修改动态链接库中的字段并不起作用。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK