Raspberry Pi 3 Model Bのブート周り

はじめに

ラズパイ3で動くOSを自作するために必要な情報を収集中.基本的に必要な情報はラズパイ公式のドキュメントに書いているみたいだけど,散らばってるし,英語だしよくわからんってことでまとめてみた.

BCM2837

まずはハードウェアについて.BCM2837 - Raspberry Pi Documentationによるとラズパイ3はBCM2837というSoCを積んでいる.そしてVideoCore - WikipediaによるとBCM2837には

  • GPU ... VideoCore 4
  • CPU ... Cortex-A53, 4コア

が含まれている.

ブートプロセス

SoCにはGPUとCPUが含まれていることがわかったので,そのSoCのブートプロセスについて調べる.公式のドキュメントは見つけられなかったので,What is the boot sequence? - Raspberry Pi Stack ExchangeRPi Software - eLinux.orgを参考にする.ブートは次に示す通り,段階的に行われるみたい.

(準備)SDカードにbootcode.bin,start.elf,config.txt,kernel.imgを置いておく(config.txtはオプショナル?)

  1. ラズパイ3に電源を入れる.
  2. GPUが起動.(このときCPUはオフ、SDRAMは無効な状態になっている.)
  3. GPUはSoC内のROMから第1段ブートローダを実行する.(第1段ブートローダはラズパイ製造時にSoC内のROMに書き込まれている.)
  4. 第1段ブートローダ
  5. 第2段ブートローダ
  6. GPUファームウェア
    • SDカードから,config.txtを読み,CPUの設定を行う.
    • SDカードから,カーネルイメージ(kernel.img)を読み,SDRAMにロード.
    • CPUのリセット信号を有効にする.
  7. CPUがSDRAMに置かれたプログラムを実行する.(CPUからGPUに処理が移る)

fixup.datというファイルをSDカード上に用意しておくと,GPUとCPU間のSDRAMパーティションの設定を行えるようだけど,詳細は不明.ファイルもバイナリなので,このファイルについては保留.

config.txt

ブートプロセスに登場するconfig.txtとは何ぞや?ということで調べてみると,ラズパイにはBIOSがないためconfig.txtで様々な設定を行うみたい.config.txt - Raspberry Pi Documentationより,ブートに関する設定の一部を以下にまとめる.

  • kernel = ファイル名

ロードするカーネルを指定.デフォルトでは,ラズパイ3の場合kernel7.imgがロードされる.kernel8.imgが(ブートパーティションに)ある場合は,優先的にそれがロードされCPUは64ビットモードに設定される.
注意:圧縮されてないカーネルイメージファイルでなければならない

  • kernel_address = メモリアドレス

カーネルイメージをロードするメモリアドレスを指定.デフォルトでは,32ビットカーネルの場合0x8000に,32ビットカーネルの場合0x80000にロードされる.

  • kernel_old = 1

kernel_oldを1にするとカーネルイメージはメモリアドレス0x0にロードされる.

  • arm_64bit

これを設定すると、CPUは64ビットモードになる.kernel8.imgを使用すれば,この設定はなくてもよさそう.

UARTって何?

はじめに

UART - Wikipediaによると,

UART (Universal Asynchronous Receiver/Transmitter, ユーアート) は、調歩同期方式によるシリアル信号パラレル信号に変換したり、その逆方向の変換を行うための集積回路
とある.なるほどわからん.まず赤文字で示した単語の意味がわからないのでそれらを調べてみる.

調歩同期方式

  • そもそも,同期って何?

通信の「同期」が重要なワケ | Think IT(シンクイット)によると,

送信側と受信側が正しくタイミングを合わせることを「同期」という
らしい.タイミングを合わせるの意味がわかりにくいけど,届いた信号のどこがデータの始まりでどこが終わりなのかを受信側が見分けられるようにするって意味らしい.

  • 調歩同期方式?

同期をとるための方式の一つで,具体的なルールは

・データ単位を決めておく(ここでは8ビットとする)
・送るデータがないとき,送信側は1を送り続ける
・送るデータがあるとき,送信側は1ビット0を送ってからそのデータ(8ビット)を送る

