<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>http://wiki.foofun.cn//index.php?action=history&amp;feed=atom&amp;title=Stack</id>
	<title>Stack - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="http://wiki.foofun.cn//index.php?action=history&amp;feed=atom&amp;title=Stack"/>
	<link rel="alternate" type="text/html" href="http://wiki.foofun.cn//index.php?title=Stack&amp;action=history"/>
	<updated>2026-04-03T23:12:34Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.37.1</generator>
	<entry>
		<id>http://wiki.foofun.cn//index.php?title=Stack&amp;diff=32&amp;oldid=prev</id>
		<title>Zhang3：创建页面，内容为“：''堆栈也可以引用 Networking中的TCP/IP堆栈。 本文讨论体系结构中使用的数据结构和堆栈。''  A normal stack, that grows upwards.  “堆栈”是一种数据结构。 您可以分别将元素推送到和从中弹出。 但是，与FIFO（先进先出）不同，从堆栈中弹出的元素是您最后推送的元素。 因此，堆栈也称为后进先出或FILO（先…”</title>
		<link rel="alternate" type="text/html" href="http://wiki.foofun.cn//index.php?title=Stack&amp;diff=32&amp;oldid=prev"/>
		<updated>2021-12-20T00:55:17Z</updated>

		<summary type="html">&lt;p&gt;创建页面，内容为“：&amp;#039;&amp;#039;堆栈也可以引用&lt;a href=&quot;/index.php?title=%EF%BC%9ACategory:Networking&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;：Category:Networking（页面不存在）&quot;&gt; Networking&lt;/a&gt;中的TCP/IP堆栈。 本文讨论体系结构中使用的数据结构和堆栈。&amp;#039;&amp;#039;  &lt;a href=&quot;/index.php?title=%E6%96%87%E4%BB%B6:Stack.png&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;文件:Stack.png（页面不存在）&quot;&gt;thumb|right|A normal stack, that grows upwards.&lt;/a&gt;  “堆栈”是一种数据结构。 您可以分别将元素推送到和从中弹出。 但是，与&lt;a href=&quot;/index.php?title=FIFO&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;FIFO（页面不存在）&quot;&gt;FIFO&lt;/a&gt;（先进先出）不同，从堆栈中弹出的元素是您最后推送的元素。 因此，堆栈也称为后进先出或FILO（先…”&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;：''堆栈也可以引用[[：Category:Networking | Networking]]中的TCP/IP堆栈。 本文讨论体系结构中使用的数据结构和堆栈。''&lt;br /&gt;
&lt;br /&gt;
[[Image:Stack.png|thumb|right|A normal stack, that grows upwards.]]&lt;br /&gt;
&lt;br /&gt;
“堆栈”是一种数据结构。 您可以分别将元素推送到和从中弹出。 但是，与[[FIFO]]（先进先出）不同，从堆栈中弹出的元素是您最后推送的元素。 因此，堆栈也称为后进先出或FILO（先进先出）。&lt;br /&gt;
&lt;br /&gt;
在[[：Category:X86 | X86架构]]和许多其他架构中，有一个用于代码执行的堆栈。 它用于在调用例程时存储返回指针，但也可以在其上存储临时数据和局部变量。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== 堆栈理论 ==&lt;br /&gt;
&lt;br /&gt;
许多语言和体系结构都有一个可供使用的堆栈。 当返回值存储在上面时，堆栈帧的概念就出现了。 堆栈被划分为多个堆栈帧。 每个堆栈帧包含例程的本地/临时数据、参数和前一个例程（调用者）的返回值。&lt;br /&gt;
&lt;br /&gt;
=== X86体系结构上的堆栈示例 ===&lt;br /&gt;
&lt;br /&gt;
在X86体系结构上，堆栈向下增长。 堆栈帧具有关于调用约定的特定结构。 CDECL呼叫约定是使用最广泛的。 它很可能由编译器使用。 使用两个寄存器：&lt;br /&gt;
&lt;br /&gt;
*“”“&amp;lt;tt&amp;gt;ESP&amp;lt;/tt&amp;gt;：“””扩展堆栈指针。 包含“栈顶”地址的32位值（更准确地说是X86上的“栈底”）&lt;br /&gt;
*''&amp;lt;tt&amp;gt;EBP&amp;lt;/tt&amp;gt;：''扩展基指针。 使用CDECL调用约定时定义当前堆栈帧的32位值。 它指向当前的本地数据。 它还可以访问例程参数。&lt;br /&gt;
&lt;br /&gt;
在实现内核时要小心。 如果使用[[segmentation]]，则应将&amp;lt;tt&amp;gt;DS&amp;lt;/tt&amp;gt;段配置为其基址与SS相同。 否则，在将指向局部变量的指针传递到函数中时，您可能会遇到问题，因为&amp;lt;tt&amp;gt;普通GPRs&amp;lt;/tt&amp;gt;无法以您可能认为的方式访问堆栈。&lt;br /&gt;
&lt;br /&gt;
下面是一个示例堆栈。 这些元素是保护模式下的4字节字：&lt;br /&gt;
&lt;br /&gt;
内存地址：堆栈元素：&lt;br /&gt;
&lt;br /&gt;
                +----------------------------+&lt;br /&gt;
