⛏️ index : haiku.git

/*
 * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */

#include <debug_paranoia.h>

#include <sys/param.h>

#include <new>

#include <OS.h>

#include <tracing.h>
#include <util/AutoLock.h>


#if ENABLE_PARANOIA_CHECKS


// #pragma mark - CRC-32


static const uint32 kCRC32Polynom = 0x04c11db7;
static uint32 sCRC32Table[256];


static uint32
crc32_reflect(uint32 value, int32 bits)
{
	uint32 result = 0;
	for (int32 i = 1; i <= bits; i++) {
		if (value & 1)
			result |= 1 << (bits - i);
		value >>= 1;
	}

	return result;
}


static void
init_crc32_table()
{
	for (int32 i = 0; i < 256; i++) {
		sCRC32Table[i] = crc32_reflect(i, 8) << 24;
		for (int32 k = 0; k < 8; k++) {
			sCRC32Table[i] = (sCRC32Table[i] << 1)
				^ (sCRC32Table[i] & (1 << 31) ? kCRC32Polynom : 0);
		}
		sCRC32Table[i] = crc32_reflect(sCRC32Table[i], 32);
	}
}


static uint32
crc32(const void* _data, size_t size)
{
	uint8* data = (uint8*)_data;
	uint32 crc = 0xffffffff;

	while (size-- > 0) {
		crc = (crc >> 8) ^ sCRC32Table[(crc & 0xff) ^ *data];
		data++;
	}

	return crc;
}


// #pragma mark - ParanoiaCheck[Set]


class ParanoiaCheckSet;

class ParanoiaCheck {
public:
	ParanoiaCheck(const void* address, size_t size)
		:
		fAddress(address),
		fSize(size)
	{
		Update();
	}

	const void*		Address() const	{ return fAddress; }
	size_t			Size() const	{ return fSize; }

	void Update()
	{
		fCheckSum = crc32(fAddress, fSize);
	}

	bool Check() const
	{
		return crc32(fAddress, fSize) == fCheckSum;
	}

private:
	const void*		fAddress;
	size_t			fSize;
	uint32			fCheckSum;
	ParanoiaCheck*	fNext;

	friend class ParanoiaCheckSet;
};


class ParanoiaCheckSet {
public:
	ParanoiaCheckSet(const void* object, const char* description)
		:
		fObject(object),
		fDescription(description),
		fChecks(NULL)
	{
	}

	const void* Object() const		{ return fObject; }
	const char* Description() const	{ return fDescription; }

	ParanoiaCheck* FirstCheck() const
	{
		return fChecks;
	}

	ParanoiaCheck* NextCheck(ParanoiaCheck* check) const
	{
		return check->fNext;
	}

	ParanoiaCheck* FindCheck(const void* address) const
	{
		ParanoiaCheck* check = fChecks;
		while (check != NULL && check->Address() != address)
			check = check->fNext;
		return check;
	}

	void AddCheck(ParanoiaCheck* check)
	{
		check->fNext = fChecks;
		fChecks = check;
	}

	void RemoveCheck(ParanoiaCheck* check)
	{
		if (check == fChecks) {
			fChecks = check->fNext;
			return;
		}

		ParanoiaCheck* previous = fChecks;
		while (previous != NULL && previous->fNext != check)
			previous = previous->fNext;

		// if previous is NULL (which it shouldn't be), just crash here
		previous->fNext = check->fNext;
	}

	ParanoiaCheck* RemoveFirstCheck()
	{
		ParanoiaCheck* check = fChecks;
		if (check == NULL)
			return NULL;

		fChecks = check->fNext;
		return check;
	}

	void SetHashNext(ParanoiaCheckSet* next)
	{
		fHashNext = next;
	}

	ParanoiaCheckSet* HashNext() const
	{
		return fHashNext;
	}

private:
	const void*			fObject;
	const char*			fDescription;
	ParanoiaCheck*		fChecks;
	ParanoiaCheckSet*	fHashNext;
};


union paranoia_slot {
	uint8			check[sizeof(ParanoiaCheck)];
	uint8			checkSet[sizeof(ParanoiaCheckSet)];
	paranoia_slot*	nextFree;
};


// #pragma mark - Tracing


#if PARANOIA_TRACING


namespace ParanoiaTracing {

class ParanoiaTraceEntry : public AbstractTraceEntry {
	public:
		ParanoiaTraceEntry(const void* object)
			:
			fObject(object)
		{
#if PARANOIA_TRACING_STACK_TRACE
		fStackTrace = capture_tracing_stack_trace(PARANOIA_TRACING_STACK_TRACE,
			1, false);
#endif
		}

#if PARANOIA_TRACING_STACK_TRACE
		virtual void DumpStackTrace(TraceOutput& out)
		{
			out.PrintStackTrace(fStackTrace);
		}
#endif