受信側の目線で見ると,1を受けとっている間は,そのデータには意味がないので何もしなくていい.そして0を受け取った時に,次に来る1ビット目がデータの始まりで8ビット目がデータの終わりであることがわかる.

  • 同期処理してるのに,なぜ非同期方式とも呼ばれるのか?

調歩同期方式は非同期方式とも呼ばれる.調歩同期方式は同期をとるために同期用のクロック信号というのを用いないことから非同期と呼ばれるらしい.ややこしい.

シリアル信号とパラレル信号

シリアル信号はシリアル通信で扱われる信号であることは想像がつくので,シリアル通信について調べる.パラレル信号も同様.

  • シリアル通信?

シリアル通信 - Wikipediaによると,

シリアル通信は,電気通信において伝送路上を一度に1ビットずつ、逐次的にデータを送ることをいう
なるほど.線路が1つあって8両編成の列車がくる感じかな.

  • パラレル通信?

パラレル通信 - Wikipediaによると,

パラレル通信は、複数のデータ信号を同時並行的にそれぞれの通信リンクで送る通信方式である.一般に有線で複数の配線を使って行う.
こっちは線路が8つあって1両の列車がくる感じか.

新たな疑問

ここまでで,

UART (Universal Asynchronous Receiver/Transmitter, ユーアート) は、調歩同期方式によるシリアル信号パラレル信号に変換したり、その逆方向の変換を行うための集積回路
の意味は分かった.でも,「シリアル信号をパラレル信号に変換したいときってどんなとき?」という疑問が湧いてきた.

  • UARTの使用例

マイコンとPC間でシリアル通信したいとき
多くのマイコンではUARTが組み込まれていて,CPU等とUARTがパラレルバスで接続されている.なのでデータを送信する際はCPUから並列にやってきたデータはUARTによって直列に直し,送信する.

ヘッダファイルの勉強

include< >とinclude" "の違い?

include<>を使用すると,プリプロセッサは「システム標準のディレクトリ」内でヘッダーファイルを検索する。
include""を使用すると,プリプロセッサは「カレントディレクトリ⇒コンパイラーの-Iオプションなどで命名されたディレクトリ⇒システム標準のディレクトリ」の順でヘッダーファイルを検索する。

実験

以下の3種類のファイルを作成する。

/* main.c */
#include<stdio.h>
#include<thank.h>

int main(){
	thank();
	return 0;
}

/* thank.h */
void thank(void);

/* thank.c */
#include<stdio.h>
#include<thank.h>

void thank(void){
	printf("Thank you!\n");
}

これでコンパイルすると,

$ gcc main.c thank.c
main.c:3:18: fatal error: thank.h: そのようなファイルやディレクトリはありません
compilation terminated.
thank.c:3:18: fatal error: thank.h: そのようなファイルやディレクトリはありません
compilation terminated.

システム標準のディレクトリにはthank.hがないのでエラーがでる.ということで,以下のように書き直す。

/* main.c */
#include<stdio.h>
#include"thank.h"

int main(){
	thank();
	return 0;
}

/* thank.h */
void thank(void);

/* thank.c */
#include<stdio.h>
#include"thank.h"

void thank(void){
	printf("Thank you!\n");
}

同様にコンパイルすると

$ gcc main.c thank.c
$ ./a.out 
Thank you!

さっきと違い無事に実行ファイルが作成できる。

#ifndefとか#endifって何?

#ifndef
機能 : 識別子が定義されていないかどうかの判定
書式 :

#ifndef <識別子名><処理>

詳細 : <識別子名>が未定義なら<処理>を実行。<処理>が複数行にわたる場合は、処理ブロックの最後を示すために#endifを記述する。

#endif
機能 : #ifdef、#ifndefなどによる処理ブロックの最後を明示
詳細 : #ifndefの結果が「真」なら続く<処理>を1行実行。複数行の処理を実行させたい場合は、その最後に#endifを記述してブロックの終端を示す。処理の構造を分かりやすくするため、1行の処理の最後に#endifを記述してもよい。

実験

以下の2種類のファイルを作成する。

/* main.c */
#include "num.h"
#include "num.h"

int main(){
	return 0;
}

/* num.h */
int number = 3;

これでコンパイルすると,

$ gcc main.c 
In file included from main.c:2:0:
num.h:1:5: error: redefinition of ‘number’
 int number = 3;
     ^
