为什么段虚拟地址需要连续?
Why does section virtual addresses need to be continuous?
我目前正在做一些工作,要求我从 PE 格式的可执行文件中删除该部分。起初,我只是删除了 IMAGE_SECTION_HEADER, changed NumberOfSections field in IMAGE_FILE_HEADER, recalculated SizeOfImage/SizeOfHeaders in IMAGE_OPTIONAL_HEADER 并将以下部分的原始地址移动了删除部分的原始大小。但是,Windows 拒绝加载文件并显示错误消息 "XXX is not a valid Win32 application"。我已经苦苦挣扎了一段时间,但也尝试通过已删除部分的虚拟大小移动以下部分的虚拟地址,一切正常。
为什么虚拟地址要连续,不能有空隙?我试图阅读官方 PE 文档,但没有成功。我一直认为,只要 SizeOfImage 有正确的值,各个部分的地址是什么并不重要。
大多数应用程序不需要在部分之间有很大的间隙——需要的应用程序可以使用单独的 DLL 或通过 VirtualAlloc.
动态分配内存
更新:经过更多测试,我发现部分之间的间隙必须是对齐下一部分所需的间隙以满足 SectionAlignment。因此,将两个 4096 字节的部分间隔 0x20000 字节需要将整个 exe 的 SectionAlignment 字段设置为 0x20000。 (例如,0x12000 的间距是不可能的。)这个 128kB 的间隙不会出现在进程的内存映射中并且不会消耗内存,但是如果您尝试在间隙内分配内存,VirtualAlloc 仍然会失败并返回 ERROR_INVALID_ADDRESS。
这是 GCC/MinGW 的最小测试用例,证明 Win32 exe 中的部分必须是连续的。
testcase.S:
.global _main
.section .text
_main:
push
call _Sleep@4
xor %eax, %eax
ret
.section .bss
.lcomm buf, NUMBYTES
这会生成一个有效的可执行文件:
gcc -m32 -Wl,--image-base=0x00400000 -Wl,-Ttext=0x00401000 -Wl,-Tbss=0x00402000 -DNUMBYTES=0xfe000 -Wl,--section-start=.idata=0x00500000 -s -nostartfiles -o contiguous.exe testcase.S
这会生成无效的可执行文件(gap.exe 不是有效的 Win32 应用程序):
gcc -m32 -Wl,--image-base=0x00400000 -Wl,-Ttext=0x00401000 -Wl,-Tbss=0x00402000 -DNUMBYTES=0x200 -Wl,--section-start=.idata=0x00500000 -s -nostartfiles -o gap.exe testcase.S
与十六进制编辑器和objdump相比,两个文件之间只有16字节的差异。改变的是:
- 时间戳(4 字节变化)。
- 校验和(4 字节变化)。
- .bss 部分的大小(8 字节变化)。在 contiguous.exe 中,.bss 部分大小为 0xfe000 字节,而在 gap.exe 中,.bss 部分大小为 0x200 字节,在它和 .idata 部分之间创建了一个大小为 0xfc000 字节的间隙。
我目前正在做一些工作,要求我从 PE 格式的可执行文件中删除该部分。起初,我只是删除了 IMAGE_SECTION_HEADER, changed NumberOfSections field in IMAGE_FILE_HEADER, recalculated SizeOfImage/SizeOfHeaders in IMAGE_OPTIONAL_HEADER 并将以下部分的原始地址移动了删除部分的原始大小。但是,Windows 拒绝加载文件并显示错误消息 "XXX is not a valid Win32 application"。我已经苦苦挣扎了一段时间,但也尝试通过已删除部分的虚拟大小移动以下部分的虚拟地址,一切正常。
为什么虚拟地址要连续,不能有空隙?我试图阅读官方 PE 文档,但没有成功。我一直认为,只要 SizeOfImage 有正确的值,各个部分的地址是什么并不重要。
大多数应用程序不需要在部分之间有很大的间隙——需要的应用程序可以使用单独的 DLL 或通过 VirtualAlloc.
动态分配内存更新:经过更多测试,我发现部分之间的间隙必须是对齐下一部分所需的间隙以满足 SectionAlignment。因此,将两个 4096 字节的部分间隔 0x20000 字节需要将整个 exe 的 SectionAlignment 字段设置为 0x20000。 (例如,0x12000 的间距是不可能的。)这个 128kB 的间隙不会出现在进程的内存映射中并且不会消耗内存,但是如果您尝试在间隙内分配内存,VirtualAlloc 仍然会失败并返回 ERROR_INVALID_ADDRESS。
这是 GCC/MinGW 的最小测试用例,证明 Win32 exe 中的部分必须是连续的。
testcase.S:
.global _main
.section .text
_main:
push
call _Sleep@4
xor %eax, %eax
ret
.section .bss
.lcomm buf, NUMBYTES
这会生成一个有效的可执行文件:
gcc -m32 -Wl,--image-base=0x00400000 -Wl,-Ttext=0x00401000 -Wl,-Tbss=0x00402000 -DNUMBYTES=0xfe000 -Wl,--section-start=.idata=0x00500000 -s -nostartfiles -o contiguous.exe testcase.S
这会生成无效的可执行文件(gap.exe 不是有效的 Win32 应用程序):
gcc -m32 -Wl,--image-base=0x00400000 -Wl,-Ttext=0x00401000 -Wl,-Tbss=0x00402000 -DNUMBYTES=0x200 -Wl,--section-start=.idata=0x00500000 -s -nostartfiles -o gap.exe testcase.S
与十六进制编辑器和objdump相比,两个文件之间只有16字节的差异。改变的是:
- 时间戳(4 字节变化)。
- 校验和(4 字节变化)。
- .bss 部分的大小(8 字节变化)。在 contiguous.exe 中,.bss 部分大小为 0xfe000 字节,而在 gap.exe 中,.bss 部分大小为 0x200 字节,在它和 .idata 部分之间创建了一个大小为 0xfc000 字节的间隙。