Memory corruption with GetProcessHeap/HeapAlloc

2019-02-20 19:09发布

As a learning/testing-my-limits exercise, I'm trying to make a DLL that can take a given value, serialize it, and de-serialize it for a program compiled with another compiler. So far everything's gone better than expected. However, I'm running into what appears to be a memory corruption issue.

Here's the code I'm using:

//POD_base.h: contains the base template POD (plain old data) class. Also contains memory allocation and deallocation functions.
namespace pod_helpers
{
  void* pod_malloc(size_t size)
  {
    HANDLE heapHandle = GetProcessHeap();
    HANDLE storageHandle = nullptr;

    if (heapHandle == nullptr)
    {
      return nullptr;
    }

    storageHandle = HeapAlloc(heapHandle, 0, size);

    return storageHandle;
  }

  void pod_free(void* ptr)
  {
    HANDLE heapHandle = GetProcessHeap();
    if (heapHandle == nullptr)
    {
      return;
    }

    if (ptr == nullptr)
    {
      return;
    }

    HeapFree(heapHandle, 0, ptr);
  }
}

template<typename T>
class pod
{
protected:
  pod();
  pod(const T& value);
  pod(const pod& copy);                   // no copy ctor in any pod
  ~pod();

  pod<T>& operator=(pod<T> value);
  operator T() const;

  T get() const;
  void swap(pod<T>& first, pod<T>& second);
};

A pod specialization for int:

//POD_basic_types.h: contains pod specializations for basic datatypes
template<>
class pod<int>
{
  typedef int original_type; //these typedefs are meant to make the specializations easier to write and understand, since they're all the same except for the underlying datatypes.
  typedef std::int32_t safe_type;

public:
  pod() : data(nullptr) {}

  pod(const original_type& value)
  {
    set_from(value);
  }

  pod(const pod<original_type>& copyVal)
  {
    original_type copyData = copyVal.get();
    set_from(copyData);
  }

  ~pod()
  {
    release();
  }

  pod<original_type>& operator=(pod<original_type> value)
  {
    swap(*this, value);

    return *this;
  }

  operator original_type() const
  {
    return get();
  }

protected:
  safe_type* data;

  original_type get() const
  {
    original_type result;

    result = static_cast<original_type>(*data);

    return result;
  }

  void set_from(const original_type& value)
  {
    data = reinterpret_cast<safe_type*>(pod_helpers::pod_malloc(sizeof(safe_type)));

    if (data == nullptr)
    {
      return;
    }

    new(data) safe_type (value);
  }

  void release()
  {
    if (data)
    {
      pod_helpers::pod_free(data);
      data = nullptr;
    }
  }

  void swap(pod<original_type>& first, pod<original_type>& second)
  {
    using std::swap;

    swap(first.data, second.data);
  }
};

My DLL takes advantage of this behind-the-scenes type conversion like this:

virtual pod<int> Add(const pod<int> number1, const pod<int> number2);

pod<int> CCDLL_v1_implementation::Add(const pod<int> number1, const pod<int> number2)
{
  int workingNum1, workingNum2;

  workingNum1 = number1;
  workingNum2 = number2;

  return workingNum1 + workingNum2;
}

Then my test program loads the DLL via LoadLibrary/GetProcAddress. So far, so good; I've confirmed that the DLL is actually loaded and the Add function called. I've also verified that pod<int>'s set_from is being called with the correct values. However, this is where things break down.

I expect set_from to allocate enough space for one safe_type (in this case, std::int32_t) value, then store the passed value within the allocated memory. When I check the value of *data within set_from, this seems to be the case. However, when I'm retrieving the pod<int>'s value via get, the pod's data appears to be garbage. It no longer points to the value the pod was passed during set_from.

I know set_from is called on the EXE's side and get is called on the DLL's side. My understanding of the process is as follows:

EXE -> creates pod<int> (allocating memory via GetProcessHeap/HeapAlloc) -> constructs the pod with a given value, which is passed to set_from.
The DLL's Add function is called, with the given pod passed to it.
DLL -> accesses pod<int> -> gets the pod's stored value via get -> accesses the memory the EXE allocated
DLL does its calculations (here, a simple addition) with the pod's value
DLL -> creates pod<int> (allocating memory via GetProcessHeap/HeapAlloc) -> constructs the pod with a given value, which is passed to set_from.
The DLL's newly-constructed pod is returned to the EXE.
EXE -> accesses the pod's internal value via get -> accesses the memory the DLL allocated

This seems to be breaking down where either the DLL or the EXE has to access memory the other allocated. I've seen elsewhere on SO that using this combination of GetProcessHeap/HeapAlloc should work across DLL boundaries. HeapCreate's documentation is also suggestive of the same idea:

The memory of a private heap object is accessible only to the process that created it. If a dynamic-link library (DLL) creates a private heap, the heap is created in the address space of the process that calls the DLL, and it is accessible only to that process.

as is the documentation for GetProcessHeap (emphasis mine):

The GetProcessHeap function obtains a handle to the default heap for the calling process.

Oddly enough, however, GlobalAlloc suffered the same problems GetProcessHeap/HeapAlloc did, making me further question what's going wrong here. Even odder, when I compile both the EXE and the DLL with the same compiler, everything works as expected.

Am I making incorrect assumptions about the way this allocation/deallocation process works? Should I be using something other than GetProcessHeap/HeapAlloc? Or am I simply trying to do the impossible?


Update with information gained from the comments:

Passing a pod by reference (CCDLL_v1_implementation::Add(const pod<int>& number1, const pod<int>& number2) works correctly. Only passing by value does not.

It doesn't appear to matter whether I pass "unwrapped" arguments to a pod-taking function or whether I wrap the arguments in pods first:

pod<int> a = 9;
pod<int> b = 2;

CCDLL_lib->Add(a, b);

produces the same corrupted data as

CCDLL_lib->Add(9, 2);

The only difference seems to be that wrapping arguments in pods first will call the copy c'tor when Add is called, and leaving arguments unwrapped will just call the regular c'tor.

This also doesn't seem to be a class layout issue:

if (reinterpret_cast<const void*>(&data) == reinterpret_cast<const void*>(this))
{
  //simple debug messagebox here
}

evaluates to true on both sides of the EXE/DLL boundary.


I've managed to prove the issue lies on the EXE/DLL boundary, although I still don't know why.

I added a new method to the DLL so I could focus on one argument instead of two:

void Square(pod<int> number);

Then I called it like this:

pod<int> a = 9;
CCDLL_lib->Square(a);

From a series of debug messageboxes, the following sequence occurs:

Regular c'tor called (EXE)
set_from(9) called (EXE)
copy c'tor called (EXE)
get returns 9 to the copy c'tor (EXE)
set_from(9) called (EXE)
Square called (DLL) with an invalid pod (Visual Studio shows the data pointer pointing to garbage at this point)

If I change Square to accept a reference instead (virtual void Square(pod<int>& number) = 0;), the following sequence occurs:

Regular c'tor called (EXE)
set_from(9) called (EXE)
Square called (DLL) with a valid pod (Visual Studio shows the data pointer holding correct data)

0条回答
登录 后发表回答