Newer
Older
c-interpreter / modules / libkc / src / kc_memory_dump.c
Nomura Kei on 9 Aug 2023 8 KB UPDATE
/**
 * @file  kc_memory_dump.c
 * @brief KC メモリダンプモジュール
 * @copyright  2003 - 2023  Nomura Kei
 */
#include <stdio.h>
#include <string.h>

#include <kc_memory_dump.h>



////////////////////////////////////////////////////////////////////////////////
//
//  構造体
//

/**
 * バッファ情報構造体
 */
typedef struct
{
	char*   write_ptr;					//<! 書き込み用ポインタ
	int     rest_size;					//<! 残りサイズ
} KcMemoryDumpBufferInfo;



////////////////////////////////////////////////////////////////////////////////
//
//  プロトタイプ宣言
//
static int  KcMemoryDump_info_column( KcMemoryDumpBufferInfo* info, int bytes, bool binary, bool ascii, int max_column);
static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo* info, const char* msg);
static void KcMemoryDump_dump_info(   KcMemoryDumpBufferInfo* info, KcMemoryEntry* entry, int column);
static void KcMemoryDump_dump_binary( KcMemoryDumpBufferInfo* info, KcMemoryEntry* entry, int bytes);
static void KcMemoryDump_dump_ascii(  KcMemoryDumpBufferInfo* info, KcMemoryEntry* entry, int bytes);
static void KcMemoryDump_format_size(char* buff, size_t buff_size, size_t size);



////////////////////////////////////////////////////////////////////////////////
//
//  関数実装
//

/**
 * 指定されたバイトを ASCII 文字に変換します。
 *
 * @param c バイト
 */
#define KC_MEMORY_DUMP_TO_ASCII(c) (((0x20 <= c) && (c < 0x7F)) ? c : '.')


/**
 * 指定されたメモリエントリの情報を buff に出力します。
 *
 * @param buff      情報を出力するバッファ
 * @param buff_size バッファサイズ
 * @param entry     メモリエントリ
 * @param binary    true の場合、データの16進数情報が出力に追加されます。
 * @param ascii     true の場合、データのASCII 情報が出力に追加されます。
 * @param column    表示文字数
 */
bool kc_memory_dump(char* buff, size_t buff_size, KcMemoryEntry* entry,
		int bytes, bool binary, bool ascii, int column)
{
	if ((int) buff_size < column)
	{	// バッファ不足の場合は何もしない
		return false;
	}

	KcMemoryDumpBufferInfo buff_info;
	buff_info.write_ptr = buff;
	buff_info.rest_size = buff_size;

	// ファイル名:行番号 (size bytes) [func=関数名] を書き込み
	int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column);
	if (info_column <= 0)
	{
		KcMemoryDump_dump_info(&buff_info, entry, column);
		return true;
	}

	KcMemoryDump_dump_info(&buff_info, entry, info_column);

	if (binary)
	{	// 16進数ダンプ
		KcMemoryDump_dump_message(&buff_info, " | ");
		KcMemoryDump_dump_binary(&buff_info, entry, bytes);
	}

	if (ascii)
	{	// ASCII ダンプ
		KcMemoryDump_dump_message(&buff_info, " | ");
		KcMemoryDump_dump_ascii(&buff_info, entry, bytes);
	}
	return true;
}


/**
 * メモリ情報を表示するカラム数を算出します。
 * @code
 * ファイル名:行番号 (size bytes) [func=関数名]
 * @codeend
 *
 * @param info       バッファ情報
 * @param bytes      ダンプするバイト数
 * @param binary     バイナリの16進数情報を出力するか否か
 * @param ascii      ASCIIを出力するか否か
 * @param max_column 最大桁数
 * @return メモリ情報を表示する部分のカラム数
 */
static int KcMemoryDump_info_column(KcMemoryDumpBufferInfo* info,
	int bytes, bool binary, bool ascii, int max_column)
{
	int info_column = (max_column < (info->rest_size - 1))
		? max_column : (info->rest_size - 1);

	info_column -= (binary) ? (bytes * 3) + 3 : 0;
	info_column -= (ascii ) ? (bytes    ) + 3 : 0;

	return info_column;
}


/**
 * 指定された info の write_ptr に指定されたメッセージ msg を書き込みます。
 * info の rest_size が 0 未満の場合は、何もしません。
 * info の rest_size を超えるメッセージは書き込まれません。
 * info->rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。
 *
 * @param info バッファ情報
 * @param msg  書き込むメッセージ
 */
static void KcMemoryDump_dump_message(KcMemoryDumpBufferInfo* info, const char* msg)
{
	if (info->rest_size <= 0)
	{	// 空きバッファサイズ無しのため、何もしない。
		return;
	}
	int write_size = snprintf(info->write_ptr, info->rest_size, msg);
	if (write_size > info->rest_size)
	{	// msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く)
		write_size = (info->rest_size - 1);
	}
	info->write_ptr += write_size;
	info->rest_size -= write_size;
}


