Valgrind Memcheck Obscures Program's Memory Segments in /proc/self/maps

61 views Asked by At

I am writing a thread simulation program in C which needs to know the lowest address of the stack segment so that it can read and write metadata needed for switching contexts. I find this address by reading proc/self/maps and looking for a line that matches the pattern [stack] -- something like this:

7fffa7774000-7fffa7795000 rwxp 00000000 00:00 0                          [stack]

This is all well and good during normal program execution, where the stack is clearly labelled. However, when I execute the program using Valgrind Memcheck, the [stack] memory segment is not my program's execution stack, but rather Valgrind's, and my program presumably uses some anonymous segment allocated by Valgrind. My program, unaware that it is being run by Valgrind and that it is not using the [stack] block, blindly gets the lowest address of Valgrind's stack and starts doing whatever it pleases down there. Bad news, obviously.

I have no idea how to reliably get the lowest address of my stack given this unique situation. I would like to maintain my current approach of reading from proc/self/maps if possible by tweaking it to account for this edge case. I suspect this may be possible with some advanced knowledge of how Valgrind runs the program it's testing, but haven't been able to find good information on that.

I acknowledge that that may not be feasible or practical, though, and so would love to hear any solutions for how I can reliably get the lowest address of my program's execution stack, even when executing my program through Valgrind.

In case it's relevant, I am using C17, Valgrind 3.21.0, Fedora Linux 38, Intel x86_64 architecture.

Thank you!

1

There are 1 answers

0
Paul Floyd On BEST ANSWER

Valgrind does its best to make the guest environment appear just like the guest is running standalone. It isn't possible to have perfect emulation. For instance there are differences

  • in the file handle limits (since Valgrind uses some file handles)
  • in the auxiliary vector auxv
  • in the CPUID instruction

Valgrind does try to mask some of the differences between the host and the guest /proc/self. There's handling for path, fd, exe, cmdline, psinfo (depending on the platform).

Valgrind does make use of /proc/self/maps on Linux and Solaris. Valgrind will read its own debug info (which it will use to print its own callstack in case it crashes). In order to do that it needs to find itself in the memory map and also get the associated file and ELF information.

Valgrind doesn't try to sanitize /proc/self/maps to hide memory mappings that it is using for itself. I do not think that would be a good idea. It would give the false impression that memory ranges are available when they aren't.

Valgrind does not use the stack that it gets from the OS for its own use. In fact it uses two stacks. The very first things that Valgrind does on startup is to switch to using a small-ish fixed sized interim stack of about 1Mbytes. That's enough for it to bootstrap itself. When Valgrind is ready to start VEX (the virtual CPU) running the guest code it transfers to another stack. This final stack is dynamically allocated and the size can be controlled by a command line argument.

Valgrind needs to do a fair bit of work to synthesize the client stack. It needs to build argv/auxv/env. The client stack location is hard coded at 128Gbytes less 1 (0x0000001fffffffff) at least on FreeBSD/Linux/Solaris 64bit systems.

You can get more info if you run Valgrind with -d -d, the following piped through 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 will also print various memory maps at various stages (bootstrap, guest start and guest end). You can compare that to /proc/self/maps.