オプション付16進ダンプツール作成について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
TKG

オプション付16進ダンプツール作成について

#1

投稿記事 by TKG » 7年前

c言語初心者です。初めて投稿させていただきます。
現在課題でオプション付16進ダンプツールを作成しているのですが、部分部分でうまくいきません。

ダンプツール詳細
・コマンドラインから実行
・ファイルの内容をすべてダンプする通常ダンプの他、開始アドレス・終了アドレスを指定して任意の部分でダンプ(開始、終了どちらかのみの指定も可)、24行単位でページ表示するviewerモードがある
・コマンドライン上のファイル名、オプションは順不同
・viewerオプション(-v)が指定された場合は開始・終了アドレスは無視
・同じオプションが複数指定された場合は後から指定された方を有効とする
・終了アドレスがファイルサイズを超えている場合は、指定を無効としファイル の最後までダンプ
・開始アドレスがファイルサイズ、終了アドレスより大きい場合は、ダンプせ ずに終了
・開始アドレスや終了アドレスが16バイト境界と合わない場合その分を空白で埋める
・Viewer オプションで データ数が 1 ページに満たない場合はアドレスのみ表示してデータは空白で埋める。 ページの先頭にデータが無い場合は、ページの移動を行わない

Viewer モード 詳細
・1 ページ(24 行)分のデータをダンプした後、コマンド入力を受け付け る
・コマンドはf,b,q,?
f … 次のページを表示。 次のページの先頭がファイルサイズを超えている場合は ページ移動をしない
b … 前のページを表示。 前のページがファイルの先頭より前になる場合は、ペー ジ移動しない
q … プログラムを終了
? … Viewer モードで使用可能なコマンドを表示。 enter を押すとダンプ表示を 再開

表示フォーマット
aaaaaaaa:xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx |zzzzzzzzzzzzzzzz
(zzzz.....はASCIIダンプ)

以上のプログラムを作成しているのですが、現状
・開始、終了アドレス指定時はうまくいく
・開始、終了アドレスの指定がない場合最後の方の表示が崩れる
・終了アドレスのみ指定の場合もうまくいく
・開始アドレスのみ指定の場合暴走?する(プログラムが止まらまい)
・vオプション時、bコマンドがうまく動作しない
・vオプション時、コマンド入力を促す「enter command('?')for help:」の分が一行多く表示される
・vオプション時のf,bコマンドについて、次のページ、前のページがない場合の処理がわからない
という状態です。

以上の問題となっている部分、また気づいていない間違っている部分の改善法を教えていただきたいです。
環境は
OS:win8.1
コンパイラ:GCC
です。
よろしくお願いします。

以下コード

コード:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void HexDumping(int page,int begin,int end,FILE *fp);
void Vmode(FILE *fp);
void HowToUse();
int menu();

//グローバル変数
	int begin=0;    //開始アドレスを保存
	int end;    //終了アドレスを保存
	int addr=0;
	int flsz=0;    //ファイルサイズ

int main(int argc,char *argv[])
{
	char a1[64];
	char a2[64];
	
	FILE *fp;
	
	int e=1;    //実行プログラム名はスキップするため0でなく1
	
	for(;e<argc;e++){    //コマンドライン解析
		if(*argv[e]!='-'){    //'-'が先頭についてない→ファイルかどうか判断
			if((fp=fopen(argv[e],"rb"))==NULL){
				printf("need input filename \n");
				HowToUse();
				exit(1);
			}
		}
		
		if(*argv[e]=='-'){    //'-'が先頭に付いている→オプション解析
			if(argv[e][1]=='b'){    //bなら後ろについている数値を保存
				strncpy(a1,argv[e]+2,64);
				begin=strtol(a1,NULL,16);
			}
			else if(argv[e][1]=='e'){    //eなら後ろについている数値を保存
				strncpy(a2,argv[e]+2,64);
				end=strtol(a2,NULL,16);
			}
			else if(argv[e][1]=='v'){    //vならviewermodeへ(ファイルかどうかの判別の前にオプション解析に入った時のためにこちらでもファイルオープン)
				int y=1;
				for(;y<argc;y++){    //コマンドラインからファイルを探す
					if(*argv[y]!='-'){
						if((fp=fopen(argv[y],"rb"))==NULL){
						printf("need input filename \n");
						HowToUse();
						exit(1);
						}
					fseek(fp,0,SEEK_END);    //ファイルサイズ取得
					flsz=ftell(fp);
					rewind(fp);
					}
				}
				Vmode(fp);
			}
			else
				HowToUse();
		}
	}

	fseek(fp,0,SEEK_END);    //ファイルサイズ取得
	flsz=ftell(fp);
	rewind(fp);
	

	if(argc==2)HexDumping(1365,0,flsz,fp);
	else if(end>flsz)HexDumping(1365,begin,flsz,fp);
	else HexDumping(1365,begin,end,fp);    //1365*24でintの最大値
	fclose(fp);
	return 0;
	
}


