什么是堆栈,51单片机堆栈指针SP的使用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是堆栈,51单片机堆栈指针SP的使用相关的知识,希望对你有一定的参考价值。

在计算机领域,堆栈是一个不容忽视的概念,堆栈是两种数据结构。堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。在单片机应用中,堆栈是个特殊的存储区,主要功能是暂时存放数据和地址,通常用来保护断点和现场。要点:堆,队列优先,先进先出(FIFO—first in first out)[1] 。栈,先进后出(FILO—First-In/Last-Out)。 参考技术A mcs-51单片机汇编语言中,没有sp这条指令,只有针对sp操作的指令。51单片机中sp是堆栈指针寄存器,存放着当前堆栈地址。堆栈用于存储子程序调用、中断程序调用时程序返回的地址,或者用来临时保存某一寄存器的值。
除了初始化堆栈时直接给sp赋值,sp显式存在,其它的对sp有影响的指令,对sp的操作都是隐式的,就是说sp并不出现在指令操作数当中。
例如:
mov
sp,#80h
;初始堆栈指针,(sp)=80h
lcall
xxxx;调用xxxx处的子程序,下一条指令pc值压栈,(sp)=(sp)+2
nop;子程序返回时,ret指令从堆栈中弹出pc值,所以会返回到这里,(sp)=(sp)-2
push
acc;累加器a的内容压栈保存,(sp)=(sp)+1
pop
b;堆栈内保存的累加器a的内容弹出到b寄存器中,(sp)=(sp)-1
使用push、pop指令使用堆栈,要注意进出栈的匹配,否则将引起不可预期的后果。
另外
如果把30h赋给了sp,称为设置栈底。以后:
push
acc
,就把acc的内容存放到31h单元;
push
psw
,就把psw的内容存放到32h单元;
push
b
,就把b的内容存放到33h单元。
……
弹出时,要用pop指令。
利用堆栈,就不用管具体的内容存放在什么单元了,sp会自动管理。

打印堆栈指针的值

【中文标题】打印堆栈指针的值【英文标题】:Print out value of stack pointer 【发布时间】:2013-12-02 07:19:00 【问题描述】:

如何在 Linux(Debian 和 Ubuntu)中打印 C 中堆栈指针的当前值?

我尝试了谷歌,但没有找到任何结果。

【问题讨论】:

它将取决于体系结构,但在 x86 中,寄存器 SP 指向堆栈顶部,BP 指向堆栈框架的底部...您可以使用内联 asm 将其复制到 void * 并打印. 你为什么要问?这样做的目的是什么? @BasileStarynkevitch 在我的情况下玩缓冲区溢出 【参考方案1】:

一个不能移植甚至不能保证工作的技巧是简单地将本地地址作为指针打印出来。

void print_stack_pointer() 
  void* p = NULL;
  printf("%p", (void*)&p);

这实际上将打印出p 的地址,这是当前堆栈指针的一个很好的近似值

【讨论】:

是的,我想不出在标准 C 中更好的方法,OP 应该知道 p 的声明可能作为函数谓词的一部分发生在最后一帧被推送之后和之前完全构建...可能。 没有必要初始化p,因为它的值从未被使用过——也没有任何特别的理由让p成为void*(它也可以是int )。 void* 值的正确格式是 %p,而不是 %d——您需要将指针值转换为 void*。所以:int dummy; printf("%p\n", (void*)&dummy);。 (您还拼错了printf。)但是,是的,这似乎给出了当前堆栈指针的合理近似值。 @KeithThompson 我意识到NULL init 是不必要的,但我也不能强迫自己编写使用未初始化变量的代码。感觉比打印出堆栈指针更脏:) @JaredPar:你可以用一个未初始化的变量做很多事情——比如给它赋值。仅使用它的 会导致问题。【参考方案2】:

没有便携的方法可以做到这一点。

在 GNU C 中,这可能适用于具有名为 SP 的寄存器的目标 ISA,包括 x86,其中 gcc 将“SP”识别为 ESP 或 RSP 的缩写。

// broken with clang, but usually works with GCC
register void *sp asm ("sp");
printf("%p", sp);

local register variables 的这种用法现在已被 GCC 弃用:

此功能唯一受支持的用途是在调用Extended asm时为输入和输出操作数指定寄存器

定义寄存器变量不会保留寄存器。除了调用扩展 asm 时,不保证指定寄存器的内容。因此,明确不支持以下用途。 如果它们似乎起作用,那只是偶然,并且可能由于周围代码(看似)不相关的更改,甚至是未来版本 gcc 优化中的微小更改而停止按预期工作。 ...

在实践中,clang 也破坏了 sp 被视为任何其他未初始化变量。

【讨论】:

