OS自作入門 -Advent18-

NO IMAGE

Step5 ELFフォーマットの展開 後半

前回はreadelfの結果を用いてelfフォーマットの中身を読んでみた。さて、今回はこれを元にしてelfフォーマットのファイルをメモリ内に展開するコードを追加する。

追加・編集するのは以下のファイル。

  • elf.h, elf.c (追加)
    ELF形式の解析
  • main.c
  • Makefile

ELF形式の解析処理

elf.h, elf.cの追加

elf.h, elf.cを追加する。追加した内容はリンクを参照。

elf_loadという関数を追加している。シグニチャはint elf_load(char *buf)としている。これは引数bufをで与えられたメモリ領域に格納されているELF形式を解析する。

ざっとelf.cを眺めると、構造体を作ってそのポインタを利用することによりフォーマットの中身を参照してcheckしたり読み込んだりしているようだ。確かに各フォーマットのbyte数が分かっていればこのような形で定義して上から順にマッピングをするというのはコードがシンプルになっていいという気がする。

ちょっとわからなかったのは以下の部分

  for (i = 0; i < header->program_header_num; i++) {
    phdr = (struct elf_program_header *)
      ((char *)header + header->program_header_offset +
       header->program_header_size * i);

    if(phdr->type != 1)
      continue;

    putxval(phdr->offset,        6); puts(" ");
    putxval(phdr->virtual_addr,  8); puts(" ");
    putxval(phdr->physical_addr, 8); puts(" ");
    putxval(phdr->file_size,     5); puts(" ");
    putxval(phdr->memory_size,   5); puts(" ");
    putxval(phdr->flags,         2); puts(" ");
    putxval(phdr->align,         2); puts(" ");
  }

最終的にputxvalをしているところを見るとプログラムヘッダーの解析を行なっている箇所だと思われる。上のループの部分が、program_header_numを最大としてループを回している。確かにELFヘッダーには「プログラムヘッダーサイズ」というものがあるのでそこを終端にするのだろう。で次の行がphdrというelf_program_headerというstructにcastしていく。最初の(char *)headerこれがなんで必要なのかちょっとわからなかった。そのあとはprogram_header_offsetのバイト分だけ加算し、さらにループ分program_header_sizeをずらして都度読み込んでいる感じ。一旦細かいのは置いといて先に進むか。

main.cの修正

あとは、main.cにrunコマンドを追加する。これでload => runをすれば読み込まれるはず(とはいってシリアルにコンソール出力しているだけだけど)

Makefileの修正

Makefileにobjectフィアルを追加しただけ。

プログラムの実行

今回はstep4で作ったelfファイルを使ってロードすることにする。ロードはまだ行わず内部の解析だけを行なっているので問題はない。

make
make image
# スイッチを切り替えて書き込みモードに
make write
# スイッチを切り替えて読み込みモードに
../../tools/bin/kz_xmodem  ../../04/bootload/kzload.elf   /dev/tty.usbserial-FT0BTH3I
# =================================================
#  XMODEM for KOZOS H8/3069F (Version 0.0.2)
#  Copyright(C) 2012 Shinichiro Nakamura
# =================================================
# Flushing serial port.
# Wait.
# Setup load condition.
# Wait a NAK.
# ..........
# Transmit the target ELF file.
# File(../../04/bootload/kzload.elf): 41 blocks + 56 bytes
# ..........................................
# Wait a message from the target.
# Complete.

こんな感じでstep4で作ったkzload.elfをkz_xmodemを使って転送する。これでH8のROM上にelfファイルが転送されたはずだ。なのでdumpコマンドを実行して上がっているのか確認してみる。

sudo cu -l /dev/tty.usbserial-FT0BTH3I -s 9600
kzload> dump
# size: 1500
# 7f 45 4c 46 01 02 01 00  00 00 00 00 00 00 00 00
# 00 02 00 2e 00 00 00 01  00 00 01 00 00 00 00 34
# 00 00 13 00 00 81 00 00  00 34 00 20 00 04 00 28
....
# 00 00 00 11 00 00 00 03  00 00 00 00 00 00 00 00
# 00 00 12 ab 00 00 00 54  00 00 00 00 00 00 00 00
# 00 00 00 01 00 00 00 00  1a 1a 1a 1a 1a 1a 1a 1a
....

最初の方のバイトがELFヘッダのマジック: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00になっているのが確認できる。これでrunコマンドを実行する。