In file included from main.c:1:0:
num.h:1:5: note: previous definition of ‘number’ was here
 int number = 3;
     ^

変数numberが再定義されているというエラーが出る。したがって、num.hを以下のように書きなおす。

/* num.h */
#ifndef _NUM_H_INCLUDED_
#define _NUM_H_INCLUDED_
int number = 3;
#endif

同様にコンパイルすると

$ gcc main.c
$ 

といったように、コンパイル完了しエラーを回避できた。

ELF形式を理解する.2

Step3: セクションヘッダを読んでみる

セクションヘッダテーブル用の構造体を定義する。ELFヘッダから、セクションヘッダは8個あることがわかったので、8つ用意。

/* section_header_read.c */
#include <stdio.h>

short swap_short(unsigned short value);
int swap_int(unsigned int value);

struct section_header{
	int name;
	int type;
	int flags;
	int addr;
	int offset;
	int size;
	int link;
	int info;
	int align;
	int entry_size;
};		  

int main(void){
	struct section_header header[8];
	FILE *fp;
	int i;

	fp = fopen("kzload.elf", "rb");
	fseek(fp, 1312, SEEK_SET);
	fread(&header, sizeof(header), 1, fp);
	for(i=0; i<8; i++){
		printf("section_header %d\n", i+1);
		printf("%08x\n", swap_int(header[i].name));
		printf("%08x\n", swap_int(header[i].type));
		printf("%08x\n", swap_int(header[i].flags));
		printf("%08x\n", swap_int(header[i].addr));
		printf("%08x\n", swap_int(header[i].offset));
		printf("%08x\n", swap_int(header[i].size));
		printf("%08x\n", swap_int(header[i].link));
		printf("%08x\n", swap_int(header[i].info));
		printf("%08x\n", swap_int(header[i].align));
		printf("%08x\n", swap_int(header[i].entry_size));
	}
	fclose(fp);
	return 1;
}

実行すると、標準出力は以下の通り。

section_header 1
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
00000000
section_header 2
0000001b
00000001
00000003
00000000
00000074
00000100
00000000
00000000
00000004
00000000
section_header 3
00000024
00000001
00000006
00000100
00000174
00000128
00000000
00000000
00000002
00000000
section_header 4
0000002a
00000001
00000032
00000228
0000029c
0000000e
00000000
00000000
00000001
00000001
section_header 5
00000032
00000001
00000003
00000238
000002ac
0000000c
00000000
00000000
00000004
00000000
section_header 6
00000011
00000003
00000000
00000000
000004e6
00000038
00000000
00000000
00000001
00000000
section_header 7
00000001
00000002
00000000
00000000
000002b8
00000190
00000007
00000011
00000004
00000010
section_header 8
00000009
00000003
00000000
00000000
00000448
0000009e
00000000
00000000
00000001
00000000

それぞれの意味を調べる。
2つ目のセクションヘッダ

意味 kzload.elf kzload.elfでの意味
セクション名 0x0000001b .shstrtabセクション先頭位置から27バイト目を見る→.vectors
セクションの種類 0x00000001 このセクションはデータ(機械語や初期値など)を持っている
各種フラグ 0x00000003 このセクションはプロセス実行時に「書き込み可能にする必要がある+メモリを占領する」
メモリ上でのセクションのアドレス 0x00000000 0番地
セクションの先頭位置 0x00000074 kzload.elfの先頭から116バイト目
セクションのサイズ 0x00000100 256バイト
関連するセクションの番号 0x00000000 ???
セクション依存の情報 0x00000000 ???
セクションのアラインメント 0x00000004 ???
セクション内のエントリのサイズ 0x00000000 ???

3つ目のセクションヘッダ

セクション名 0x00000024 .shstrtabセクション先頭位置から36バイト目を見る→.text
セクションの種類 0x00000001 このセクションはデータ(機械語や初期値など)を持っている
各種フラグ 0x00000006 このセクションは「プロセス実行時にメモリを占領する+実行可能な機械語を含む」
メモリ上でのセクションのアドレス 0x00000100 256番地
セクションの先頭位置 0x00000174 kzload.elfの先頭から372バイト目
セクションのサイズ 0x00000128 296バイト
関連するセクションの番号 0x00000000 ???
セクション依存の情報 0x00000000 ???
セクションのアラインメント 0x00000002 ???
セクション内のエントリのサイズ 0x00000000 ???

