我一直对“栈”在哪里实现感到困惑。我知道一个进程的典型内存布局(至少在类Unix系统上),但我一直想知道实际上是什么 * 设置 * 了那个布局的结构。是操作系统吗?编译器吗?如果是,那么我不明白x86伊萨怎么会有push指令;这是否意味着在加载任何操作系统之前必须存在某种堆栈?
push
ovfsdjhp1#
在主流操作系统中,操作系统Map一些堆栈内存,并进入用户空间,RSP指向Map顶部附近。但这只是设计上的选择,并不是必须的。在运行任何使用堆栈的指令之前(以及在安装任何信号处理程序或任何其他可以异步使用RSP的程序之前),操作系统可能会要求用户空间进程运行到lea rsp, [rel top_of_some_bss_array]或_start中的任何早期版本。(In保护模式或64位操作系统,内核通常会设置硬件中断使用separate kernel stack,而不是用户空间堆栈指针,这是出于安全考虑。但如果不是这样,或者在16位DOS中,除非禁用中断,否则始终拥有有效的堆栈指针是很重要的。)例如,在Linux中:使用pmap. [stack]分析进程的内存Map,讨论初始堆栈如何增长,但这种魔力如何只对主线程的堆栈起作用;同一进程中的新线程 * 确实 * 需要您(或线程库)手动mmap一些空间。但是线程创建系统调用clone需要一个参数来设置新线程的堆栈指针。因此,API仍然是围绕每个任务(线程)设计的,每个任务(线程)在启动前都有一个有效的堆栈指针。还相关:Beginning of stack on Linux-Linux内核随机化初始堆栈指针。它还使用堆栈空间将argc、argv和envp传递到用户空间,沿着存储指向的参数和环境字符串。
lea rsp, [rel top_of_some_bss_array]
_start
mmap
clone
argc
argv
envp
例如,在x86 CPU上电时,rsp保持0,或者在Intel上至少保持esp;(机器在16位虚幻模式下启动,所以最初只有ss:sp是相关的)。push将 Package 为ss:FFFE。在执行任何push或pop,或call/ret之前,重设向量的BIOS程式码应该设定ss:sp指向某个地方,或使能中断(它将异步使用堆栈)。我假设系统启动时IF=0,因为软件还没有存储中断表并使用lidt。(我所说的代码位于BIOS本身中,在您自己的代码通过UEFI或作为传统BIOS MBR引导加载程序运行之前。)类似地,xlat的存在并不意味着RBX总是一个有效的指针。但是,不要在代码中放置xlat,因为当RBX不是一个有效的指针时,它将运行!或者干脆不用它,因为它不是很快。同样对于loop,RCX并不总是一个有效的循环计数器。优选为dec ecx/jnz。为了方便和效率,操作系统只提供一个堆栈Map就可以了,而不是要求进程在BSS中分配自己的堆栈,或者使用mmap系统调用或其他方法。假设每个进程都需要一个堆栈,所以最好在进入用户空间之前提前设置好。
rsp
esp
ss:sp
ss:FFFE
pop
call
ret
lidt
xlat
loop
dec ecx
jnz
5ktev3wc2#
如果我们考虑windows、linux、macos等,操作系统将有二进制文件格式的规则,它将有一个加载它支持的二进制文件的加载器,它也可能是已知的,例如,应用程序是否需要初始化.data和.bss,或者操作系统加载器是否会根据二进制文件中的条目为您清零.bss?而且还需要典型应用程序的存储空间。然后,当你或其他人为这个操作系统和目标(x86,arm等)构建比如gcc或llvm工具链时,它也在构建知道目标操作系统的工具链,理想情况下,默认为首选的二进制格式。因此,虽然看起来我只是下载了一个预先构建的windows gnu工具链,或者我下载了一个预先构建的linux工具链,它们都有gcc和其他工具。这两个构建在C库的后端和二进制文件的构建方式(链接器脚本等)方面是非常不同的。作为程序员,堆栈空间以及堆、.text等最终是您的责任。如果您正在为MCU做裸机,您可能会也可能不会看到这一点(很多人使用现成的供应商的东西,这里也不知道发生了什么)。但是几乎每个人都使用预先构建的工具链,或者即使你从源代码构建你自己的比如说gnu或llvm,您仍然可能会得到与操作系统环境匹配的其他人为您准备的默认值。
2条答案
按热度按时间ovfsdjhp1#
在主流操作系统中,操作系统Map一些堆栈内存,并进入用户空间,RSP指向Map顶部附近。
但这只是设计上的选择,并不是必须的。在运行任何使用堆栈的指令之前(以及在安装任何信号处理程序或任何其他可以异步使用RSP的程序之前),操作系统可能会要求用户空间进程运行到
lea rsp, [rel top_of_some_bss_array]
或_start
中的任何早期版本。(In保护模式或64位操作系统,内核通常会设置硬件中断使用separate kernel stack,而不是用户空间堆栈指针,这是出于安全考虑。但如果不是这样,或者在16位DOS中,除非禁用中断,否则始终拥有有效的堆栈指针是很重要的。)
例如,在Linux中:使用pmap. [stack]分析进程的内存Map,讨论初始堆栈如何增长,但这种魔力如何只对主线程的堆栈起作用;同一进程中的新线程 * 确实 * 需要您(或线程库)手动
mmap
一些空间。但是线程创建系统调用clone
需要一个参数来设置新线程的堆栈指针。因此,API仍然是围绕每个任务(线程)设计的,每个任务(线程)在启动前都有一个有效的堆栈指针。还相关:Beginning of stack on Linux-Linux内核随机化初始堆栈指针。它还使用堆栈空间将
argc
、argv
和envp
传递到用户空间,沿着存储指向的参数和环境字符串。可能存在需要一些设置才能使用的指令。
例如,在x86 CPU上电时,
rsp
保持0,或者在Intel上至少保持esp
;(机器在16位虚幻模式下启动,所以最初只有ss:sp
是相关的)。push
将 Package 为ss:FFFE
。在执行任何
push
或pop
,或call
/ret
之前,重设向量的BIOS程式码应该设定ss:sp
指向某个地方,或使能中断(它将异步使用堆栈)。我假设系统启动时IF=0,因为软件还没有存储中断表并使用lidt
。(我所说的代码位于BIOS本身中,在您自己的代码通过UEFI或作为传统BIOS MBR引导加载程序运行之前。)类似地,
xlat
的存在并不意味着RBX总是一个有效的指针。但是,不要在代码中放置xlat
,因为当RBX不是一个有效的指针时,它将运行!或者干脆不用它,因为它不是很快。同样对于
loop
,RCX并不总是一个有效的循环计数器。优选为dec ecx
/jnz
。为了方便和效率,操作系统只提供一个堆栈Map就可以了,而不是要求进程在BSS中分配自己的堆栈,或者使用
mmap
系统调用或其他方法。假设每个进程都需要一个堆栈,所以最好在进入用户空间之前提前设置好。5ktev3wc2#
如果我们考虑windows、linux、macos等,操作系统将有二进制文件格式的规则,它将有一个加载它支持的二进制文件的加载器,它也可能是已知的,例如,应用程序是否需要初始化.data和.bss,或者操作系统加载器是否会根据二进制文件中的条目为您清零.bss?
而且还需要典型应用程序的存储空间。
然后,当你或其他人为这个操作系统和目标(x86,arm等)构建比如gcc或llvm工具链时,它也在构建知道目标操作系统的工具链,理想情况下,默认为首选的二进制格式。
因此,虽然看起来我只是下载了一个预先构建的windows gnu工具链,或者我下载了一个预先构建的linux工具链,它们都有gcc和其他工具。这两个构建在C库的后端和二进制文件的构建方式(链接器脚本等)方面是非常不同的。
作为程序员,堆栈空间以及堆、.text等最终是您的责任。如果您正在为MCU做裸机,您可能会也可能不会看到这一点(很多人使用现成的供应商的东西,这里也不知道发生了什么)。但是几乎每个人都使用预先构建的工具链,或者即使你从源代码构建你自己的比如说gnu或llvm,您仍然可能会得到与操作系统环境匹配的其他人为您准备的默认值。