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

#include <kc_memory.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, const KcMemoryEntry *entry, int column);
static void KcMemoryDump_dump_binary(KcMemoryDumpBufferInfo *info, const KcMemoryEntry *entry, int bytes);
static void KcMemoryDump_dump_ascii(KcMemoryDumpBufferInfo *info, const 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    表示文字数
 * @return true/false (出力成功/出力失敗[バッファ不足等])
 */
bool KcMemoryDump_dump(char *buff, size_t buff_size, const KcMemoryEntry *entry,
					   int bytes, bool binary, bool ascii, int column)
{
	// 改行コード <CR><LF> 考慮
	if (((int)buff_size + 2) < column)
	{ // バッファ不足の場合は何もしない
		return false;
	}

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

	// ファイル名:行番号 (size bytes) [関数名] を書き込み
	int info_column = KcMemoryDump_info_column(&buff_info, bytes, binary, ascii, column);
	if (info_column <= 0)
	{
		KcMemoryDump_dump_info(&buff_info, entry, column);
		// 改行追加
		int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n");
		buff_info.write_ptr += write_size;
		buff_info.rest_size -= write_size;
		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);
	}

	// 改行追加
	int write_size = snprintf(buff_info.write_ptr, buff_info.rest_size, "\n");
	buff_info.write_ptr += write_size;
	buff_info.rest_size -= write_size;

	return true;
}

/**
 * メモリ情報を表示するカラム数を算出します。
 * @code
 * ファイル名:行番号 (size bytes) [関数名]
 * @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, const 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) [%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 < info->rest_size となる。
		// 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, const 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, const 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]);
}

////////////////////////////////////////////////////////////////////////////////
//
//  単体テスト用
//  外部からの関数実行によりテスト困難なテストケースを記載する。
//

/**
 * KcMemoryDump_dump_message 単体テスト用関数。
 *
 * 指定された info_write_ptr に指定されたメッセージ msg を書き込みます。
 * info_rest_size が 0 未満の場合は、何もしません。
 * info_rest_size を超えるメッセージは書き込まれません。
 * info_rest_size または、msg のサイズが INT_MAX を超える場合の動作は保証しません。
 *
 * 現状、KcMemoryDump_dump_message は、外部からの実行可能な関数経由で呼び出される際に、
 * 次のエラー処理に到達することはない。(本関数実行前に、サイズ等をチェックしているため。)
 * (1) info の rest_size が 0 未満の場合は、何もしない。
 * (2) info の rest_size を超えるメッセージは書きこまない。
 *
 * @param info_write_ptr 書込み用ポインタ
 * @param info_rest_size サイズ
 * @param msg  書き込むメッセージ
 */
void _UT_KcMemoryDump_dump_message(char *info_write_ptr, int info_rest_size, const char *msg)
{
	KcMemoryDumpBufferInfo info = {
		.write_ptr = info_write_ptr,
		.rest_size = info_rest_size,
	};
	KcMemoryDump_dump_message(&info, msg);
}