4つ目のセクションヘッダ

セクション名 0x0000002a .shstrtabセクション先頭位置から42バイト目を見る→.rodata
セクションの種類 0x00000001 このセクションはデータ(機械語や初期値など)を持っている
各種フラグ 0x00000032 このセクションは「プロセス実行時にメモリを占領する+マージ可能なデータを含む?+文字列で構成される」
メモリ上でのセクションのアドレス 0x00000228 552番地
セクションの先頭位置 0x0000029c kzload.elfの先頭から668バイト目
セクションのサイズ 0x0000000e 14バイト
関連するセクションの番号 0x00000000 ???
セクション依存の情報 0x00000000 ???
セクションのアラインメント 0x00000001 ???
セクション内のエントリのサイズ 0x00000001 ???

5つ目のセクションヘッダ

セクション名 0x00000032 .shstrtabセクション先頭位置から50バイト目を見る→.data
セクションの種類 0x00000001 このセクションはデータ(機械語や初期値など)を持っている
各種フラグ 0x00000003 このセクションはプロセス実行時に「書き込み可能にする必要がある+メモリを占領する」
メモリ上でのセクションのアドレス 0x00000238 568番地
セクションの先頭位置 0x000002ac kzload.elfの先頭から684バイト目
セクションのサイズ 0x0000000c 12バイト
関連するセクションの番号 0x00000000 ???
セクション依存の情報 0x00000000 ???
セクションのアラインメント 0x00000004 ???
セクション内のエントリのサイズ 0x00000001 ???

6つ目のセクションヘッダ

セクション名 0x00000011 .shstrtabセクション先頭位置から17バイト目を見る→.shstrtab
セクションの種類 0x00000003 このセクションは文字列テーブルをを持っている
各種フラグ 0x00000000 フラグなし
メモリ上でのセクションのアドレス 0x00000000 このセクションはメモリ上に置かない
セクションの先頭位置 0x000004e6 kzload.elfの先頭から1254バイト目
セクションのサイズ 0x00000038 56バイト
関連するセクションの番号 0x00000000 ???
セクション依存の情報 0x00000000 ???
セクションのアラインメント 0x00000001 ???
セクション内のエントリのサイズ 0x00000000 ???

7つ目のセクションヘッダ

セクション名 0x00000001 .shstrtabセクション先頭位置から17バイト目を見る→.symtab
セクションの種類 0x00000002 このセクションはシンボルテーブルを持っている
各種フラグ 0x00000000 フラグなし
メモリ上でのセクションのアドレス 0x00000000 このセクションはメモリ上に置かない
セクションの先頭位置 0x000002b8 kzload.elfの先頭から696バイト目
セクションのサイズ 0x00000190 400バイト
関連するセクションの番号 0x00000007 ???
セクション依存の情報 0x00000011 ???
セクションのアラインメント 0x00000004 ???
セクション内のエントリのサイズ 0x00000010 ???


8つ目のセクションヘッダは

意味 kzload.elf kzload.elfでの意味
セクション名 0x00000009 .shstrtabセクション先頭位置から9バイト目を見る→.strtab
セクションの種類 0x00000003 このセクションは文字列テーブルをもつ
各種フラグ 0x00000000 フラグなし
メモリ上でのセクションのアドレス 0x00000000 ???
セクションの先頭位置 0x00000448 kzload.elfの先頭から1096バイト目
セクションのサイズ 0x0000009e 158バイト
関連するセクションの番号 0x00000000 ???
セクション依存の情報 0x00000000 ???
セクションのアラインメント 0x00000001 ???
セクション内のエントリのサイズ 0x00000000 ???

Step4: まとめ

f:id:bunkyu3:20181008020110p:plain

ELF形式を理解する.1

はじめに

リンカが何をやってるのかが、いまいちわからないのでリンカので出力であるELFフォーマットのファイルを調べてみることにした。1stステップで作られるkzload.elfを先頭から解析してみる。kzload.elfはH8上(ビッグエンディアン)で動かすが、解析はintelの64ビットCPU上(リトルエンディアン)で行うので、エンディアンの変換が必要。よって以下の関数を作成

