4

I am debugging a multi-threaded C++ application under Visual Studio 2017. The type of problem can be reproduced with the following code sample

  int* i = new int();
  *i = 4;
  int* j = i;
  delete i;
  //perhaps some code or time passes
  *j = 5;//write to freed momory = possible heap corruption!!

I've used the built in heap checker to find the type of problem with flags:

 _CrtSetDbgFlag (_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_DELAY_FREE_MEM_DF )

Then used _ASSERTE(_CrtCheckMemory()); to try to narrow it down - but only to conclude it was in another thread. Looking at other threads by the time the corruption was detected seems to just be doing 'normal' stuff and not be in the app's code at the time. Reports look like this:

HEAP CORRUPTION DETECTED: on top of Free block at 0x00000214938B88D0.
CRT detected that the application wrote to a heap buffer that was freed.
DAMAGED located at 0x00000214938B88D0 is 120 bytes long.
Debug Assertion Failed!

Everytime 120 bytes - but the address varies. (the way it's detected is that the 0xdddddddd pattern has been overwritten when the heap is checked) Either finding the allocation or finding the offending write would be helpfull. I've tried to use 'gflags.exe', but I was unable to find this type of corruption (as I understand its mainly designed to find buffer overruns) and I do not have previous experience with this tool. If I could find the "unique allocation number" from the address, that might also be helpfull.

SergeyA
  • 61,605
  • 5
  • 78
  • 137
darune
  • 10,480
  • 2
  • 24
  • 62
  • Can you figure out the address before it gets written to? If so, you can use a data breakpoint in visual studio. – Tony J Apr 04 '19 at 19:07
  • @TonyJ thats not possible – darune Apr 04 '19 at 19:09
  • 1
    Years ago I used to use MemoryValidator (paid product) to help with this however it is very slow to do heap corruption detection in a complex multithreaded application. – drescherjm Apr 04 '19 at 19:10
  • Is this a "Find out which variable is is getting whacked and how?" question or do you already know which variable is getting whacked? – user4581301 Apr 04 '19 at 19:11
  • Is installing clang with it's sanitizers out of question? – SergeyA Apr 04 '19 at 19:12
  • 3
    There are ways to make the memory allocation deterministic, once you get the address, re-run with a data break point. https://stackoverflow.com/questions/35024481/how-to-make-memory-allocation-in-msvc-c-deterministic – Tony J Apr 04 '19 at 19:15
  • @SergeyA I think that's a valuable answer - this is the first I'd heard of [AddressSanitizer](https://github.com/google/sanitizers/wiki/AddressSanitizer), but it sounds like a good tool to help. – mgarey Apr 04 '19 at 19:17
  • @TonyJ that will not work well here, as this bug is happening too late and allocations happend randomly due to timing etc. (I could try to make it deterministic but that would require some effort) – darune Apr 04 '19 at 19:18
  • 2
    valgrind is also pretty easy, but i'd recommend glang++ with `-fsanitize=address` – Cruz Jean Apr 04 '19 at 19:19
  • @Cruz Valgrind runs on Windows these days? – Voo Apr 04 '19 at 19:23
  • @user4581301 I don't know the variable in advance in the real case - i have a test program where i do though (like the sample code) – darune Apr 04 '19 at 19:30
  • @Voo [Apparently yes](http://www.albertgao.xyz/2016/09/28/how-to-use-valgrind-on-windows/), but I was mostly just giving another alternative sanitize option – Cruz Jean Apr 04 '19 at 19:30
  • See also [how to use AddressSanitizer in gcc](https://stackoverflow.com/questions/37970758/how-to-use-addresssanitizer-in-gcc). – mgarey Apr 04 '19 at 19:31
  • @Cruz Most non-trivial programs developed in VS wouldn't compile under Linux I'd think, but I agree there might be exceptions and using WSL sounds pretty interesting. – Voo Apr 04 '19 at 19:35
  • @Voo I do a lot of msvc/gcc stuff and, though there are some differences, if you disable msvc language extensions with `/Za` they're mostly compatible - (aside from msvc's abysmal handling of fold expressions except in the very latest builds). – Cruz Jean Apr 04 '19 at 19:39
  • Heap memory can be hard to inspect directly due to fragmentation. Have you considered instrumentation via a [library that makes allocations compact](https://github.com/soryy708/StackHeap)? That way you can inspect your memory and see what exactly is getting corrupted. After that, you can do root cause analysis. – Ivan Rubinson Apr 04 '19 at 19:49
  • @Cruz Haven't done much C++, particularly not with MSVC in the last few years. It used to be quite the challenge, good to hear it got better. – Voo Apr 04 '19 at 19:55
  • @Voo MSVC2015 was a massive leap forward in Standard support. – user4581301 Apr 04 '19 at 20:16
  • @IvanRubinson i can already inspect the memory address – darune Apr 04 '19 at 20:46
  • Apparently the code must be voluminous or it shouldn't be to difficult to track down. If you can compile with `gcc -g` then `valgrind` (it will report what additional options to use) does a very good job of giving the exact line-number where memory problems occur. (another good case for why using smart-pointers helps `:)` – David C. Rankin Apr 04 '19 at 22:15
  • https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/crtsetallochook?view=vs-2019 Try hooking in it, and track each allocation/free, and with the address you can least see what the memory use to be and see if you have a dangling pointer to it in your code. – Tony J Apr 05 '19 at 21:35
  • @TonyJ I actually tried something like that - but it turns out theres no address on allocation and no request number reported on release. Im currently exploring some options for 'guarding pages' with VirtualAlloc – darune Apr 08 '19 at 07:23

1 Answers1

0

Here is the code i used to track it down. Executive summary: It leaves pages in virtual memory uncommitted instead of fully releasing. This means an attempt to use such a page will fail.

DWORD PageSize = 0;

namespace {
  inline void SetPageSize()
  {
    if (!PageSize)
    {
      SYSTEM_INFO sysInfo;
      GetSystemInfo(&sysInfo);
      PageSize = sysInfo.dwPageSize;
    }
  }

  void* alloc_impl(size_t nSize) {
    SetPageSize();
    size_t Extra = nSize % PageSize;
    if (Extra != 0 || nSize == 0) {
      nSize = nSize + (PageSize - Extra);
    }
    void* res = VirtualAlloc(0, nSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (!res) {
      DWORD errorMessageID = ::GetLastError();
      LPSTR messageBuffer = nullptr;
      size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);

      throw std::bad_alloc{};
    }
    if (reinterpret_cast<size_t>(res) % PageSize != 0) {
      throw std::bad_alloc{};
    }

    return res;
  }

  void dealloc_impl(void* pPtr) {
    if (pPtr == nullptr) {
      return;
    }


    VirtualFree(pPtr, 0, MEM_DECOMMIT);


  }
}


void* operator new (size_t nSize)
{
  return alloc_impl(nSize);
}

void operator delete (void* pPtr)
{
  dealloc_impl(pPtr);
}
void *operator new[](std::size_t nSize) throw(std::bad_alloc)
{
  return alloc_impl(nSize);
}
void operator delete[](void *pPtr) throw()
{
  dealloc_impl(pPtr);
}
darune
  • 10,480
  • 2
  • 24
  • 62
  • This is basically what [heob](https://github.com/ssbssa/heob) does if you enable freed memory protection (`-f1`). – ssbssa Jun 10 '20 at 17:34