kzload> run
0000b4 00000000 00000000 00100 00100 06 01
0001b4 00000100 00000100 0066a 0066a 05 01
000820 0000076c 0000076c 000a5 000a5 04 01
0008c8 00fffc20 00000811 00004 00018 06 01

この値がプログラムヘッダテーブルと一致している!上手く表現できているようだ。

論理回路

ELF形式の解析が実装できましたが、まだ少し余裕があると思います。

いやねぇよ。お腹いっぱいだよ。論理回路とかやるのか。。。
ということで書いてあるのでキャッチアップ。

NOT
プログラムだと!で書かれている言語が多いかな。

AND
論理演算の&&

OR
論理演算の||

XOR
論理演算の^かな。言語による気がする。

2bitの加算

2進数の加算は

00 + 00 = 000
00 + 01 = 001
00 + 10 = 010
00 + 11 = 011
01 + 00 = 001
01 + 01 = 010
....
....
....
....
11 + 10 = 101
11 + 11 = 110

こんな感じになる。ここで上記の式をA1A2 + B1B2 = C1C2C3(第一項の上位ビットA1下位ビットA2、同様に第二項の上位下位B1B2、結果の3桁C1C2C3)には次のような関係がある。

C1 =   A1 XOR B1
C2 =  (A1 AND B1) XOR (A2 XOR B2)
C3 = ((A1 AND B1) XOR (A2 XOR B2)) OR (A2 AND B2)

なので論理演算の組み合わせで2bitの加算は行うことができるということである。これが基礎の基礎。

論理ゲート

先に図を出すと

これのこと。左からinputが入り右がoutputになる。

例えばNOTゲートの入力と出力は

inputoutput
01
10

例えばANDゲートの入力と出力は

input1input2output
000
010
100
111

加算器

なので先ほどのA1A2 + B1B2 = C1C2C3もこのゲートを使えば表現できる。

まぁややこしいがドットの部分が繋がっていると思って見るとなんとなくそうなっているのが分かるかと。これを加算器と呼び、このように複数の論理ゲートを組み合わせて作成するものを組み合わせ論理回路と呼ぶ。

計算機の基本概念は、自然現象によって数値計算を行うことです。

なるほど。意味が分からん。だが、この回路を作っておくと計算をすることなしに計算結果を得ることができる(電圧さえかけられれば)。という意図かと思っている。

比較器

同様に組み合わせ論理回路で比較するための回路を組むことができる。step2でDRAMやコントローラのチップ・セレクタ信号の接続に比較器を使っていた。これはコンパレータとも呼ばれていて、2つのinputの値が正しいかどうか(正しい場合は1を返す)。二つのinputの値を合わせると以下のようなoutputが得られる回路。

00011011
001000
010100
100010
110001

こんな感じになる。

マルチプレクサ

入力された値に応じて、複数の入力からひとつを選んで出力する回路のこと。データ・セレクタと呼ばれるらしい。制御用のinputと出力のための値が入ってくるinputの口が用意されていて、以下のような感じでのアウトプットになる。

セレクタ0セレクタ1出力
00input0の値
01input1の値
10input2の値
11input3の値

順序回路

これまでの組み合わせ論理回路は現在の入力のみから出力が一意に決まっていたが、これに対して地震の出力を入力に戻すことも可能で、現在の入力と過去の状態から出力が決まるようになる。このような回路を順序回路と呼ぶ。自分にも聞いたことがあるやつだとフリップ・フロップ回路がそれにあたる。きっと見た目がサンダルに似ているからだろう。

SR出力
00保持
010
101
11不定

となるので最大の特徴は、状態を維持することができるという点と、現在の状態を自由に変えることができるという点である。これで1bitのメモリとして取り扱うことができるようになる。 最終的に4bitのメモリの論理回路図が載っていたが興味のある方は本を読んでみていただければと。


メモリの扱い方が構造体を宣言してよろしく上からマッピングしていく(明示的にindexとかを指定せず、型のbyte長に合わせる)ってのがなんかプログラミングでも使えそうだなーと思っているところ。可変長引数とかスプレッド演算子とかに似てるんだ。自分の感覚だと。論理演算は知らないという訳ではないがそこまで腰を据えて勉強したこともないし、パッと読み解けるほど慣れてはいないけど4bitの組み合わせ回路がみれてより理解が進んだ。これ開発した人ってすごいなーと思う。発想力というか。。。逆にこれしか解がないのか、素子を一つでも少なくして開発することができたりするとすごい発明になるんだろうなーと思う。