/* swap.c */
short swap_short(unsigned short value);	//unsigned-->論理シフト可能に
int swap_int(unsigned int value);	//unsigned-->論理シフト可能に

short swap_short(unsigned short value){	//16bit用
	short ret;
	ret = value << 8;
	ret |= value >> 8;
	return ret;
}

int swap_int(unsigned int value){	//32bit用
	int ret;
	ret = value << 24;
	ret |= (value&0x0000FF00) << 8;
	ret |= (value&0x00FF0000) >> 8;
	ret |= value >> 24;
	return ret;
}

以後、このファイルの関数を使う前提で進める。

Step1: ELFヘッダを読んで見る

Executable and Linkable Format - Wikipediaを参考に構造体(全部で52バイト)を定義し、以下のelf_header_read.cを用意。

/* elf_header_read.c */
#include <stdio.h>

short swap_short(unsigned short value);
int swap_int(unsigned int value);

struct elf_header{
	struct{
		unsigned char magic[4];
		unsigned char class;	
		unsigned char format;
		unsigned char version;
		unsigned char abi;
		unsigned char abi_version;
		unsigned char reserve[7];
	}id;
	short type;
	short arch;
	int version;
	int entry_point;
	int program_header_offset;
	int section_header_offset;
	int flags;
	short header_size;
	short program_header_size;
	short program_header_num;
	short section_header_size;
	short section_header_num;
	short section_name_index;
};		  

int main(void){
	struct elf_header header;
	FILE *fp;

	fp = fopen("kzload.elf", "rb");

	fread(&header, sizeof(header), 1, fp);
	printf("%s\n", header.id.magic);
	printf("%d\n", header.id.class);
	printf("%d\n", header.id.format);
	printf("%d\n", header.id.version);
	printf("%d\n", header.id.abi);
	printf("%d\n", header.id.abi_version);
	printf("%s\n", header.id.reserve);
	printf("%04x\n", swap_short(header.type));
	printf("%04x\n", swap_short(header.arch));
	printf("%08x\n", swap_int(header.version));
	printf("%08x\n", swap_int(header.entry_point));
	printf("%08x\n", swap_int(header.program_header_offset));
	printf("%08x\n", swap_int(header.section_header_offset));
	printf("%08x\n", swap_int(header.flags));
	printf("%04x\n", swap_short(header.header_size));
	printf("%04x\n", swap_short(header.program_header_size));
	printf("%04x\n", swap_short(header.program_header_num));
	printf("%04x\n", swap_short(header.section_header_size));
	printf("%04x\n", swap_short(header.section_header_num));
	printf("%04x\n", swap_short(header.section_name_index));

	fclose(fp);
	return 1;
}

実行すると、標準出力は以下の通り。

ELF
1
2
1
0
0

0002
002e
00000001
00000100
00000034
00000520
00810000
0034
0020
0002
0028
0008
0005

Executable and Linkable Format - Wikipediaを参考にそれぞれの意味を調べる。

意味 kzload.elf kzload.elfでの意味
マジック・ナンバ ELF ELF
32ビット/64ビットの区別 1 32ビット
エンディアン 2 ビッグエンディアン
ELF形式のバージョン 1 オリジナルバージョン
OSの種別 0 意味なし(ターゲットプラットフォームに関係なく0にセットすることが多い)
OSのバージョン 0 意味なし
予約 意味なし(未使用)
ファイルの種類 0x0002 ET_EXEC (実行形式ファイル)
CPUの種類 0x002e Hitachi H8/300
ELF形式のバージョン 0x00000001 オリジナルバージョン
実行開始アドレス 0x00000100 ???
プログラムヘッダテーブルの先頭位置 0x00000034 kzload.elfの先頭から52バイト目
セクションヘッダテーブルの先頭位置 0x00000520 kzload.elfの先頭から1312バイト目
各種フラグ 0x00810000 ???(ターゲットによって解釈が変わるらしいが詳細不明)
ELFヘッダのサイズ 0x0034 52バイト
プログラムヘッダのサイズ 0x0020 32バイト
プログラムヘッダの個数 0x0002 2個
セクションヘッダのサイズ 0x0028 40バイト
セクションヘッダの個数 0x0008 8個
セクション名を格納するセクションの番号 0x0005 ???