	protected:
		const void*	fObject;
#if PARANOIA_TRACING_STACK_TRACE
		tracing_stack_trace* fStackTrace;
#endif
};


class CreateCheckSet : public ParanoiaTraceEntry {
	public:
		CreateCheckSet(const void* object, const char* description)
			:
			ParanoiaTraceEntry(object)
		{
			fDescription = alloc_tracing_buffer_strcpy(description, 64, false);
			Initialized();
		}

		virtual void AddDump(TraceOutput& out)
		{
			out.Print("paranoia create check set: object: %p, "
				"description: \"%s\"", fObject, fDescription);
		}

	private:
		const char*	fDescription;
};


class DeleteCheckSet : public ParanoiaTraceEntry {
	public:
		DeleteCheckSet(const void* object)
			:
			ParanoiaTraceEntry(object)
		{
			Initialized();
		}

		virtual void AddDump(TraceOutput& out)
		{
			out.Print("paranoia delete check set: object: %p", fObject);
		}
};


class SetCheck : public ParanoiaTraceEntry {
	public:
		SetCheck(const void* object, const void* address, size_t size,
				paranoia_set_check_mode mode)
			:
			ParanoiaTraceEntry(object),
			fAddress(address),
			fSize(size),
			fMode(mode)
		{
			Initialized();
		}

		virtual void AddDump(TraceOutput& out)
		{
			const char* mode = "??? op:";
			switch (fMode) {
				case PARANOIA_DONT_FAIL:
					mode = "set:   ";
					break;
				case PARANOIA_FAIL_IF_EXISTS:
					mode = "add:   ";
					break;
				case PARANOIA_FAIL_IF_MISSING:
					mode = "update:";
					break;
			}
			out.Print("paranoia check %s object: %p, address: %p, size: %lu",
				mode, fObject, fAddress, fSize);
		}

	private:
		const void*				fAddress;
		size_t					fSize;
		paranoia_set_check_mode	fMode;
};


class RemoveCheck : public ParanoiaTraceEntry {
	public:
		RemoveCheck(const void* object, const void* address, size_t size)
			:
			ParanoiaTraceEntry(object),
			fAddress(address),
			fSize(size)
		{
			Initialized();
		}

		virtual void AddDump(TraceOutput& out)
		{
			out.Print("paranoia check remove: object: %p, address: %p, size: "
				"%lu", fObject, fAddress, fSize);
		}

	private:
		const void*				fAddress;
		size_t					fSize;
		paranoia_set_check_mode	fMode;
};


}	// namespace ParanoiaTracing

#	define T(x)	new(std::nothrow) ParanoiaTracing::x

#else
#	define T(x)
#endif	// PARANOIA_TRACING


// #pragma mark -


#define PARANOIA_HASH_SIZE	PARANOIA_SLOT_COUNT

static paranoia_slot		sSlots[PARANOIA_SLOT_COUNT];
static paranoia_slot*		sSlotFreeList;
static ParanoiaCheckSet*	sCheckSetHash[PARANOIA_HASH_SIZE];
static spinlock				sParanoiaLock;


static paranoia_slot*
allocate_slot()
{
	if (sSlotFreeList == NULL)
		return NULL;

	paranoia_slot* slot = sSlotFreeList;
	sSlotFreeList = slot->nextFree;
	return slot;
}


static void
free_slot(paranoia_slot* slot)
{
	slot->nextFree = sSlotFreeList;
	sSlotFreeList = slot;
}


static void
add_check_set(ParanoiaCheckSet* set)
{
	int slot = (addr_t)set->Object() % PARANOIA_HASH_SIZE;
	set->SetHashNext(sCheckSetHash[slot]);
	sCheckSetHash[slot] = set;
}


static void
remove_check_set(ParanoiaCheckSet* set)
{
	int slot = (addr_t)set->Object() % PARANOIA_HASH_SIZE;
	if (set == sCheckSetHash[slot]) {
		sCheckSetHash[slot] = set->HashNext();
		return;
	}

	ParanoiaCheckSet* previousSet = sCheckSetHash[slot];
	while (previousSet != NULL && previousSet->HashNext() != set)
		previousSet = previousSet->HashNext();

	// if previousSet is NULL (which it shouldn't be), just crash here
	previousSet->SetHashNext(set->HashNext());
}


static ParanoiaCheckSet*
lookup_check_set(const void* object)
{
	int slot = (addr_t)object % PARANOIA_HASH_SIZE;
	ParanoiaCheckSet* set = sCheckSetHash[slot];
	while (set != NULL && set->Object() != object)
		set = set->HashNext();

	return set;
}

// #pragma mark - public interface