/**
 * 指定されたバッファに、指定されたメモリエントリの情報を書き込みます。
 * 書き込む情報の文字数は、column に指定された文字数揃えられます。
 * ※空白でパディングされます。
 *
 * @param info   バッファ情報
 * @param entry  メモリエントリ
 * @param column 制限文字数
 */
static void KcMemoryDump_dump_info(
		KcMemoryDumpBufferInfo* info, KcMemoryEntry* entry, int column)
{
	if (info->rest_size <= 0)
	{	// 空きバッファサイズ無しのため、何もしない。
		return;
	}

	char size_buff[16];
	KcMemoryDump_format_size(size_buff, sizeof(size_buff), entry->size);
	int write_size = snprintf(info->write_ptr, info->rest_size, "%s:%d (%s) [func=%s]", 
				entry->file, entry->line, size_buff, entry->func);
	int padding = column - write_size;
	if (write_size > info->rest_size)
	{	// msg が切り詰められ、info->rest_size - 1 分書き込んだ ('\0'除く)
		write_size = (info->rest_size - 1);
	}

	if (write_size > column)
	{	// 最大文字数より多く書き込んでいたら、最大文字数で制限をかける。
		write_size = column;
	}

	info->write_ptr += write_size;
	info->rest_size -= write_size;
	*(info->write_ptr) = '\0';

	if (padding > 0)
	{
		padding = (padding < info->rest_size) ? padding : (info->rest_size - 1);
		memset(info->write_ptr, ' ', padding);
		info->write_ptr += padding;
		info->rest_size -= padding;
		*(info->write_ptr)  = '\0';
	}
}


/**
 * 指定されたバッファに、指定されたメモリエントリの16進数ダンプを出力します。
 * バッファサイズが不足する場合は、何も出力しません。
 *
 * @param info   バッファ情報
 * @param entry  メモリエントリ
 * @param bytes  ダンプするバイト数
 */
static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo* info, KcMemoryEntry* entry, int bytes)
{
	int required_size = bytes * 3;
	if (info->rest_size < required_size)
	{	// 空きバッファサイズ無しのため、何もしない。
		return;
	}

	const unsigned char* data_ptr = (const unsigned char*) entry->data;
	int                  data_len = ((int) entry->size < bytes) ? (int) entry->size : bytes;

	int idx = 0;
	for (; idx < data_len; idx++)
	{
		int write_size = snprintf(info->write_ptr, info->rest_size, "%02X ", data_ptr[idx]);
		info->write_ptr += write_size;
		info->rest_size -= write_size;
	}
	for (; idx < bytes; idx++)
	{
		int write_size = snprintf(info->write_ptr, info->rest_size, "-- ");
		info->write_ptr += write_size;
		info->rest_size -= write_size;
	}
}


/**
 * 指定されたバッファに、指定されたメモリエントリのASCIIダンプを出力します。
 * バッファサイズが不足する場合は、何も出力しません。
 *
 * @param info   バッファ情報
 * @param entry  メモリエントリ
 * @param bytes  ダンプするバイト数
 */
static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo* info, KcMemoryEntry* entry, int bytes)
{
	int required_size = bytes;
	if (info->rest_size < required_size)
	{	// 空きバッファサイズ無しのため、何もしない。
		return;
	}

	const unsigned char* data_ptr = (const unsigned char*) entry->data;
	int                  data_len = ((int) entry->size < bytes) ? (int) entry->size : bytes;

	int idx = 0;
	for (; idx < data_len; idx++)
	{
		int write_size = snprintf(info->write_ptr, info->rest_size, "%c", KC_MEMORY_DUMP_TO_ASCII(data_ptr[idx]));
		info->write_ptr += write_size;
		info->rest_size -= write_size;
	}
	for (; idx < bytes; idx++)
	{
		int write_size = snprintf(info->write_ptr, info->rest_size, " ");
		info->write_ptr += write_size;
		info->rest_size -= write_size;
	}
}


/**
 * 指定されたサイズ (size) の単位付き文字列表現の文字列を指定されたバッファに格納します。
 * バッファのサイズは、12 Byte 以上である必要があります。
 *
 * @param buff      バッファ
 * @param size      サイズ
 */
static void KcMemoryDump_format_size(char* buff, size_t buff_size, size_t size)
{
	// UINT64_MAX ~ 16EB,  ... PB, EB, ZB, YB, RB, QB
	static const char* SIZE_UNIT[] = { " B", "KB", "MB", "GB", "TB", "PB", "EB" };
	int unit_index = 0;
	double view_size = (double) size;
	while (view_size >= 1024)
	{
		view_size /= 1024;
		unit_index++;
	}
	snprintf(buff, buff_size, "%8.3lf %s", view_size, SIZE_UNIT[unit_index]);
}