Step2: プログラムヘッダを読んで見る

プログラムヘッダテーブル用の構造体(全部で128×2バイト)を定義する。ELFヘッダから、プログラムヘッダは2個あることがわかったので、2つ用意。

/* program_header_read.c */
#include <stdio.h>

short swap_short(unsigned short value);
int swap_int(unsigned int value);

struct program_header{
	int type;
	int offset;
	int virtual_addr;
	int physical_addr;
	int file_size;
	int memory_size;
	int flags;
	int align;
};		  

int main(void){
	struct program_header header[2];
	FILE *fp;
	int i;

	fp = fopen("kzload.elf", "rb");
	fseek(fp, 52, SEEK_SET); //ELFヘッダ分シフト
	fread(&header, sizeof(header), 1, fp);
	for(i=0; i<2; i++){
		printf("program_header %d\n", i+1);
		printf("%08x\n", swap_int(header[i].type));
		printf("%08x\n", swap_int(header[i].offset));
		printf("%08x\n", swap_int(header[i].virtual_addr));
		printf("%08x\n", swap_int(header[i].physical_addr));
		printf("%08x\n", swap_int(header[i].file_size));
		printf("%08x\n", swap_int(header[i].memory_size));
		printf("%08x\n", swap_int(header[i].flags));
		printf("%08x\n", swap_int(header[i].align));
	}
	fclose(fp);
	return 1;
}

実行すると、標準出力は以下の通り。

program_header 1
00000001
00000074
00000000
00000000
00000236
00000236
00000007
00000001
program_header 2
00000001
000002ac
00000238
00000238
0000000c
0000000c
00000006
00000001

それぞれの意味を調べる。
1つ目のプログラムヘッダは

意味 kzload.elf kzload.elfでの意味
セグメントの種類 0x00000001 このセグメントはメモリ上にロードされる予定
セグメントの先頭位置 0x00000074 kzload.elfの先頭から116バイト目
メモリ上でのセグメントの論理アドレス 0x00000000 ???
メモリ上でのセグメントの物理アドレス 0x00000000 ???
セグメントのサイズ 0x00000236 566バイト
メモリ上でのセグメントのサイズ 0x00000236 566バイト
各種フラグ 0x00000007 ???
セグメントのアラインメント 0x00000001 ???

2つ目のプログラムヘッダは

セグメントの種類 0x00000001 このセグメントはメモリ上にロードされる予定
セグメントの先頭位置 0x000002ac kzload.elfの先頭から116バイト目
メモリ上でのセグメントの論理アドレス 0x00000238 ???
メモリ上でのセグメントの物理アドレス 0x00000238 ???
セグメントのサイズ 0x0000000c 566バイト
メモリ上でのセグメントのサイズ 0x0000000c 566バイト
各種フラグ 0x00000006 ???
セグメントのアラインメント 0x00000001 ???

c言語でバイナリファイルから構造体を読み込む

はじめに

ファイルから構造体を読み込むプログラムを作成する。

目標

以下のようにstruct_read.cに構造体を定義。

/* struct_read.c */
#include <stdio.h>

struct data{
	char	first_name[10];	
	int 	age;			
	int	height;
};

以下のtanakaさんのデータtanaka.datを読み込み、ターミナルに出力することを目標とする。

/* tanaka.dat */
74 61 72 6F 00 00 00 00 00 00
20 00 00 00 3B 00 00 00

Step1:ファイルを読み込む

以下のようにstruct_read.cを作成。

/* struct_read.c */
#include <stdio.h>

struct data{
	char	first_name[10];	
	int 	age;	
	int	height;
};

int main(void){
	struct data tanaka;
	FILE *fp;

	fp = fopen("tanaka.dat", "rb");				//r:読み込み、b:バイナリモード
	fread(&tanaka, sizeof(tanaka), 1, fp);		//1:読み込みデータ数
	printf("%s\n", tanaka.first_name);
	printf("%d\n", tanaka.age);
	printf("%d\n", tanaka.height);

	fclose(fp);
	return 1;
}

