我正在用C编写一个线程模拟程序,它需要知道堆栈段的最低地址,以便它可以读取和写入切换上下文所需的元数据。我通过阅读
proc/self/maps
并查找与模式 [stack]
匹配的行来找到此地址 - 像这样:
7fffa7774000-7fffa7795000 rwxp 00000000 00:00 0 [stack]
在正常程序执行期间,这一切都很好,堆栈被清楚地标记。但是,当我使用 Valgrind Memcheck 执行程序时,
[stack]
内存段不是我程序的执行堆栈,而是 Valgrind 的,并且我的程序可能使用了 Valgrind 分配的一些匿名段。我的程序不知道它正在由 Valgrind 运行并且没有使用 [stack]
块,盲目地获取 Valgrind 堆栈的最低地址并开始在那里做任何它喜欢做的事情。显然是坏消息。
鉴于这种独特的情况,我不知道如何可靠地获取堆栈的最低地址。如果可能的话,我想通过调整它以解决这种边缘情况来保持当前从
proc/self/maps
读取的方法。我怀疑这可能需要一些关于 Valgrind 如何运行它正在测试的程序的高级知识,但还没有找到关于这方面的好的信息。
不过,我承认这可能不可行或不切实际,因此很想听到任何解决方案,以了解如何可靠地获取程序执行堆栈的最低地址,即使是通过 Valgrind 执行程序时也是如此。
如果相关的话,我正在使用 C17、Valgrind 3.21.0、Fedora Linux 38、Intel x86_64 架构。
谢谢!
Valgrind 尽最大努力使来宾环境看起来就像来宾独立运行一样。不可能有完美的仿真。例如有差异
Valgrind 确实尝试掩盖主机和来宾 /proc/self 之间的一些差异。可以处理
path
、fd
、exe
、cmdline
、psinfo
(取决于平台)。
Valgrind 在 Linux 和 Solaris 上确实使用了 /proc/self/maps。 Valgrind 将读取自己的调试信息(如果崩溃,它将用于打印自己的调用堆栈)。为此,它需要在内存映射中找到自己,并获取关联的文件和 ELF 信息。
Valgrind 不会尝试清理 /proc/self/maps 来隐藏它自己使用的内存映射。我认为这不是一个好主意。这会给人一种错误的印象,即内存范围可用,而实际上并不可用。
Valgrind 不会将从操作系统获取的堆栈供自己使用。事实上它使用了两个堆栈。 Valgrind 在启动时做的第一件事就是切换到使用大约 1MB 的小型固定大小临时堆栈。这足以让它自我引导。当 Valgrind 准备好启动 VEX(虚拟 CPU)运行来宾代码时,它会传输到另一个堆栈。最终堆栈是动态分配的,大小可以通过命令行参数控制。
Valgrind 需要做大量工作来综合客户端堆栈。它需要构建 argv/auxv/env。至少在 FreeBSD/Linux/Solaris 64 位系统上,客户端堆栈位置硬编码为 128Gbytes 减 1 (0x0000001fffffffff)。
如果您使用
-d -d
运行 Valgrind,您可以获得更多信息,以下内容通过 grep stack
进行管道传输
--46962:2: aspacem suggested_clstack_end = 0x1ffc000fff (computed)
--46962:1: initimg Setup client stack: size will be 16777216
--46962:2: initimg Client info: initial_SP=0x1FFC0003E0 max_stack_size=16777216
--46962:2: main mark stack inaccessible 1ffbfff000-1ffc00035f
--46962:2: stacks register [start-end] [0x1FFBFFF000-0x1FFC000FFF] as stack 0
-d -d
还将在各个阶段(引导、来宾启动和来宾结束)打印各种内存映射。您可以将其与 /proc/self/maps 进行比较。