0x105000      | Parameter 1 for routine 1  | \&lt;br /&gt;
+----------------------------+  |&lt;br /&gt;
0x104FFC      | First callers return addr. |   &amp;gt;  Stack frame 1&lt;br /&gt;
+----------------------------+  |&lt;br /&gt;
0x104FF8      | First callers EBP          | /&lt;br /&gt;
+----------------------------+&lt;br /&gt;
0x104FF4   +-&amp;gt;| Parameter 2 for routine 2  | \  &amp;lt;-- Routine 1's EBP&lt;br /&gt;
|  +----------------------------+  |&lt;br /&gt;
0x104FF0   |  | Parameter 1 for routine 2  |  |&lt;br /&gt;
|  +----------------------------+  |&lt;br /&gt;
0x104FEC   |  | Return address, routine 1  |  |&lt;br /&gt;
|  +----------------------------+  |&lt;br /&gt;
0x104FE8   +--| EBP value for routine 1    |   &amp;gt;  Stack frame 2&lt;br /&gt;
+----------------------------+  |&lt;br /&gt;
0x104FE4   +-&amp;gt;| Local data                 |  | &amp;lt;-- Routine 2's EBP&lt;br /&gt;
|  +----------------------------+  |&lt;br /&gt;
0x104FE0   |  | Local data                 |  |&lt;br /&gt;
|  +----------------------------+  |&lt;br /&gt;
0x104FDC   |  | Local data                 | /&lt;br /&gt;
|  +----------------------------+&lt;br /&gt;
0x104FD8   |  | Parameter 1 for routine 3  | \&lt;br /&gt;
|  +----------------------------+  |&lt;br /&gt;
0x104FD4   |  | Return address, routine 2  |  |&lt;br /&gt;
|  +----------------------------+   &amp;gt;  Stack frame 3&lt;br /&gt;
0x104FD0   +--| EBP value for routine 2    |  |&lt;br /&gt;
+----------------------------+  |&lt;br /&gt;
0x104FCC   +-&amp;gt;| Local data                 | /  &amp;lt;-- Routine 3's EBP&lt;br /&gt;
|  +----------------------------+&lt;br /&gt;
0x104FC8   |  | Return address, routine 3  | \&lt;br /&gt;
|  +----------------------------+  |&lt;br /&gt;
0x104FC4   +--| EBP value for routine 3    |  |&lt;br /&gt;
+----------------------------+   &amp;gt;  Stack frame 4&lt;br /&gt;
0x104FC0      | Local data                 |  | &amp;lt;-- Current EBP&lt;br /&gt;
+----------------------------+  |&lt;br /&gt;
0x104FBC      | Local data                 | /&lt;br /&gt;
+----------------------------+&lt;br /&gt;
0x104FB8      |                            |    &amp;lt;-- Current ESP&lt;br /&gt;
\/\/\/\/\/\/\/\/\/\/\/\/\/\/&lt;br /&gt;
&lt;br /&gt;
The CDECL calling convention is described here:&lt;br /&gt;
&lt;br /&gt;
; Caller's responsibilities&lt;br /&gt;
* Push parameters in reverse order (last parameter pushed first)&lt;br /&gt;
* Perform the call&lt;br /&gt;
* Pop the parameters, use them, or simply increment ESP to remove them (stack clearing)&lt;br /&gt;
* The return value is stored in EAX&lt;br /&gt;
&lt;br /&gt;
; Callee's responsibilities (callee is the routine being called)&lt;br /&gt;
* Store caller's EBP on the stack&lt;br /&gt;
* Save current ESP in EBP&lt;br /&gt;
* Code, storing local data on the stack&lt;br /&gt;
* For a fast exit load the old ESP from EBP, else pop local data elements&lt;br /&gt;
* Pop the old EBP and return &amp;amp;ndash; store return value in EAX&lt;br /&gt;
&lt;br /&gt;
It looks like this in assembly (NASM):&lt;br /&gt;
&lt;br /&gt;
SECTION .text&lt;br /&gt;
&lt;br /&gt;
caller:&lt;br /&gt;
&lt;br /&gt;
     ; ...&lt;br /&gt;
     &lt;br /&gt;
     ; Caller responsibilities:&lt;br /&gt;
     PUSH  3         ; push the parameters in reverse order&lt;br /&gt;
     PUSH  2&lt;br /&gt;
     CALL  callee    ; perform the call&lt;br /&gt;
     ADD   ESP, 8    ; stack cleaning (remove the 2 words)&lt;br /&gt;
     &lt;br /&gt;
     ; ... Use the return value in EAX ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
