blob: f0072eae3b9312a130f3b0d77c6067f8faec8dc6 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
* Copyright 2019 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "PrivateHeap.h"
#include <string.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
namespace {
constexpr uint32_t kInvalidHeapBlockSize = 0xFFFFFFFF;
constexpr int32_t kHeapBlockInUse = 0x01;
constexpr int32_t kHeapBlockFree = 0x10;
constexpr int32_t kInvalidHeaderState = 0xff;
using internal::PrivateHeapBlockHeader;
// this makes life easier, no need to add align offsets.
static_assert(sizeof(PrivateHeapBlockHeader) % kPrivateHeapAllocationAlignment == 0, "Invalid block size.");
uint32_t ComputeHeapBlockChecksum(const PrivateHeapBlockHeader * header)
{
uint32_t checksum = header->prevBytes;
checksum *= 31;
checksum += header->nextBytes;
checksum *= 31;
checksum += header->state;
return checksum;
}
// Advances the heap block to the next value
PrivateHeapBlockHeader * NextHeader(PrivateHeapBlockHeader * start)
{
if (start->nextBytes == kInvalidHeapBlockSize)
{
return nullptr;
}
if (start->checksum != ComputeHeapBlockChecksum(start))
{
ChipLogError(Support, "Corrupted heap: checksum is invalid");
chipDie();
}
return reinterpret_cast<PrivateHeapBlockHeader *>(reinterpret_cast<char *>(start) + sizeof(PrivateHeapBlockHeader) +
start->nextBytes);
}
// Advances the heap block to the previous value
PrivateHeapBlockHeader * PreviousHeader(PrivateHeapBlockHeader * start)
{
if (start->prevBytes == kInvalidHeapBlockSize)
{
return nullptr;
}
if (start->checksum != ComputeHeapBlockChecksum(start))
{
ChipLogError(Support, "Corrupted heap: checksum is invalid");
chipDie();
}
return reinterpret_cast<PrivateHeapBlockHeader *>(reinterpret_cast<char *>(start) - sizeof(PrivateHeapBlockHeader) -
start->prevBytes);
}
void ValidateHeader(const PrivateHeapBlockHeader * header)
{
if (header->state != kHeapBlockFree && header->state != kHeapBlockInUse)
{
ChipLogError(Support, "Invalid header state (neither free nor in use) at %p", header);
chipDie();
}
if (header->checksum != ComputeHeapBlockChecksum(header))
{
ChipLogError(Support, "Corrupted heap: checksum is invalid at %p", header);
chipDie();
}
}
} // namespace
extern "C" void PrivateHeapInit(void * heap, size_t size)
{
if (heap == nullptr)
{
ChipLogError(Support, "Cannot initialize null heap");
chipDie();
}
if (size < 2 * sizeof(PrivateHeapBlockHeader))
{
ChipLogError(Support, "Insufficient space in private heap");
chipDie();
}
if (reinterpret_cast<uintptr_t>(heap) % kPrivateHeapAllocationAlignment != 0)
{
ChipLogError(Support, "Invalid alignment for private heap initialization");
chipDie();
}
PrivateHeapBlockHeader * header = reinterpret_cast<PrivateHeapBlockHeader *>(heap);
header->prevBytes = kInvalidHeapBlockSize;
header->nextBytes = static_cast<uint32_t>(size - 2 * sizeof(PrivateHeapBlockHeader));
header->state = kHeapBlockFree;
header->checksum = ComputeHeapBlockChecksum(header);
header = NextHeader(header);
header->nextBytes = kInvalidHeapBlockSize;
header->prevBytes = static_cast<uint32_t>(size - 2 * sizeof(PrivateHeapBlockHeader));
header->state = kHeapBlockFree; // does not matter really
header->checksum = ComputeHeapBlockChecksum(header);
}
extern "C" void * PrivateHeapAlloc(void * heap, size_t size)
{
PrivateHeapBlockHeader * header = reinterpret_cast<PrivateHeapBlockHeader *>(heap);
// we allocate aligned, no matter what
if (size % kPrivateHeapAllocationAlignment != 0)
{
size += kPrivateHeapAllocationAlignment - (size % kPrivateHeapAllocationAlignment);
}
for (; header != nullptr; header = NextHeader(header))
{
ValidateHeader(header);
if (header->nextBytes == kInvalidHeapBlockSize)
{
continue;
}
if (header->state != kHeapBlockFree)
{
continue; // not free
}
if (header->nextBytes < size)
{
continue; // insufficient space
}
if (header->nextBytes - size < sizeof(PrivateHeapBlockHeader) + kPrivateHeapAllocationAlignment)
{
// allocate the entire block
header->state = kHeapBlockInUse;
header->checksum = ComputeHeapBlockChecksum(header);
}
else
{
// splits the block into two
//
// +--------+ +--------+ +------+
// | header | ---> | middle | ---> | next |
// +--------+ +--------+ +------+
//
PrivateHeapBlockHeader * next = NextHeader(header);
PrivateHeapBlockHeader * middle =
reinterpret_cast<PrivateHeapBlockHeader *>(reinterpret_cast<char *>(header + 1) + size);
// middle is a new block
middle->nextBytes = static_cast<uint32_t>(header->nextBytes - size - sizeof(PrivateHeapBlockHeader));
middle->prevBytes = static_cast<uint32_t>(size);
middle->state = kHeapBlockFree;
middle->checksum = ComputeHeapBlockChecksum(middle);
// fix up the header
header->nextBytes = static_cast<uint32_t>(size);
header->state = kHeapBlockInUse;
header->checksum = ComputeHeapBlockChecksum(header);
// fix up the final block
if (next != nullptr)
{
next->prevBytes = middle->nextBytes;
next->checksum = ComputeHeapBlockChecksum(next);
}
}
// we can now use the header
return header + 1; // data right after the header
}
// no space found
return nullptr;
}
extern "C" void PrivateHeapFree(void * ptr)
{
if (ptr == nullptr)
{
// freeing NULL pointers is always acceptable and a noop
return;
}
PrivateHeapBlockHeader * header =
reinterpret_cast<PrivateHeapBlockHeader *>(static_cast<char *>(ptr) - sizeof(PrivateHeapBlockHeader));
ValidateHeader(header);
header->state = kHeapBlockFree;
header->checksum = ComputeHeapBlockChecksum(header);
// Merge with previous
//
// +-------+ +--------+
// | other | ----- nextBytes -----> | header |
// +-------+ +--------+
//
PrivateHeapBlockHeader * other = PreviousHeader(header);
if (other != nullptr && other->state == kHeapBlockFree && other->nextBytes != kInvalidHeapBlockSize)
{
// includes the free bytes in this block in the previous
other->nextBytes += static_cast<uint32_t>(header->nextBytes + sizeof(PrivateHeapBlockHeader));
other->checksum = ComputeHeapBlockChecksum(other);
header->state = kInvalidHeaderState;
header = other;
// fixes up the next block
other = NextHeader(header);
if (other != nullptr)
{
other->prevBytes = header->nextBytes;
other->checksum = ComputeHeapBlockChecksum(other);
}
}
// Merge with next
//
// +--------+ +-------+
// | header | ----- nextBytes -----> | other |
// +--------+ +-------+
//
other = NextHeader(header);
if (other != nullptr && other->state == kHeapBlockFree && other->nextBytes != kInvalidHeapBlockSize)
{
// includes the free bytes in the next block
other->state = kInvalidHeaderState;
header->nextBytes += static_cast<uint32_t>(other->nextBytes + sizeof(PrivateHeapBlockHeader));
header->checksum = ComputeHeapBlockChecksum(header);
// fixes up the next block
other = NextHeader(header);
if (other != nullptr)
{
other->prevBytes = header->nextBytes;
other->checksum = ComputeHeapBlockChecksum(other);
}
}
}
void * PrivateHeapRealloc(void * heap, void * ptr, size_t size)
{
if (ptr == nullptr)
{
return PrivateHeapAlloc(heap, size);
}
if (size == 0)
{
PrivateHeapFree(ptr);
return nullptr;
}
PrivateHeapBlockHeader * header =
reinterpret_cast<PrivateHeapBlockHeader *>(static_cast<char *>(ptr) - sizeof(PrivateHeapBlockHeader));
ValidateHeader(header);
if (header->nextBytes >= size)
{
return ptr; // no reallocation needed
}
void * largerCopy = PrivateHeapAlloc(heap, size);
if (largerCopy == nullptr)
{
// NOTE: original is left untouched (not freed) to match realloc() libc
// functionality
return nullptr;
}
memcpy(largerCopy, ptr, header->nextBytes);
PrivateHeapFree(ptr);
return largerCopy;
}
extern "C" void PrivateHeapDump(void * top)
{
PrivateHeapBlockHeader * header = reinterpret_cast<PrivateHeapBlockHeader *>(top);
ChipLogProgress(Support, "========= HEAP ===========");
while (header->nextBytes != kInvalidHeapBlockSize)
{
ChipLogProgress(Support, " %td: size: %d, state: %d", reinterpret_cast<char *>(header) - reinterpret_cast<char *>(top),
static_cast<int>(header->nextBytes), static_cast<int>(header->state));
header = NextHeader(header);
}
}