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 ExchangeとRPi Software - eLinux.orgを参考にする.ブートは次に示す通り,段階的に行われるみたい.
(準備)SDカードにbootcode.bin,start.elf,config.txt,kernel.imgを置いておく(config.txtはオプショナル?)
- ラズパイ3に電源を入れる.
- GPUが起動.(このときCPUはオフ、SDRAMは無効な状態になっている.)
- GPUはSoC内のROMから第1段ブートローダを実行する.(第1段ブートローダはラズパイ製造時にSoC内のROMに書き込まれている.)
- 第1段ブートローダは
- 第2段ブートローダは
- GPUファームウェアは
- 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によると,
調歩同期方式
- そもそも,同期って何?
通信の「同期」が重要なワケ | Think IT(シンクイット)によると,
- 調歩同期方式?
同期をとるための方式の一つで,具体的なルールは
・データ単位を決めておく(ここでは8ビットとする)
・送るデータがないとき,送信側は1を送り続ける
・送るデータがあるとき,送信側は1ビット0を送ってからそのデータ(8ビット)を送る
受信側の目線で見ると,1を受けとっている間は,そのデータには意味がないので何もしなくていい.そして0を受け取った時に,次に来る1ビット目がデータの始まりで8ビット目がデータの終わりであることがわかる.
- 同期処理してるのに,なぜ非同期方式とも呼ばれるのか?
調歩同期方式は非同期方式とも呼ばれる.調歩同期方式は同期をとるために同期用のクロック信号というのを用いないことから非同期と呼ばれるらしい.ややこしい.
シリアル信号とパラレル信号
シリアル信号はシリアル通信で扱われる信号であることは想像がつくので,シリアル通信について調べる.パラレル信号も同様.
- シリアル通信?
シリアル通信 - Wikipediaによると,
- パラレル通信?
パラレル通信 - Wikipediaによると,
新たな疑問
ここまでで,
- 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: まとめ
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
が出力された。目標達成