callee:&lt;br /&gt;
&lt;br /&gt;
     ; Callee responsibilities:&lt;br /&gt;
     PUSH  EBP       ; store caller's EBP&lt;br /&gt;
     MOV   EBP, ESP  ; save current stack pointer in EBP&lt;br /&gt;
     &lt;br /&gt;
     ; ... Code, store return value in EAX ...&lt;br /&gt;
     &lt;br /&gt;
     ; Callee responsibilities:&lt;br /&gt;
     MOV   ESP, EBP  ; remove an unknown number of local data elements&lt;br /&gt;
     POP   EBP       ; restore caller's EBP&lt;br /&gt;
     RET             ; return&lt;br /&gt;
&lt;br /&gt;
The GCC compiler does all this automatically, but if you have to call C/C++ methods from assembly or reverse, you have to know the convention. Now look at one stack frame (the callee's):&lt;br /&gt;
&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Parameter 3             |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Parameter 2             |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Parameter 1             |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Caller's return address |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Caller's EBP value      |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Local variable          | &amp;lt;-- Current EBP&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Local variable          |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Local variable          |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Temporary data          |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
| Temporary data          |&lt;br /&gt;
+-------------------------+&lt;br /&gt;
|                         | &amp;lt;-- Current ESP&lt;br /&gt;
+-------------------------+&lt;br /&gt;
&lt;br /&gt;
Using &amp;lt;tt&amp;gt;EBP&amp;lt;/tt&amp;gt; the callee can access both parameters and local variables:&lt;br /&gt;
&lt;br /&gt;
MOV  EAX, &amp;lt;nowiki&amp;gt;[[EBP + 12]]&amp;lt;/nowiki&amp;gt;  ; Load parameter 1 into EAX&lt;br /&gt;
&lt;br /&gt;
MOV  EAX, &amp;lt;nowiki&amp;gt;[[EBP + 16]]&amp;lt;/nowiki&amp;gt;  ; Load parameter 2&lt;br /&gt;
&lt;br /&gt;
MOV  EAX, &amp;lt;nowiki&amp;gt;[[EBP + 4 * EBX + 12]]&amp;lt;/nowiki&amp;gt;  ; Load parameter EBX (0-indexed)&lt;br /&gt;
&lt;br /&gt;
MOV  EAX, &amp;lt;nowiki&amp;gt;[[EBP]]&amp;lt;/nowiki&amp;gt;       ; Load local variable 1&lt;br /&gt;
&lt;br /&gt;
MOV  EAX, &amp;lt;nowiki&amp;gt;[[EBP - 4]]&amp;lt;/nowiki&amp;gt;   ; Load local variable 2&lt;br /&gt;
&lt;br /&gt;
X86还有其他调用约定。举几个例子：Pascal调用约定、fastcall约定、stdcall。 更多关于维基百科的信息，请参阅下面的链接。&lt;br /&gt;
&lt;br /&gt;
=== 设置堆栈 ===&lt;br /&gt;
&lt;br /&gt;
创建内核时，必须手动设置堆栈。 &lt;br /&gt;
&lt;br /&gt;
如果从实模式转到保护模式，还必须设置堆栈。 这是因为&amp;lt;tt&amp;gt;SS&amp;lt;/tt&amp;gt;段可能会发生变化，因此受保护模式下的&amp;lt;tt&amp;gt;ESP&amp;lt;/tt&amp;gt;不会指向与实际模式下的&amp;lt;tt&amp;gt;SP&amp;lt;/tt&amp;gt;相同的位置。 如果您在实模式和保护模式之间切换很多，那么它们可以共享堆栈。 你必须自己找到一个聪明的解决方案。 这是可以做到的。&lt;br /&gt;
&lt;br /&gt;
在保护模式下，只需将新指针值移动到&amp;lt;tt&amp;gt;ESP&amp;lt;/tt&amp;gt;寄存器中即可设置堆栈：&lt;br /&gt;
&lt;br /&gt;
MOV ESP，0x105000；设置堆栈指针&lt;br /&gt;
&lt;br /&gt;
记住，它是向下生长的。 您可以在内核的&amp;lt;tt&amp;gt;中为它分配空间。BSS&amp;lt;/tt&amp;gt;部分，如果它包含一个：&lt;br /&gt;
&lt;br /&gt;
SECTION .text&lt;br /&gt;
&lt;br /&gt;
set_up_stack:&lt;br /&gt;
&lt;br /&gt;
     MOV  ESP, stack_end  ; Set the stack pointer&lt;br /&gt;
&lt;br /&gt;
SECTION .bss&lt;br /&gt;
&lt;br /&gt;
stack_begin:&lt;br /&gt;
RESB 4096  ; Reserve 4 KiB stack space&lt;br /&gt;
stack_end:&lt;br /&gt;
&lt;br /&gt;
如果您的内核是由[[Multiboot]]兼容的引导加载程序引导的，比如[[GRUB]]，则会向您提供一个内存映射。 您可以通过查找适当大小的空闲内存块来设置堆栈。 您只需确保在设置堆栈指针时不会覆盖任何重要数据或代码。&lt;br /&gt;
&lt;br /&gt;
=== 安全 ===&lt;br /&gt;
&lt;br /&gt;
堆栈很容易使用，但有一个问题。 没有“结束”，所以它容易受到缓冲区溢出攻击的变化。 攻击者推送的元素超过堆栈所能容纳的数量，因此元素被推送到堆栈内存之外，从而覆盖代码，然后攻击者可以执行这些代码。&lt;br /&gt;
&lt;br /&gt;
在X86保护模式下，可以通过仅为堆栈分配[[GDT | GDT描述符]]来解决此问题，该描述符定义了堆栈的边界。&lt;br /&gt;
&lt;br /&gt;
== 堆栈跟踪 ==&lt;br /&gt;
&lt;br /&gt;
调试时，通常会显示堆栈跟踪，这很有帮助。 [[Stack Trace]]介绍了如何实现这一点，并使用上面的堆栈布局为X86 CDECL提供了示例代码。&lt;br /&gt;
&lt;br /&gt;
== 展开堆栈 ==&lt;br /&gt;
&lt;br /&gt;
展开堆栈很复杂。它是在使用异常时完成的，比如在[[C++]]中。它是在抛出异常时执行的。展开堆栈的目的是调用堆栈帧的本地对象的析构函数，并移除堆栈帧，直到找到合适的平台。着陆台是最好的选择。。catch块在C++或java中。catch块必须与异常匹配，即RuntimeException对象不能作为String对象捕获。&lt;br /&gt;
&lt;br /&gt;
退绕算法取决于体系结构。 通常，该算法在语言运行库中提供。 当使用GCC和C++时，它在与应用程序链接的LIbSuc++库中定义。 但是，在创建内核时不会发生这种情况。 libsupc++库也过于臃肿，无法在内核空间中使用。&lt;br /&gt;
&lt;br /&gt;
== 另见 ==&lt;br /&gt;
&lt;br /&gt;
=== 文章 ===&lt;br /&gt;
[[Stack Trace]] - Trace the called functions from the stack&lt;br /&gt;
&lt;br /&gt;
=== 线程 ===&lt;br /&gt;
&lt;br /&gt;
=== 外部链接 ===&lt;br /&gt;
&lt;br /&gt;
* [[wikipedia:x86 calling conventions| x86 calling conventions]] on Wikipedia.&lt;br /&gt;
* [[wikipedia:Stack (data structure)| Stack (data structure)]] on Wikipedia.&lt;br /&gt;
&lt;br /&gt;
[[Category:OS theory]]&lt;/div&gt;</summary>
		<author><name>Zhang3</name></author>
	</entry>
</feed>