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

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

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

【问题讨论】:

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

标签: c linux stack-memory stack-pointer


【解决方案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。)
      猜你喜欢
      • 2017-10-01
      • 2015-11-16
      • 2013-01-20
      • 2015-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-23
      相关资源
      最近更新 更多