IoT企業社内便利ツール③ IoT製品なら必須の地味だけど大切なバイナリ置換の話 / Akerun Advent Calendar 20日目

みなさん、Akerunしてますか?(挨拶)

この記事はAkerun Advent Calendar 2016 - Qiitaの20日目の記事です。

今日は社内バイナリ職人のkazuphが担当します。

IoT企業ならバイナリ置換するよね??

Linuxじゃないマイコンレベルの組み込み前提

あ、いきなりですがしない方法もあります。マイコンへはすべての個体で一律にファームウェアバイナリを書き込んでおいて、別個でユニークなIDをメモリに書き込むパターン。 僕らはやったことないですが、そうやっている企業も多い?でもメモリへ直接のインターフェースをつくるか、工場出荷時のみマイコン経由でメモリへつながるインターフェースをつくるか?そういう悩みが発生しそう。

あとはソースコードが参照している認証情報をまとめているファイル自体を置換して、その後コンパイルするパターン。これならバイナリ自体は置換する必要ない。

で、最後にコンパイル後に生成されるバイナリの中身を置換しにいくパターン。

単にガジェットならID管理する必要すらないかもだけど、ネットにつながるなら、BLE使うなら認証系の鍵・証明書はもちろんユニークに埋め込むわけで。何かしらの方法でやっているはずかなーって。

で、この前は発表自体はしなかったのですが、アップロード時の資料にはこんなスライドを追加していました。

f:id:photosynth-inc:20161221000409p:plain

f:id:photosynth-inc:20161221000412p:plain

f:id:photosynth-inc:20161221000414p:plain

f:id:photosynth-inc:20161221000417p:plain

f:id:photosynth-inc:20161221000419p:plain

カジュアルにUnix上でmakeできるような良心設計なマイコンSDKが提供されているならまだいいですが、Windows環境でしか条件を満たすバイナリを生成できないケースもあったりするので、どうせ置換するならバイナリ置換に倒してもいいかなと。

一応ソース置換の話

#!bin/bash
perl -i -p -e "s/(__ID) (.*)?$/\$1 ${ID}/" $CONFIG_SOURCE_PATH

#define で定義してある箇所をPerlワンライナーで置換する。それをシェルスクリプトで束ねて、引数でIDや認証情報を食わせればそれで完了。 まあ実装は楽でしたね。一瞬。

バイナリ置換の話

で、メイン。

うわー、俺Web系だからバイナリとか置換できないょぉ、みたいな感じだったのですが、やったら案外できたみたいな。

まず親しみを込めてVimからバイナリを眺める

普通に開くとこんなん。

$ vim hoge.bin

f:id:photosynth-inc:20161221002439p:plain

蕁麻疹出るやつ。

でもこの状態で、

:%!xxd

とするとこうなる。

f:id:photosynth-inc:20161221002550p:plain

ちなみに -g オプションを付けると、区切る間隔を変更できる。

:%!xxd -g16

f:id:photosynth-inc:20161221002801p:plain