コンパイルして、実行した結果は

taro
32
59

となり、無事に読み込めた。

c言語で16進ダンプするプログラム

はじめに

c言語でファイル入出力を扱う練習として、16進ダンプするプログラムを作成する。

目標

まず、正解が必要なのでバイナリエディタ「Ghex」をインストール。以下のhello.cを作成。

/* hello.c */
#include<stdio.h>

int main(void){
	printf("Hello World!\n");
	return 0;
}

これをバイナリエディタで見てみる。

23 69 6e 63 6c 75 64 65 3c 73 74 64 69 6f 2e 68
3e 0a 0a 69 6e 74 20 6d 61 69 6e 28 76 6f 69 64
29 7b 0a 09 70 72 69 6e 74 66 28 22 48 65 6c 6c
6f 20 57 6f 72 6c 64 21 5c 6e 22 29 3b 0a 09 72
65 74 75 72 6e 20 30 3b 0a 7d 0a

上記と同じ出力が得られるプログラムの作成を目標とする。

Step1:ファイルのオープン、クローズ

以下のようにdump.cを作成。

/* dump.c */
#include <stdio.h>

int main(void){
	FILE *fp;
	fp = fopen("hello.c", "rb");	//r:読み取り、b:バイナリモード
	if(fp == NULL){
		printf("ファイルが開けません。\n");
		return 0;
	}
	fclose(fp);

	return 1;
}

コンパイルして、実行した結果何も出力されなかったので、おそらくファイルの開閉は成功している。

Step2:最初の1バイトを読んで出力

以下のようにdump.cを作成。

/* dump.c */
#include <stdio.h>

int main(void){
	FILE *fp;
	int data;

	fp = fopen("hello.c", "rb");	//r:読み取り、b:バイナリモード
	if(fp == NULL){
		printf("ファイルが開けません。\n");
		return 0;
	}

	data = getc(fp);
	printf("%02X ", data);	//0で埋める、2桁、16進

	fclose(fp);

	return 1;
}

実行した結果、23が出力された。これはバイナリエディタと一致するのでおそらく成功。

Step3:ファイルの最後まで読んで出力

以下のようにdump.cを作成。

/* dump.c */
#include <stdio.h>

int main(void){
	FILE *fp;
	int data;

	fp = fopen("hello.c", "rb");	//r:読み取り、b:バイナリモード
	if(fp == NULL){
		printf("ファイルが開けません。\n");
		return 0;
	}

	for(;;){
		if((data = getc(fp)) == EOF){
			break;
		}
		printf("%02X ", data);	//0で埋める、2桁、16進
	}
	fclose(fp);

	return 1;
}

実行すると、

23 69 6E 63 6C 75 64 65 3C 73 74 64 69 6F 2E 68 3E 0A 0A 69 6E 74 20 6D 61 69 6E 28 76 6F 69 64 29 7B 0A 09 70 72 69 6E 74 66 28 22 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 5C 6E 22 29 3B 0A 09 72 65 74 75 72 6E 20 30 3B 0A 7D 0A 

が出力された。間違ってなさそうだけど、見にくい。

Step4:16バイトごとに改行する

以下のようにdump.cを作成。

/* dump.c */
#include <stdio.h>

int main(void){
	FILE *fp;
	int data;
	int i;

	fp = fopen("hello.c", "rb");	//r:読み取り、b:バイナリモード
	if(fp == NULL){
		printf("ファイルが開けません。\n");
		return 0;
	}

	for(;;){
		for(i=0; i<16; i++){
			if((data = getc(fp)) == EOF){
				printf("\n");
				fclose(fp);
				return 1;
			}
			printf("%02X ", data);	//0で埋める、2桁、16進
		}
		printf("\n");
	}
}

実行すると、

23 69 6E 63 6C 75 64 65 3C 73 74 64 69 6F 2E 68 
3E 0A 0A 69 6E 74 20 6D 61 69 6E 28 76 6F 69 64 
29 7B 0A 09 70 72 69 6E 74 66 28 22 48 65 6C 6C 
6F 20 57 6F 72 6C 64 21 5C 6E 22 29 3B 0A 09 72 
65 74 75 72 6E 20 30 3B 0A 7D 0A 

が出力された。目標達成