这似乎适用于 gcc。我怀疑它是否可以移植到大多数其他编译器。 当然,在 64 位上,您需要使用能够保存指针的整数类型,最好是来自 stdint.h 的 intptr_t 它看起来也特定于处理器架构(x86,可能也适用于 ARM)【参考方案3】:

除了duedl0r's answer 和专门的GCC,您还可以使用__builtin_frame_address(0),它是GCC 特定的(但不是x86 特定的)。

这也应该适用于Clang(但也有一些bugs)。

取本地地址(如JaredPar answered)也是一种解决方案。

请注意,AFAIK C 标准理论上不需要任何调用堆栈。

记住阿佩尔的论文:garbage collection can be faster than stack allocation;一个非常奇怪的 C 实现可以使用这种技术!但是 AFAIK 它从未用于 C.

人们可以梦想其他技术。你可以有split stacks(至少在最近的GCC上),在这种情况下,堆栈指针的概念意义不大(因为堆栈不是连续的,并且可以由每个调用帧的许多段组成)。

【讨论】:

【参考方案4】:

Linux上,您可以使用proc 伪文件系统来打印堆栈指针。

查看here,查看/proc/your-pid/stat 伪文件,查看字段2829

startstack %lu 的开始(即底部)的地址 堆栈。

kstkesp %lu ESP(堆栈指针)的当前值,如找到 在进程的内核堆栈页面中。

你只需要解析这两个值!

【讨论】:

你不需要你的PID,你可以随时使用/proc/self/stat 确实如此,对于您想为自己的流程执行此操作的情况。不过这会很奇怪,因为这个动作可能会改变 SP。那时我最终使用了内联汇编特定于架构的方法! 我认为这个问题是在询问有关获取您自己的堆栈指针值的问题。 (所以是的,内联汇编显然要好得多。)如果您想要另一个进程的当前堆栈指针,ptrace(2) 可以读取寄存器。这个答案有点像一个相关的问题,比如你的堆栈映射的最低地址,这是一个合理的事情,也想从进程内部知道。【参考方案5】:

您在文件/proc/<your-process-id>/maps 中拥有该信息,与字符串[stack] 出现在同一行中(因此它独立于编译器或机器)。这种方法的唯一缺点是要读取该文件,它需要是 root。

【讨论】:

[stack] 仅出现在初始/主线程的堆栈中。线程堆栈没有获得该标记,因此这在多线程进程中没有用。不过,您不必成为 root 也可以阅读自己的 /proc/self/maps【参考方案6】:

试试 lldb 或 gdb。比如我们可以在lldb中设置回溯格式。

settings set frame-format "frame #$frame.index: $ansi.fg.yellow$frame.pc: pc:$frame.pc,fp:$frame.fp,sp:$frame.sp  $ansi.normal $module.file.basename\`$function.name-with-args$frame.no-debug$function.pc-offset at $ansi.fg.cyan$line.file.basename$ansi.normal:$ansi.fg.yellow$line.number$ansi.normal:$ansi.fg.yellow$line.column$ansi.normal$function.is-optimized [opt]$frame.is-artificial [artificial]\n"

这样我们就可以在debug中打印bp、sp了

frame #10: 0x208895c4: pc:0x208895c4,fp:0x01f7d458,sp:0x01f7d414   UIKit`-[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 376

更多关注https://lldb.llvm.org/use/formatting.html

【讨论】:

【参考方案7】:

您还可以使用扩展的汇编指令,例如:

#include <stdint.h>

uint64_t getsp( void )

    uint64_t sp;
    asm( "mov %%rsp, %0" : "=rm" ( sp ));
    return sp;

对于 32 位系统,必须将 64 替换为 32,并将 rsp 替换为 esp。

【讨论】:

我建议只使用"=r"。不幸的是,当它是一个选项时,clang 总是选择记忆。省略"=m" 可以解决这个脑死优化错误。另外,请使用uintptr_t 我得到的值从根本上不同于接受的接受局部变量地址的答案。例如,0x7FFEE0DA8190 来自此,0x1168bf020 来自其他方式。此外,随着您深入调用链,这种方式给出的值会减少,而另一种方式给出的值会增加。 (在 Mac 上测试,64 位,Clang。)

以上是关于什么是堆栈,51单片机堆栈指针SP的使用的主要内容,如果未能解决你的问题,请参考以下文章

Keil C51 中堆栈指针的问题

5V单片机与3.3V单片机串口通信问题

MCS-51单片机复位后,第一次压入到堆栈操作的数据被保存到_地址单元

yongc语言编写单片机程序,出现了堆栈溢出情况,怎么解决?堆栈指针怎么初始化?

单片机里sp是啥意思啊

51单片机死机重启的原因都有哪些?