(;´Д`)ハァハァ このままVimで置換しょぉ

$ vim -b <IMAGE_NAME>.bin +'%!xxd' +'%s/9999 9999/7777 7777/g' +'%!xxd -r' +wq

一応1行に必ず出てくる一部の文字列ならこれで対応できます。

VimからPerl

そうそうに"それVim"を切り上げてPerlで置換しにいきます。ですが、今回はバイナリです。どうやるのでしょうか?

こうやります。

# 文字(数字)をPerlが認識するバイナリ形式に変更
num_to_binary () {
  echo $1 | perl -ane '@ary = $_ =~ /.{2}/g; @x = map{"\\x".$_} @ary;print @x;'
}

perl -pi -e "s/`num_to_binary $BEFORE_ID`/`num_to_binary $AFTER_ID`/" ${FW_PATH}.bin

$BEFORE_ID などに入っているのは 8F3A145B...みたいな1バイトを16進数表示で並べた文字列です。2文字ずつ区切って処理しています。 これなら、一度binを人間が読めるテキスト形式にせずともバイナリの世界のまま置換してくれます。

元データが文字列の場合は以下の様になります。

# 文字(ASCII)をPerlが認識するバイナリ形式に変更
string_to_binary () {
  echo $1 | perl -ane 'map{$str = unpack("H*", $_);@ary = $str =~ /.{2}/g; @x=map{"\\x".$_} @ary;print @x;}@F'
}

perl -pi -e "s/`string_to_binary $BEFORE_NAME`/`string_to_binary $AFTER_NAME`/" ${FW_PATH}.bin

BEFORE_NAME へは akerun_name みたいな文字列が入ります。

単純なバイナリなら、これで完璧です。

IntelHex形式をbin形式へ

ここでmake時にデフォルトでIntelHex形式を吐き出すツールチェインもあります。

Intel HEXはバイナリ情報をASCIIテキスト形式で運ぶためのファイル形式である。マイクロコントローラやEPROMなどのプログラム可能なデバイスのプログラム書き込みのために広く用いられている。典型的な利用用途としてはコンパイラアセンブラがプログラムのC言語アセンブリ言語などのソースコード機械語に変換し、HEXファイルとして出力する。

Intel HEX - Wikipedia

詳しくはウィキってもらうとして、マイコンに書き込むバイナリは、単なる01を羅列したバイナリじゃなくて、このIntelHex形式じゃないと正しく動作しない場合もあります(IntelHexからbinへの変換は書き込む位置の情報が失われる)。

見た目はこんな感じのやつです。

f:id:photosynth-inc:20161221010633p:plain

今までの方法を使いたいなら、単純にIntelHex形式をbinに変換しましょう。binにした結果必要な情報が失われて結果正しく動作しないファームウェアになってしまう可能性もありますが、その場合は後で示すIntelHexを直接置換する方法を実践しましょう。

ここでPythonさんの登場です。

github.com

pip install intelhex

これでhexからbinに変換するコマンドも一緒にインストールされます。

$ hex2bin.py $FW_PATH.{hex,bin}

これでOKですね!

IntelHex形式に対して直接置換行為をはたらきたい

これはちょっと苦しめられました。特にPerlワンライナーで全部やろうと腹を決めていた自分に取っては屈辱的でしたが、ここでRubyさんを召集することにしました。

#!/usr/bin/env ruby

# チェックサムを計算する
def calculate_checksum (line)
  ary = line.sub(":", "").each_char.each_slice(2).map(&:join)
  sum = ary.map{|x| x.to_i(16)}.inject(0){|a, i| a + i }
  "%02X" % (((sum ^ 0xffff) + 1) & 0x00ff)
end

# バイナリデータの部分だけ抽出する
# 本当はもっと色々やってる
File.open("org_intelhex_file.hex") do |file|
      leading_codes << line.slice(0, 9)
      data += line.slice(9, 32)
end

# ソース中で使ってるデフォルト設定を引数で受け取ったデータで置換する
replaced_data = data.sub("1A3F56...", ARGV[1].upcase)

# データ部分を元のIntelHexの形式であるn文字区切りにする
data_ary = replaced_data.each_char.each_slice(n).map(&:join)

# データの先頭についていたコードを付け直す
leading_codes.each_with_index do |code, i|
  line = code + data_ary[i]
  # 末尾のチェックサムは計算し直し
  checksum = calculate_checksum line
  hex += (line + checksum + "\r\n")
end

# 最後に書き出して完了
File.open("new_intelhex_file.hex", "w") do |f|
  f.puts bin
end

端折ったのでこのソースのままでは動作しませんが、やりことはなんとなくわかるかなと思います。 IntelHexのデータ部分だけ抽出したあとに、置換処理を実行し、元のIntelHex形式に戻しているだけです。

固定長のデータの置換であることが前提ですが、これでIntelHex形式のファイルすら置換できました。

まとめ

日夜自分が書いた置換ツールが工場で動き続けていると思うと (;´Д`)ハァハァ しちゃうなぁという感じです。 日本でこんなにバイナリの置換行為をしている(させている)のは自分だけなんじゃないかと思ったりもしますが、上には上がいると思うのでその辺はコメントをお待ちしております。


Akerunをつくっている株式会社フォトシンスでは、APIやWeb管理画面を良くしてくれる、Railsエンジニアを募集しています! www.wantedly.com

Akerun Developersサイトもやってます。Akerun APIについては、こちらをご覧ください。 photosynth-inc.github.io

Akerun Proのご購入はこちらからどうぞ! akerun.com