※ 表示や動作が正しくないと思う場合は、JavaScript を有効にしてください。
このプログラムは、Windows 実行ファイル(exe)およびダイナミック・リンク・ライブラリファイル(dll)が、 32 ビットでビルドされたものなのか、64 ビットでビルドされたものなのかを判断するためのツールです。 32/64 ビット EXE/DLL チェッカーにある、 Windows デスクトップ・アプリの JavaScript 版です。
ここに判定結果が表示されます。
判断の根拠となったデータ部分が、下に表示されます。
現時点でのテスト済みブラウザは、
です。
動作の確認は、Windows デスクトップアプリ 「32/64 ビット EXE/DLL チェッカー」で行っています。 検証は行っておりますが、ご利用に関しては、 自己責任でお願いします。 判定結果に誤りがあったとしても、当方では責任を負いかねます。
スマホやタブレットで開く場合は、
https://www.straightapps.com/web/js-x86x64checker.html
)
投稿 October 8, 2020
ここから先は、上記、JavaScript 版 x86x64checker プログラム開発を行った際に調べたりした JavaScript コードについての情報です。
作者背景については、「素因数分解トレーニング」のページの 「ここから先は開発情報です」をご覧ください。
まずは、HTML 部です。
<label for="infile">32 ビットか、64 ビットかを判定したい exe または dll ファイルを選択してください。</label> <input type="file" id="infile" accept=".exe,.dll" style="width: 90%;"/> <!-- 判定結果表示欄 --> <p id="result" class="sz120p blue strong">ここに判定結果が表示されます。</p> <!-- 判断根拠となった部分を表示 --> <p>判断の根拠となったデータ部分が、下に表示されます。</p> <pre class="data2" id="evidence"></pre> <script type="text/javascript" src="js/x86x64checker.js"></script>
label は、続く input の説明文です。 for に書いている値と、input の id に書いている ID を一致させるといいようです。
input type="file" が、ファイル選択のためのコンポーネントです。
Windows 10 + Chrome の場合、「ファイルを選択」と書かれたボタンと、
その右に選択されたファイル名表示領域、初期状態は「選択されていません」がセットで表示されます。
今回、対象ファイルは実行ファイル .exe と DLL ファイル .dll だけですので、 accept=".exe,.dll" を指定して、拡張子を制限しています。 ただし、ファイル選択ダイアログで「すべてのファイル」を選択すると、 指定以外の拡張子でも選択できるので、頼りすぎてはいけません。
p id="result" には、判定結果が表示されます。 class 指定がありますが、これは自分で定義した css への参照ですので、決まりはありません。
pre id="evidence" には、 どの部分で 32 ビット、あるいは 64 ビットと判定したのかを表示します。 こちらも class 指定がありますが、表示をフォーマットするための css への参照です。
ここまでで表示を更新する部分がすべて定義されたので、JavaScript を読み込んでいます。 ここで読み込んだ JavaScript は、先頭から解釈され、順次実行されます。
以降のセクションで、それぞれ詳細を確認していきます。
なお、ご参考までに、JavaScript ソースコードそのものを、拡張子 js を拡張子 txt に変えて、次のリンクより開けるようにしてあります。
x86x64checker.txt
※ 作成時はタブを4文字としていますので、環境によってはタブが8文字のため、
コメント等がずれて見えるかも知れません。
※ 念のため書き添えますが、このソースコードをこのまま転載・公開することはご遠慮ください。
なお、本サイトのご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
投稿 October 10, 2020
改めて、input type="file" が、ファイル選択のためのコンポーネントです。
<input type="file" id="infile" accept=".exe,.dll" style="width: 90%;"/>
Windows 10 + Chrome の場合、「ファイルを選択」と書かれたボタンと、
その右に選択されたファイル名表示領域、初期状態は「選択されていません」がセットで表示されます。
今回、対象ファイルは実行ファイル .exe と DLL ファイル .dll だけですので、 accept=".exe,.dll" を指定して、拡張子を制限しています。 ただし、ファイル選択ダイアログで「すべてのファイル」を選択すると、 指定以外の拡張子でも選択できるので、頼りすぎてはいけません。
accept に、拡張子ではなく種類を指定すると便利になるようです。 とりあえず、image/png を指定すると PNG 画像、 image/jpeg" を指定すると JPEG 画像を指定できるようです。 また、.doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document のように、「ワードで開けるファイル」のような指定もできるようです。 カンマ区切りの指定なので、4要素が指定されているわけです。
JavaScript ソースを先に見ると、HTML の読み込みが完了したら、 onload 関数が実行されるようになっています。
window.addEventListener('load', onload, false);
onload 関数の先頭で、この input からの change イベントを得て、 以降の処理を実行しています。
function onload() { const f = document.getElementById('infile'); // input type="file" が変更されたときに実行する関数をアロー関数で定義 f.addEventListener('change', (evt) => { // 引数が evt のアロー関数 <!-- 以降の部分は今は省略 -->
f に、input id="infile" 要素を取得しています。 今までの JavaScript プログラムでは var を使っていましたが、 書き換え不能な値ですので、ここでは const にしています。 まだしばらくは、正しく切り分けできないと思いますが、なるべく正しく使いたいです。
input の change イベントを待ち、 処理を、引数が evt 1つのアロー関数で実行するようにしています。
アロー関数とは、 ここで今までは関数呼び出しをしていたのですが、 他からは呼び出されない、ここでしか使われない関数になってしまうので、 ここに直接関数を書いてしまう、という書き方と思われます。 => のあと、 中カッコで囲んで処理本体を書くようです。
アロー関数は、コード量を少なくする目的で使うようですので、 一般的には、引数が1つしかない場合は、(evt) のようにカッコでくくらず、 直接 evt と書くらしいです。 私はまだ慣れていないので、明示的にカッコを付けています。 なお、引数がない場合は、() と書き、省略できないようです。
投稿 October 11, 2020
ここでは、上記 change イベントを処理する、
f.addEventListener('change', (evt) => { // 引数が evt のアロー関数 const input = evt.target; // input type="file" 要素 if (input.files.length == 0) { // 長さが 0、すなわちキャンセルの場合 let p = document.getElementById('result'); p.innerHTML = 'キャンセルされたか、ファイルサイズ 0 です。'; let e = document.getElementById('evidence'); e.innerHTML = ''; return; }
まず、この change イベントが発生する条件ですが、 選択中のファイルが変更されたときです。 上の部分で、選択されたファイルの長さが 0 のとき「キャンセルされた」としていますが、 このページを読み込んだ時はファイルが選択されていませんので、 初回のファイル選択でダイアログをキャンセルしても、change イベントは発生しません。
change イベントが発生すると、引数1つを得る関数が呼び出されます。 ここで選択されたファイルを処理するわけですが、 ここに書くべき関数は、ここでしか使われないので、この addEventListener 関数内に書いてしまう、 ということになっています。 正直深く理解しきれていないので、今はこの程度と考えています。
引数について、引数が1つの場合のアロー関数は、仮引数名にカッコをつける必要はないとされています。
f.addEventListener('change', evt => {
このように書かれていることが多いようですが、慣れないうちはわかりにくいと感じるので、 あえてカッコを付けています。 なお、引数なしの関数を指定する場合は、カッコを省略できず、() のように書き、 2つ以上の場合も、カッコは省略できないようです。
アロー関数の最初の部分としては、変数 input に、イベントが発生した要素、 すなわち input 要素を取得しています。
選択されたファイル input.files の長さ length が 0 なら、 キャンセルされたか、ファイルサイズが 0 ですので、 判定結果を表示する領域 result にその旨を、根拠としたバイト列をクリアして終わります。 なお、innerHTML によるテキスト要素の書き換えについて詳しくは、 「【基礎】HTML 要素のテキストの書き換え」 に書いています。
let file = input.files[0]; // 選択されたファイル情報 //alert(file); // "[object file]" と表示される if (!file) { let p = document.getElementById('result'); p.innerHTML = 'ファイル情報が無効です。'; let e = document.getElementById('evidence'); e.innerHTML = ''; return; }
変数 file に選択された最初のファイル、 この場合は1つしか選べませんので、選ばれたファイルを設定しています。 すぐ次の alert 関数で表示させてみると、 [object file] と表示されましたので、 これはファイル名ではなく、ファイルオブジェクトであることがわかります。
そういうケースがあるかはわかりませんが、 もしファイルオブジェクトが無効であれば、「ファイル情報が無効です」を表示します。 if 文の条件式にある not の意味ですので、 file が無効であるなら、と読めます。
// ファイルサイズを制限する場合
//if (file.size > 50 * 1024) {
//}
コメントアウトしてありますが、 何かの理由でファイルサイズを制限したい場合、 このように、size プロパティにファイルサイズが入っていますので、 このあとの処理から除外したりすることも可能です。
// 非同期でファイルを読み込みます。 readAsArrayBufferReader(file) // Promise が結果を返したら、then が実行される(引数1つのアロー関数) .then( (buff) =>
選択されたファイルオブジェクトを readAsArrayBufferReader に渡して、 ファイル読み込みを「非同期で」行います。
「非同期で」ということは、ここで処理は止まらず、ブラウザはユーザーの操作を受け付ける、 という意味だと思われますが、正確にはわかりません。
で、readAsArrayBufferReader では Promise で読み込み処理が行われているため、 読み込み処理が終わったら .then 以降が実施されることになります。 ここでも、引数 buff を取るアロー関数として定義されています。
.then 以下の処理を見る前に、readAsArrayBufferReader を見ておきます。
投稿 October 12, 2020
ここでは、ファイル読み込みを担当している、 Promise による FileReader オブジェクトの扱いを見ていきます。 といっても、どちらも初めて扱うものなので、日本語的にこれでいいのかもわかりませんが。
const readAsArrayBufferReader = (file) => // 引数が1つのアロー関数(引数が1つならカッコを省略可能) { const reader = new FileReader(); // 新しい FileReader オブジェクトを作成
これは JavaScript 特有の書き方だと思いますが、 readAsArrayBufferReader は、引数 file を取るアロー関数の戻り値を得る変数、 と言えるのでしょう。 なぜ単なる関数ではないのか、 なぜ呼び出し側で readAsArrayBufferReader( file ) のように書くのか、 今はまだ理解していませんが、きっと Promise 〜 .then したいからなのでしょう。
一応、この段階での解釈としては、 「Promise は、非同期処理の最終的な完了もしくは失敗を表すオブジェクト」である、 ということです。
return new Promise( (resolve, reject) => {
ですので、readAsArrayBufferReader のアロー関数は、新しい Promise オブジェクトを返すもので、 その処理の本体は、引数が2つのアロー関数の実行結果である、という意味です。 なお、仮引数名は MDN の通りとしていますが、 ルールがあるのかどうかも、今はわかりません。
とりあえず、処理を返すために、Promise 内のアロー関数が実行されるようです。 順番的には、このあとにある2つのイベントハンドラは関数なのでいったんスルーされ、 そのあとの reader.readAsArrayBuffer(file) が実行されるはずです。
ここでは、定義順に見ていきます。 まずは、読み込みエラーとなった場合の error イベントのハンドラを定義しています。
reader.onerror = () => // 引数がないアロー関数(カッコは省略できない) { reader.abort(); // 読み込み処理を中断する reject('Unknown error occurred during reading the file'); // reject にエラー理由を設定 };
ファイルの読み込みが何らかの理由で失敗すると、error イベントが発生し、 reader.onerror イベントハンドラが呼び出されるようです。 このように、引数なしのアロー関数で実行されていますが、なぜ直接 onerror 関数ではないのかは、わかりません。
内容的には、abort 関数を呼び出して読み込み処理をここで中断し、 引数の reject に文字列を設定している、という形です。 このような書き方になっている理由もわかりませんが、もしかして reject は関数で、 拒否理由を引数で渡している・・・と考えると、わかりやすいのかも知れません。
次は、読み込みが正常に完了した load イベントのハンドラを定義を定義しています。
reader.onload = () => { resolve( reader.result ); // resolve に reader.result を代入 };
引数の resolve に、reader が作成した result を設定している、ということです。 resolve や reject を呼び出すと、Promise が終了する、という具合でしょうか。
ここまで、Promise のアロー関数にはハンドラしか定義されていませんので、 いったん読み飛ばされて、最初に実行されるのが下記の部分ということになります。
// 指定された Blob(または file オブジェクト)の内容の読み込みを開始し、 // 終了すると、result 属性にファイルのデータを表す ArrayBuffer が格納されます。 reader.readAsArrayBuffer(file); // Blob.arrayBuffer メソッドが新しいものである。 }); // Promise の終わり }; // readAsArrayBufferReader の終わり
調べたところでは、上記のようにコメントを入れた通りですので、 この関数呼び出しでファイルの読み込みを開始している、です。 ちなみに readAsArrayBuffer 関数の利用は、古い形みたいですので、 理解が進んできたら、arrayBuffer を使うべきなのかも知れません。
投稿 October 13, 2020
input type="file" の change イベント処理の続きに戻り、 読み込まれたファイルの処理を見ていきます。
readAsArrayBufferReader(file) // Promise が結果を返したら、then が実行される(引数1つのアロー関数) .then( (buff) => { let bin = ''; // 「根拠」として出力するバイナリデータ文字列 //let dat = new Uint8Array( buff ); // ファイルから読み込んだ buff 全体を 8 ビット符号なし整数配列に変換 let dat = new Uint8Array( buff, 0, Math.min( file.size, 1024 ) ); // 同 buff の先頭 1024 バイトを、8 ビット符号なし整数配列に変換 // x86(32bit) : 50 45 00 00 4C 01 の並びを検出 // x64(64bit) : 50 45 00 00 64 86 の並びを検出 // exe でも dll でも、いずれも 0x0200 より前にあるようなので、1024 = 0x400 で十分
readAsArrayBufferReader 関数が正常終了すると、 .then に書かれた関数が実行されるようです。 FileReader の onload イベントで、resolve( reader.result ) としていますので、 ここに書いたアロー関数の引数 buff には、reader.result が渡されていると思います。 これは、ファイルを読み込んだバイト列を表すオブジェクトのようです。
最初にクリアしている変数 bin は、判断の根拠とした部分を文字列で表示するためのものです。 pre id="evidence" の部分に表示したい文字列です。
その下のコメントに書いてあるように、 exe または dll が 32 ビットであるか、64 ビットであるかを、バイトデータの並びの検出により判断しますので、 Uint8Array オブジェクトに変換して、扱いやすくします。 buff をそのまま渡すと、読み込んだファイル全体が 8 ビット符号なし配列に変換されますが、 ここでは全部は必要ないので引数を指定し、先頭から最大 1024 バイトまでを変換します。 わざわざ file.size と比較しているのは、 1024 バイト未満のファイルの場合、ここでエラーとなってしまうためです。 小さいほうの値を、Math.min 関数で選択しています。
なお、判定方法の詳細は、「32/64 ビット EXE/DLL チェッカー」を参照してください。
let x86 = 0; let x64 = 0;
32 ビットから 64 ビットかは、2つ上のコード部分の最後のコメントにある通り、 判定するバイトデータが連続6バイト一致したときに確定しますので、 この2つの変数で、連続して一致しているバイト数を表すようにしています。 0 のときは連続判定開始前、50 が見つかったら 1 に、45 が見つかったら 2 にします。 連続一致が途切れたら 0 に戻し、最後まで一致したら 6 になります。
// 16 バイト単位で検査します。 for (let i = 0; i * 16 < dat.length; i++) { let line = ''; // 1行のデータ let p = i * 16; // dat に対する 16 バイトブロックの先頭 let b = dat.slice( p, Math.min(dat.length, p + 16) ); // p バイト目から最大 16 バイト先までを取り込み
判定理由を 16 バイト単位で表示したいので、ループによる検査も 16 バイト単位で行います。 16 バイト単位でまわす、それ以外の理由はありません。 for ループで変数 i を宣言し、その 16 倍がデータバイトの最後に到達しないうちは、ループを続けます。 Uint8Array の戻り値である dat は、length プロパティに、サイズが入っています。
変数 line は、根拠となるデータを表示するための1行分のデータを一時的に入れるためのものです。 変数 p は、16 バイト単位に区切った dat データの先頭ポイントです。 変数 b は、dat のうち、調査する部分を 16 バイト切り出したものです。 slice 関数で、第1引数である p から、第2引数までの部分を取り出します。 ただ、最後の部分で端数が出て 16 バイトに満たない可能性がありますので、残りが 16 バイト未満になったら、そこまでとします。 1024 バイト以上のファイルなら 1024 バイトなので問題は起きませんが、 1024 バイト未満のファイルを選択した場合、最後も 16 バイトにすると問題が起きる可能性があります。
for (const e of b) { // 「変数 of 配列」により配列1要素ずつ取り出し e に設定する let h = e .toString(16) // 16 進数の文字列に変換 .toUpperCase() // 英大文字に変換 .padStart(2, '0'); // 2桁になるよう先頭を 0 で埋める line += ' ' + h; // 1行のデータに追記 // データの並びを判断します if ( x86 != 6 ){ x86 = checkIfX86( h, x86 ); } if ( x64 != 6 ){ x64 = checkIfX64( h, x64 ); } }
まず気になるのは for 文の書き方です。
C/C++ にはないこの書き方は、
取り出された 8 ビット符号なしの e について、 toString 関数を呼び出しています。 引数に 16 を指定しているので、値を 16 進数の文字列に変換します。 16 進数は 0 〜 9 と、a 〜 f で構成され、 8 ビット符号なしの値なら 10 進数で 0 〜 255、16 進数で 00 〜 ff までとなります。
e.toString 呼び出しで 16 進数で表した文字列が返りますので、 いったん変数に代入してもいいのですが、ここではこのまま、 .toUpperCase 関数を呼び出して、英文字が大文字であることを保証します。 このような書き方は C/C++ でも使える通り、左から順に評価されていくので OK、ということです。
さらにもう1段、1桁だった場合は先頭を 0 で埋める指示、 padStart 関数を呼び出して、2桁にしています。 C/C++ では全部あわせて、sprintf( h, "%02X", e ) 相当ですね。 C/C++ は便利ですね。
出力行 line に、スペースとこの2桁の 16 進数値を追加します。
そして、checkIfX86 関数を呼び出して、X86 かどうか、 すなわち 32 ビットを表すバイト並びがあったかどうかを判断します。 この関数については、次のセクション「連続するバイトデータの判定>」に書いています。
同じように、checkIfX64 関数を呼び出して、X64 かどうか、 すなわち 64 ビットを表すバイト並びがあったかどうかを判断します。 この関数についても、次のセクション「連続するバイトデータの判定>」に書いています。
どちらの場合も、x86 または x64 が 6、つまり 6 バイト連続で目的のバイト並びになったら、 もうそれ以上呼び出しません。つまり更新されないので、ずっと 6 のままになります。
let addr = p .toString(16) .toUpperCase() .padStart(8, '0'); line = `${addr}:${line}\n`; // 「テンプレートリテラル」で行データを作成 bin += line; if ( x86 < 1 && x64 < 1 ){ bin = ''; }
先ほどの e と同じように、dat に対する検査部分の最初の位置 p についても、 16 進数 8 桁で、先頭を 0 で埋めたものにし、addr に設定します。
次の line の構成方法は、「テンプレート リテラル」と呼ばれる書き方だそうです。 グレイヴ・アクセントと呼ばれる記号で文字列を囲み、 コマンドラインなどで見られるように ${ 変数名 } のように記述すると、 その変数の値が取り込まれるようです。 C/C++ の sprintf( line, "%s:%s\n", addr, line ) のようなイメージです。
ちなみにこのグレイヴ・アクセント、始めて使いましたが、 手元のキーボードでは Shift + @ で入力できました。 シングルクォーテーションとは違いますので、ご注意ください。
最後に、変数 x86 も x 64 も 0 の場合、 判定に関係ありませんので、累積してきた文字列 bin をクリアしています。
// 結果が出た場合は、ここで終わります。 if ( x86 == 6 || x64 == 6 ){ break; } }
変数 x86 または x64 が 6 になった場合は、判定結果がでましたので、ここで終わります。 break は、C/C++ と同様、for ループからの脱出です。
ここまでで、32 ビットか、64 ビットか、あるいはどちらでもないかが判定されました。
let res = document.getElementById('result'); if ( x86 == 6 ){ res.innerHTML = '32 ビットです。'; } else if ( x64 == 6 ){ res.innerHTML = '64 ビットです。'; } else{ res.innerHTML = '<span class="red">判定できませんでした。</span>'; } let pre = document.getElementById('evidence'); pre.innerHTML = bin; }) // .then のアロー関数の終わり
p id="result" に、結果文字列を表示します。
また、pre id="evidence" に、根拠となったバイト列部分を出力します。
// Promise が拒絶(reject)した場合の処理 .catch( (reason) => { alert(reason); }); // .catch のアロー関数の終わり }); // addEventListener 関数のアロー関数の終わり
ファイルの読み込みに失敗した場合、.then ではなく .catch が呼び出されます。 基本的には想定外ですので、単純に alert 関数でメッセージを表示します。
投稿 October 13, 2020
改めて、32 ビットか 64 ビットかの判断は、次の2種類のバイト並びのどちらが見つかるか、によります。
x86(32bit) : 50 45 00 00 4C 01 の並びを検出 x64(64bit) : 50 45 00 00 64 86 の並びを検出
それぞれの判定に、変数 x86 と x64 を用意し、初期値として 0 が設定されています。
x86、x64 とも、データバイト列の内容が異なるだけで、処理はほぼ同じですので、 x86 の判定コードのみ、見ていきます。
function checkIfX86( h, x86 ) { // 0x50 なら、文字列の開始の可能性があります。 if ( h == "50" ){ return 1; }
まずこの関数は、第1引数に今見ているバイトデータ、16 進数2桁になった文字列を、第2引数に x86 の現在の値を受け取ります。 戻り値として、x86 を更新した値を返します。
h が 50 だった場合、対象バイト列の1つ目以外にはチャンスはありません。 ですので、今の x86 の値に関係なく、戻り値で 1 を返し、x86 を 1 とします。
// 0x45 なら、0x50 に続く場合のみ、判定対象です。 if ( h == "45" ){ if ( x86 == 1 ){ return 2; } return 0; }
同じように、h が 45 の場合です。 50 に続く 45 のみが有効なので、x86 が 1 のときのみ、値 2 を返します。 50 に続かない 45 の場合は、連続データが途切れていますので、0 を返します。
// 0x00 なら、0x45 または 0x00 に続く場合のみ、判定対象です。 if ( h == "00" ){ if ( x86 == 2 ){ // 0x45 の次 return 3; } if ( x86 == 3 ){ // 0x00 の次 return 4; } return 0; }
3バイト目と4バイト目は、いずれも 00 です。 x86 が 2 のとき 00 が来たなら 50 45 00 ですので、3 つ OK となりますので、値 3 を返しています。 x86 が 3 のとき 00 が着たなら、それに続く2つ目の 00 ですので、値 4 を返します。
どちらでもない 00 なら、連続データが途切れていますので、0 を返します。
// 0x4C なら、2つ目の 0x00 に続く場合のみ、判定対象です。 if ( h == "4C" ){ if ( x86 == 4 ){ return 5; } return 0; } // 0x01 なら、0x4C に続く場合のみ、判定対象です。 if ( h == "01" ){ if ( x86 == 5 ){ return 6; } return 0; } // 上記以外の値は、判定対象ではありません。 return 0; }
同じように、そのあと 4C が続くかどうか、そしてさらに 01 が続くかどうかを判断します。 01 まで OK となった場合は 6 を返しますので、これで確定となります。
上記 5 種類以外のバイトデータの場合、連続データが途切れていますので、0 を返します。
ウェブ開発に関するトピックは、「ウェブ開発トップ」にまとめられています。