void HexDumping(int page,int begin,int end,FILE *fp)
{
	int data;
	int a,i;
	int p=0;
	int w=0;
	char buff[256];
	int cnt=0;

	for(;addr<=page*24;addr++){    //24行(-vモードでなければpageに大きい数値代入、-vなら1代入)
		printf("%08x: ",addr);

		for(a=0;a<16;a++){
			data=fgetc(fp);
			cnt++;
			
			if(cnt==end){
				buff[a]='\0';
				for(;a<16;a++)printf("   ");
				printf("|%s\n",buff);
				fclose(fp);
				return;
			}
			if(w!=begin){    //開始アドレスまでの部分を空白で埋める
				buff[a]=' ';
				w++;
			}
			else{
				if(data<=0x20||data>=0x7F) buff[a]='.';    //ASCII範囲か判定、範囲外なら'.'に置き換え
				else buff[a]=data;    //ASCII範囲内ならそのまま
			}
			if(p!=begin){    //開始アドレスまでの部分を空白で埋める
				printf("   ");
				p++;
			}
			else printf("%02X ",data);
		}
		buff[a]='\0';
		printf("|%s\n",buff);    //ASCIIダンプ表示
		
		fseek(fp,1,SEEK_CUR);
	}
}

void Vmode(FILE *fp)
{
	HexDumping(1,0,flsz,fp);    //まず24行ダンプ
	
	int z=2;
	
	char command;
	
	do{
		command=menu();
		
		switch(command){
		case 'f':
//次ページがあるかどうかの判断と処理をいれたい
			HexDumping(z,0,flsz,fp);
			z++;
			break;
		case 'b':
//前ページがあるかどうかの判断と処理をいれたい
			fseek(fp,-16*24*2,SEEK_CUR);
			HexDumping(1,0,flsz,fp);
			
			break;
		case 'q':
			exit(1);
		case '?':
			printf("'f':forward page\n'b':back page\n'q'quit binview\n'?'display this\npress enter...\n");
			getchar();
			break;
		}
	}while(command!='q');
}

int menu()
{
	int command=0;
	char str[64];
	
	do{
		printf("enter command('?' for help):");
		command=getchar();
		printf("\n");
	}while((command!='f')&&(command!='b')&&(command!='q')&&(command!='?'));
	
	return command;
}
	

void HowToUse()
{
printf("usage: binview <filename> [-b<address(hex)>] [-e<address(hex)>] [-v]\n<filename>  ... input filename\n-b<address> ... begin address\n-e<address> ... end address\n-v          ... viewer mode \n");
return;
}

かずま

Re: オプション付16進ダンプツール作成について

#2

投稿記事 by かずま » 7年前

次のように修正すれば多くの問題が解決するでしょう。

コード:

12行目を変更
	int end = -1;    //終了アドレスを保存
67行目に追加
	if (end < 0) end = flsz;