status_t
create_paranoia_check_set(const void* object, const char* description)
{
	T(CreateCheckSet(object, description));

	if (object == NULL) {
		panic("create_paranoia_check_set(): NULL object");
		return B_BAD_VALUE;
	}

	InterruptsSpinLocker _(sParanoiaLock);

	// check, if object is already registered
	ParanoiaCheckSet* set = lookup_check_set(object);
	if (set != NULL) {
		panic("create_paranoia_check_set(): object %p already has a check set",
			object);
		return B_BAD_VALUE;
	}

	// allocate slot
	paranoia_slot* slot = allocate_slot();
	if (slot == NULL) {
		panic("create_paranoia_check_set(): out of free slots");
		return B_NO_MEMORY;
	}

	set = new(slot) ParanoiaCheckSet(object, description);
	add_check_set(set);

	return B_OK;
}


status_t
delete_paranoia_check_set(const void* object)
{
	T(DeleteCheckSet(object));

	InterruptsSpinLocker _(sParanoiaLock);

	// get check set
	ParanoiaCheckSet* set = lookup_check_set(object);
	if (set == NULL) {
		panic("delete_paranoia_check_set(): object %p doesn't have a check set",
			object);
		return B_BAD_VALUE;
	}

	// free all checks
	while (ParanoiaCheck* check = set->RemoveFirstCheck())
		free_slot((paranoia_slot*)check);

	// free check set
	remove_check_set(set);
	free_slot((paranoia_slot*)set);

	return B_OK;
}


status_t
run_paranoia_checks(const void* object)
{
	InterruptsSpinLocker _(sParanoiaLock);

	// get check set
	ParanoiaCheckSet* set = lookup_check_set(object);
	if (set == NULL) {
		panic("run_paranoia_checks(): object %p doesn't have a check set",
			object);
		return B_BAD_VALUE;
	}
	
	status_t error = B_OK;

	ParanoiaCheck* check = set->FirstCheck();
	while (check != NULL) {
		if (!check->Check()) {
			panic("paranoia check failed for object %p (%s), address: %p, "
				"size: %lu", set->Object(), set->Description(),
				check->Address(), check->Size());
			error = B_BAD_DATA;
		}

		check = set->NextCheck(check);
	}

	return error;
}


status_t
set_paranoia_check(const void* object, const void* address, size_t size,
	paranoia_set_check_mode mode)
{
	T(SetCheck(object, address, size, mode));

	InterruptsSpinLocker _(sParanoiaLock);

	// get check set
	ParanoiaCheckSet* set = lookup_check_set(object);
	if (set == NULL) {
		panic("set_paranoia_check(): object %p doesn't have a check set",
			object);
		return B_BAD_VALUE;
	}

	// update check, if already existing
	ParanoiaCheck* check = set->FindCheck(address);
	if (check != NULL) {
		if (mode == PARANOIA_FAIL_IF_EXISTS) {
			panic("set_paranoia_check(): object %p already has a check for "
				"address %p", object, address);
			return B_BAD_VALUE;
		}

		if (check->Size() != size) {
			panic("set_paranoia_check(): changing check sizes not supported");
			return B_BAD_VALUE;
		}

		check->Update();
		return B_OK;
	}

	if (mode == PARANOIA_FAIL_IF_MISSING) {
		panic("set_paranoia_check(): object %p doesn't have a check for "
			"address %p yet", object, address);
		return B_BAD_VALUE;
	}

	// allocate slot
	paranoia_slot* slot = allocate_slot();
	if (slot == NULL) {
		panic("set_paranoia_check(): out of free slots");
		return B_NO_MEMORY;
	}

	check = new(slot) ParanoiaCheck(address, size);
	set->AddCheck(check);

	return B_OK;
}


status_t
remove_paranoia_check(const void* object, const void* address, size_t size)
{
	T(RemoveCheck(object, address, size));

	InterruptsSpinLocker _(sParanoiaLock);

	// get check set
	ParanoiaCheckSet* set = lookup_check_set(object);
	if (set == NULL) {
		panic("remove_paranoia_check(): object %p doesn't have a check set",
			object);
		return B_BAD_VALUE;
	}

	// get check
	ParanoiaCheck* check = set->FindCheck(address);
	if (check == NULL) {
		panic("remove_paranoia_check(): no check for address %p "
			"(object %p (%s))", address, object, set->Description());
		return B_BAD_VALUE;
	}

	if (check->Size() != size) {
		panic("remove_paranoia_check(): changing check sizes not "
			"supported");
		return B_BAD_VALUE;
	}

	set->RemoveCheck(check);
	return B_OK;
}


#endif	// ENABLE_PARANOIA_CHECKS


void
debug_paranoia_init()
{
#if ENABLE_PARANOIA_CHECKS
	// init CRC-32 table
	init_crc32_table();

	// init paranoia slot free list
	for (int32 i = 0; i < PARANOIA_SLOT_COUNT; i++)
		free_slot(&sSlots[i]);
#endif
}