3

For debugging purposes, I would like malloc to return the same addresses every time the program is executed, however in MSVC this is not the case. For example:

#include <stdlib.h>
#include <stdio.h>

int main() {
    int test = 5;
    printf("Stack: %p\n", &test);
    printf("Heap: %p\n", malloc(4));
    return 0;
}

Compiling with cygwin's gcc, I get the same Stack address and Heap address everytime, while compiling with MSVC with aslr off...

cl t.c /link /DYNAMICBASE:NO /NXCOMPAT:NO

...I get the same Stack address every time, but the Heap address changes.

I have already tried adding the registry value HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\MoveImages but it does not work.

  • 3
    That's not guaranteed behavior of `malloc` and is never specified anywhere. If you have other requirements you should consider using your own wrapper for `malloc` that could satisfy your requirements. But remember to only use that wrapper for the testing you need. – Some programmer dude May 11 '20 at 21:57
  • 2
    You can use `VirtualAlloc()` in windows to allocate a specific address. See the following question: https://stackoverflow.com/questions/10364582/can-i-allocate-a-specific-memory-address-using-pointers-in-c – Enosh Cohen May 12 '20 at 06:47
  • 1
    @Someprogrammerdude While deterministic return values are not a guaranteed behaviour of malloc, and may be unachievable in systems lacking virtual memory hardware, it is a property useful for debugging. Similarly, that's one of the reasons pseudo-random generators offer a seed function. – Diomidis Spinellis May 12 '20 at 07:00
  • I suggest adding the "windows" tag to this question since it applies to all programs running under Windows, not just those compiled with MSVC. – Scott McPeak Apr 03 '21 at 14:48

3 Answers3

2

Both the stack address and the pointer returned by malloc() may be different every time. As a matter of fact both differ when the program is compiled and run on Mac/OS multiple times.

The compiler and/or the OS may cause this behavior to try and make it more difficult to exploit software flaws. There might be a way to prevent this in some cases, but if your goal is to replay the same series of malloc() addresses, other factors may change the addresses, such as time sensitive behaviors, file system side effects, not to mention non-deterministic thread behavior. You should try and avoid relying on this for your tests.

Note also that &test should be cast as (void *) as %p expects a void pointer, which is not guaranteed to have the same representation as int *.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
2

It turns out that you may not be able to obtain deterministic behaviour from the MSVC runtime libraries. Both the debug and the production versions of the C/C++ runtime libraries end up calling a function named _malloc_base(), which in turn calls the Win32 API function HeapAlloc(). Unfortunately, neither HeapAlloc() nor the function that provides its heap, HeapCreate(), document a flag or other way to obtain deterministic behaviour.

You could roll up your own allocation scheme on top of VirtualAlloc(), as suggested by @Enosh_Cohen, but then you'd loose the debug functionality offered by the MSVC allocation functions.

Diomidis Spinellis
  • 18,734
  • 5
  • 61
  • 83
0

Diomidis' answer suggests making a new malloc on top of VirtualAlloc, so I did that. It turned out to be somewhat challenging because VirtualAlloc itself is not deterministic, so I'm documenting the procedure I used.

First, grab Doug Lea's malloc. (The ftp link to the source is broken; use this http alternative.)

Then, replace the win32mmap function with this (hereby placed into the public domain, just like Doug Lea's malloc itself):

static void* win32mmap(size_t size) {
  /* Where to ask for the next address from VirtualAlloc. */
  static char *next_address = (char*)(0x1000000);

  /* Return value from VirtualAlloc. */
  void *ptr = 0;

  /* Number of calls to VirtualAlloc we have made. */
  int tries = 0;

  while (!ptr && tries < 100) {
    ptr = VirtualAlloc(next_address, size,
                       MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
    if (!ptr) {
      /* Perhaps the requested address is already in use.  Try again
       * after moving the pointer. */
      next_address += 0x1000000;
      tries++;
    }
    else {
      /* Advance the request boundary. */
      next_address += size;
    }
  }

  /* Either we got a non-NULL result, or we exceeded the retry limit
   * and are going to return MFAIL. */
  return (ptr != 0)? ptr: MFAIL;
}

Now compile and link the resulting malloc.c with your program, thereby overriding the MSVCRT allocator.

With this, I now get consistent malloc addresses.

But beware:

  • The exact address I used, 0x1000000, was chosen by enumerating my address space using VirtualQuery to look for a large, consistently available hole. The address space layout appears to have some unavoidable non-determinism even with ASLR disabled. You may have to adjust the value.

  • I confirmed this works, in my particular circumstances, to get the same addresses during 100 sequential runs. That's good enough for the debugging I want to do, but the values might change after enough iterations, or after rebooting, etc.

  • This modification should not be used in production code, only for debugging. The retry limit is a hack, and I've done nothing to track when the heap shrinks.

Scott McPeak
  • 8,803
  • 2
  • 40
  • 79