94行目を変更
			if (cnt > end) {
118行目を削除
		//fseek(fp,1,SEEK_CUR);
なぜ、この修正でよいのか考えて説明してください。
そうすれば、次に vオプションの問題に進みます。

TKG

Re: オプション付16進ダンプツール作成について

#3

投稿記事 by TKG » 7年前

>かずまさん
ありがとうございます。返信が遅れ申し訳ありません。
提示いただいたように変更した結果通常ダンプ、アドレス指定ダンプがうまくいきました。

12行目
int end;をint end=-1;に変更については、endの値が未定義のままだと後の条件判断ができないためでしょうか。

67行目
if(end<0)end=flsz;については、コマンドライン上で終了アドレスオプションが指定されなかったときにファイル終端までダンプするためのものだと考えています。

94行目
if(cnt==end){をif(cnt>end){に変更については、cnt==endだと一文字分ダンプ不足になるからだと考えています。

118行目
これに関しては、作業中のコピペミスです。この場所でファイル位置を1進めてもダンプがずれるだけでした。確認不足で申し訳ありません。

かずま

Re: オプション付16進ダンプツール作成について

#4

投稿記事 by かずま » 7年前

他にもおかしなところがいくつかあります。

・ファイルの指定がない場合、fp が不定になる。
・ファイルの指定が複数ある場合、fopen が何度も実行される。
・fclose が main と HexDumping の両方にある。
・表示されるアドレスが、0x10ではなく、1ずつ増える。
・-b を指定した場合でも アドレス 0 から表示するので、大量の無駄な行が表示される。

vオプションの処理の前に、上記の問題を修正したと思われるコードを書いてみました。

コード:

#include <stdio.h>   // fseek, ftell, fgetc, printf, puts
#include <stdlib.h>  // strtol, exit
#include <ctype.h>   // isprint

void HexDumping(int begin, int end, FILE *fp);
void HowToUse();

int main(int argc, char *argv[])
{
	FILE *fp;
	char *name = NULL;
	int begin = 0, end = -1, i;

	for (i = 1; i < argc; i++)
		if (argv[i][0] == '-')
			if (argv[i][1] == 'b') begin = strtol(argv[i] + 2, NULL, 16);
			else if (argv[i][1] == 'e') end = strtol(argv[i] + 2, NULL, 16);
			else HowToUse();
		else
			if (name) HowToUse(); // file name specified again
			else name = argv[i];

	if (!name) HowToUse();  // no file name specified
	fp = fopen(name, "rb");
	if (!fp) return printf("can't open %s\n", name), 1;

	fseek(fp, 0, SEEK_END);
	i = ftell(fp);  // file size
	rewind(fp);

	if (end < 0 || end > i) end = i;
	HexDumping(begin, end, fp);

	fclose(fp);
	return 0;
}

void HexDumping(int begin, int end, FILE *fp)
{
	int addr = begin & -16;
	fseek(fp, addr, SEEK_SET);
	while (1) {
		char buff[17];
		int i;
		for (i = 0; i < 16; i++, addr++) {
			int data = fgetc(fp);
			if (addr >= end) {
				if (i > 0) {
					buff[i] = '\0';
					while (i++ < 16) printf("   ");
					printf("|%s\n", buff);
				}
				return;
			}
			buff[i] = addr < begin ? ' ' : isprint(data) ? data : '.';
			if (i == 0) printf("%08x: ", addr);
			if (addr < begin) printf("   ");
			else printf("%02X ", data);
		}
		buff[i] = '\0';
		printf("|%s\n", buff);
	}
}

void HowToUse()
{
	puts("usage: binview <filename> [-b<address(hex)>] [-e<address(hex)>]\n"
		 "    <filename>  ... input filename\n"
		 "    -b<address> ... begin address\n"
		 "    -e<address> ... end address");
	exit(1);
}
このプログラムの問題点を指摘していただくか、
分からないところを質問していただくか、
理解したという返事をいただけると、
vオプションについて回答しようと思います。

TKG

Re: オプション付16進ダンプツール作成について

#5

投稿記事 by TKG » 7年前

返信ありがとうございます。
提示いただいたコードの問題点としては、

main関数
・for (i = 1; i < argc; i++)の後のオプション、ファイル判別の処理が{}で括られていない
・begin>iとbegin>endの時の処理がない
・20行目、「name」にコマンドラインから読み取ったオプション以外の文字列を代入する前に
真偽判断をしている

でしょうか。

HexDumping関数についてですが、
・int addr=begin&-16の「&」は条件演算子でしょうか。そうであれば、「beginが真の場合
(開始アドレスが指定されたとき)にaddr=-16」という解釈でいいのでしょうか。

・buff = addr < begin ? ' ' : isprint(data) ? data : '.';について、
この文は「addr<beginが真の場合buff=' '」、「addr<beginが偽の場合においてdataが
表示文字ならbuff=data、dataが表示文字でないならbuff='.'」という解釈でいいのでしょうか。

「int addr=begin&-16」の一文がわからないため、ダンプ関数全体の理解があやふやになっている
状態です。

かずま

Re: オプション付16進ダンプツール作成について

#6

投稿記事 by かずま » 7年前

TKG さんが書きました:
7年前
・for (i = 1; i < argc; i++)の後のオプション、ファイル判別の処理が{}で括られていない
文が一つの場合、{ } で括る必要はありません。
コンパイルエラーにならないし、正しく動きます。
TKG さんが書きました:
7年前
・begin>iとbegin>endの時の処理がない
begin>iの意味が不明です。
begin はダンプ開始アドレスですが、i はアドレスではありません。

begin>end の処理は、ほとんどの場合不要です。
-b200 -e100 と指定しても、HexDumping の中で、addr を begin から始めて、
すぐに addr >= endにひっかかって終了しますから、何も表示されなくて、
それでよいはずです。
実は、-b38 -e34 のように 1行の中で、begin と end が逆順の場合だけ
空白だけの無駄な行が表示されるのが問題です。
TKG さんが書きました:
7年前
・20行目、「name」にコマンドラインから読み取ったオプション以外の文字列を代入する前に
真偽判断をしている
ファイル名が複数指定されていたら困るので、
まだ指定されていないかどうかをチェックしています。

name は NULL に初期化されていて、これはファイル名未指定を表しています。
name が NULL だと、if (name) で name は偽だと判断されて、
else の文が実行され、name がファイル名を指すようになります。

2個目のファイル指定('-' で始まらない文字列)があると、
name は NULL ではないので、if (name) で name は真だと判断されて
HowToUse() に行き、終了します。
TKG さんが書きました:
7年前
・int addr=begin&-16の「&」は条件演算子でしょうか。そうであれば、「beginが真の場合
(開始アドレスが指定されたとき)にaddr=-16」という解釈でいいのでしょうか。
& はビット単位の AND演算子です。

-b58 という指定があった時でも、表示の 1行目は
00000050 から 00000057 までを空白にし、
00000058 から 0000005f までを表示しなければなりません。
すなわち、フィルの読み出し開始のアドレスは 00000050で、
begin の 00000058 からそれを作り出さないといけません。
16進数の最下位一桁を 0 にしないといけないのです。
そのために begin & 0xfffffff0 というビットAND演算が必要です。
0xfffffff0 は -16 なので、begin & -16 と書きました。
TKG さんが書きました:
7年前
・buff = addr < begin ? ' ' : isprint(data) ? data : '.';について、
この文は「addr<beginが真の場合buff=' '」、「addr<beginが偽の場合においてdataが
表示文字ならbuff=data、dataが表示文字でないならbuff='.'」という解釈でいいのでしょうか。

その通りです。

vオプションの処理を加えたコードを示します。

コード:

#include <stdio.h>   // fseek, ftell, fgetc, printf, puts, fgets
#include <stdlib.h>  // strtol, exit
#include <ctype.h>   // isprint

enum { LINE = 24, PAGE = LINE * 16 };

void HexDumping(FILE *fp, int begin, int end, int vmode);
int Vmode(int begin, int end, int top);
void HowToUse(void);

int main(int argc, char *argv[])
{
	FILE *fp;
	char *name = NULL;
	int begin = 0, end = -1, vmode = 0, i;

	for (i = 1; i < argc; i++)
		if (argv[i][0] == '-')
			if (argv[i][1] == 'b') begin = strtol(argv[i] + 2, NULL, 16);
			else if (argv[i][1] == 'e') end = strtol(argv[i] + 2, NULL, 16);
			else if (argv[i][1] == 'v') vmode = 1;
			else HowToUse();
		else
			if (name) HowToUse(); // file name specified again
			else name = argv[i];

	if (!name) HowToUse();  // no file name specified
	fp = fopen(name, "rb");
	if (!fp) return printf("can't open %s\n", name), 1;

	fseek(fp, 0, SEEK_END);
	i = ftell(fp);  // file size
	if (end < 0 || end > i) end = i;

	fseek(fp, begin & -16, SEEK_SET);
	if (begin < end) HexDumping(fp, begin, end, vmode);
	fclose(fp);
	return 0;
}

void HexDumping(FILE *fp, int begin, int end, int vmode)
{
	int addr = begin & -16, top = addr, line = 0, i;
	char buff[17] = "";

	while (addr < end) {
		for (i = 0; i < 16 && addr < end; i++, addr++) {
			int data = fgetc(fp);
			if (i == 0) printf("%08x: ", addr);
			if (addr < begin) printf("   "), buff[i] = ' ';
			else printf("%02X ", data), buff[i] = isprint(data) ? data : '.';
		}
		while (i < 16) printf("   "), buff[i++] = ' ';
		printf("|%s\n", buff);
		if (vmode && (++line >= LINE || addr >= end)) {
			addr = top = Vmode(begin, end, top);
			fseek(fp, top, SEEK_SET);
			line = 0;
		}
	}
}

int Vmode(int begin, int end, int top)
{
	while (1) {
		char buf[256];
		printf("enter command('?' for help): ");
		if (!fgets(buf, sizeof buf, stdin)) exit(1);
		switch (buf[0]) {
		case 'f': case '\n': if (top + PAGE < end) top += PAGE; return top;
		case 'b': if (top - PAGE >= (begin & -16)) top -= PAGE; return top;
		case 'q': exit(0);
		}
		puts("  f: forward page\n" "  b: back page\n"
			 "  q: quit binview\n" "  ?: display this");
	}
}

void HowToUse(void)
{
	puts("usage: binview <filename> [-b<address(hex)>] [-e<address(hex)>] [\v]\n"
		 "    <filename>  ... input filename\n"
		 "    -b<address> ... begin address\n"
		 "    -e<address> ... end address\n"
		 "    -v          ... viewer mode");
	exit(1);
}

TKG

Re: オプション付16進ダンプツール作成について

#7

投稿記事 by TKG » 7年前

返信いただきありがとうございます。
提示いただいたコードを見て、可読性の差に驚きました。
AND演算子や列挙型、三項演算子など、あまり馴染みのないものが多く理解に時間がかかりそうですが、答えを示していただいたということで、もう一度初めから考え直してみたいと思います。
今回はありがとうございました。

かずま

Re: オプション付16進ダンプツール作成について

#8

投稿記事 by かずま » 7年前

TKG さんが書きました:
7年前
提示いただいたコードを見て、可読性の差に驚きました。
AND演算子や列挙型、三項演算子など、あまり馴染みのないものが多く理解に時間がかかりそうですが、
AND演算子や列挙型、三項演算子などをやめて、馴染みのある書き方に
変えてみました。これなら理解に時間がかかりませんか?

コード:

#include <stdio.h>   // fseek, ftell, fgetc, printf, puts, fgets
#include <stdlib.h>  // strtol, exit
#include <ctype.h>   // isprint

#define LINE  24
#define PAGE  (LINE * 16)

void HexDumping(FILE *fp, int begin, int end, int vmode);
int Vmode(int begin, int end, int top);
void HowToUse(void);

int main(int argc, char *argv[])
{
	FILE *fp;
	char *name = NULL;
	int begin = 0;
	int end = -1;
	int vmode = 0;
	int i;

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			if (argv[i][1] == 'b') {
				begin = strtol(argv[i] + 2, NULL, 16);
			}
			else if (argv[i][1] == 'e') {
				end = strtol(argv[i] + 2, NULL, 16);
			}
			else if (argv[i][1] == 'v') {
				vmode = 1;
			}
			else {
				HowToUse();
			}
		}
		else {
			if (name != NULL) {
				HowToUse(); // file name specified again
			}
			else {
				name = argv[i];
			}
		}
	}
	if (name == NULL) {
		HowToUse();  // no file name specified
	}
	fp = fopen(name, "rb");
	if (fp == NULL) {
		printf("can't open %s\n", name);
		return 1;
	}
	fseek(fp, 0, SEEK_END);
	i = ftell(fp);  // file size
	if (end < 0 || end > i) {
		end = i;
	}
	fseek(fp, begin - begin % 16, SEEK_SET);
	if (begin < end) {
		HexDumping(fp, begin, end, vmode);
	}
	fclose(fp);
	return 0;
}

void HexDumping(FILE *fp, int begin, int end, int vmode)
{
	int addr = begin - begin % 16;
	int top = addr;
	int line = 0;
	int i;
	char buff[17] = "";

	while (addr < end) {
		for (i = 0; i < 16 && addr < end; i++) {
			int data = fgetc(fp);
			if (i == 0) {
				printf("%08x: ", addr);
			}
			if (addr < begin) {
				printf("   ");
				buff[i] = ' ';
			}
			else {
				printf("%02X ", data);
				if (isprint(data)) {
					buff[i] = data;
				}
				else {
					buff[i] = ' ';
				}
			}
			addr++;
		}
		while (i < 16) {
			printf("   ");
			buff[i++] = ' ';
		}
		printf("|%s\n", buff);
		if (vmode) {
			++line;
			if (line >= LINE || addr >= end) {
				top = Vmode(begin, end, top);
				addr = top;
				fseek(fp, top, SEEK_SET);
				line = 0;
			}
		}
	}
}

int Vmode(int begin, int end, int top)
{
	while (1) {
		char buf[256];
		printf("enter command('?' for help): ");
		if (fgets(buf, sizeof buf, stdin) == NULL) {
			exit(1);
		}
		switch (buf[0]) {
		case 'f':
		case '\n':
			if (top + PAGE < end) {
				top += PAGE;
			}
			return top;
		case 'b':
			if (top - PAGE >= begin - begin % 16) {
				top -= PAGE;
			}
			return top;
		case 'q':
			exit(0);
		}
		puts("  f: forward page\n" "  b: back page\n"
			 "  q: quit binview\n" "  ?: display this");
	}
}

void HowToUse(void)
{
	puts("usage: binview <filename> [-b<address(hex)>] [-e<address(hex)>] [\v]\n"
		 "    <filename>  ... input filename\n"
		 "    -b<address> ... begin address\n"
		 "    -e<address> ... end address\n"
		 "    -v          ... viewer mode");
	exit(1);
}

返信

“C言語何でも質問掲示板” へ戻る