目次: ベンチマーク
Twitterで100万回のHello, World!問題を見かけ、面白そうだったのでやってみました。消えてしまった時のためにレギュレーションを書き写しておくと、
Cだとマクロを入れ子にする、setjmp/longjmpやsignalでgotoの代わりにする、辺りが設問意図のようです。C++だとクラスAのコンストラクタでHello, World!を書いておいて、A hoge[100 * 10000]のように配列宣言する方法があるみたいです。なるほどスマート。スクリプト言語だと大抵は繰り返し処理の構文があるので、あまり難しくないみたいです。最近の言語にとっては手ごたえのない問題かもしれません。
ツイートの引用をざっと見た感じだとアセンブラでやっている人がいなかったので、Cのふりしたアセンブラ(というかほぼバイナリ直書き)でチャレンジしました。
const char _start[] __attribute__((section(".text"))) = {
0xbb, 0x40, 0x42, 0x0f, 0x00, //ebx 1000000
0x66, 0xb8, 0x01, 0x00, //ax
0x89, 0xc7, //edi
0x66, 0xba, 0x0e, 0x00, //dx
0x48, 0x8d, 0x35, 0x0c, 0x00, 0x00, 0x00, //esi
0x0f, 0x05, //syscall
0xff, 0xcb, //dec ebx
0x75, 0xe9, //jne
0x66, 0xb8, 0x3c, 0x00, //ax
0x0f, 0x05, // syscall
'H', 'e', 'l', 'l', 'o', ',', ' ',
'W', 'o', 'r', 'l', 'd', '!', '\n', '\0',
};
これだけです。ソースコードのサイズは433バイトで、コメントなどは削っていませんが余裕で1000バイト以下に収まります。何が書いてあるのかわからんと思うので逆アセンブルした結果も載せます。
0000000000401000 <_start>: 401000: bb 40 42 0f 00 mov $0xf4240,%ebx ★0xf4240 = 100万 401005: 66 b8 01 00 mov $0x1,%ax 401009: 89 c7 mov %eax,%edi 40100b: 66 ba 0e 00 mov $0xe,%dx 40100f: 48 8d 35 0c 00 00 00 lea 0xc(%rip),%rsi # 401022 <_start+0x22> 401016: 0f 05 syscall ★write(1, "Hello, World!\n", 14)に相当する 401018: ff cb dec %ebx 40101a: 75 e9 jne 401005 <_start+0x5> ★goto 2行目 40101c: 66 b8 3c 00 mov $0x3c,%ax 401020: 0f 05 syscall ★exit()に相当、libcを使っていないのでretするとSEGVする ★ここから下はHello, World!の文字列なので命令列としては無意味 401022: 48 rex.W 401023: 65 6c gs insb (%dx),%es:(%rdi) 401025: 6c insb (%dx),%es:(%rdi) 401026: 6f outsl %ds:(%rsi),(%dx) 401027: 2c 20 sub $0x20,%al 401029: 57 push %rdi 40102a: 6f outsl %ds:(%rsi),(%dx) 40102b: 72 6c jb 401099 <_start+0x99> 40102d: 64 21 0a and %ecx,%fs:(%rdx)
次に動作確認しましょう。
$ gcc -static -nostdlib a.c /tmp/ccdifyE1.s: Assembler messages: /tmp/ccdifyE1.s:4: Warning: ignoring changed section attributes for .text $ ./a.out | head Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! $ ./a.out | wc 1000000 2000000 14000000
Hello, World!が繰り返し出ていること、100万行出ていること、異常事態(SEGVなど)が発生しないことを見ています。これで良さそうですね。
アセンブラでやる人がいないのは、jmp命令がgotoと同等なので当たり前にクリアできて何も面白くないからだと思います。なのでもう少しこだわって「1000バイト以下」のレギュレーションを極めましょう。
レギュレーションを上記のように強化しました。
まずは先ほど動作確認したバイナリのサイズを確認します。
$ gcc -static -nostdlib a.c /tmp/ccdifyE1.s: Assembler messages: /tmp/ccdifyE1.s:4: Warning: ignoring changed section attributes for .text $ ls -la a.out -rwxr-xr-x 1 katsuhiro suzuki 4864 2月 25 03:23 a.out $ strip -s a.out $ ls -la a.out -rwxr-xr-x 1 katsuhiro suzuki 4544 2月 25 03:25 a.out $ strip -s -R .note.gnu.build-id -R .comment a.out strip: a.out: warning: empty loadable segment detected at vaddr=0x400000, is this intentional? $ ls -la a.out -rwxr-xr-x 1 katsuhiro suzuki 4360 2月 25 03:28 a.out
実質の命令列+文字列で50バイトくらいなのにバイナリは4KBを超えています。なんだこれは。stripしても300バイトほどしか減りません。readelfで確認すると.note.gnu.build-idと.commentというセクションがstripされずに残っていたため、排除しましたが500バイトほどしか減りません。
おっと……これは?もしかして意外と難しいのか?
バイナリのセクションヘッダを眺めていると、なぜか.textが0x1000から配置されています。
セクションヘッダ: [番] 名前 タイプ アドレス オフセット サイズ EntSize フラグ Link 情報 整列 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000401000 00001000 ★この時点でバイナリサイズ4KBが確定 0000000000000031 0000000000000000 AX 0 0 32 [ 2] .shstrtab STRTAB 0000000000000000 00001031 0000000000000011 0000000000000000 0 0 1
デフォルトリンカースクリプト(gccに-Wl,-verboseオプションを渡すと表示できます)を調べると、特に何も指定しない場合.textは0x400000+SIZEOF_HEADERSというアドレスに配置されることがわかります。
SECTIONS
{
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000));
. = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;
SIZEOF_HEADERSを決定する仕組みがいまいち分からないですが、恐らくSIZEOF_HEADERS = 0x1000でしょう。0x400000のような大きなアドレスの場合はプログラムヘッダのロードアドレスで調整するので、0x400000の0データがバイナリに書かれることはありません。しかし一定サイズ以下(今回のような0x1000など)の場合はバイナリに0データを埋め込んで開始位置を調整するようです。
この仕組みを逆手にとって.textの開始アドレス下位をもっと手前のアドレスにずらすことで、ELFヘッダやプログラムヘッダを避けながら開始位置調整用の無駄な0データを削除できるはずです。
$ gcc -static -nostdlib -Wl,-Ttext=0x400160 a.c $ ls -la a.out -rwxr-xr-x 1 katsuhiro suzuki 1120 2月 25 03:43 a.out セクションヘッダ: [番] 名前 タイプ アドレス オフセット サイズ EntSize フラグ Link 情報 整列 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000400160 00000160 ★0x1000から0x160に詰められた 0000000000000031 0000000000000000 AX 0 0 32 [ 2] .note.gnu.bu[...] NOTE 00000000004000e8 000000e8 0000000000000024 0000000000000000 A 0 0 4 [ 3] .comment PROGBITS 0000000000000000 00000191 000000000000001f 0000000000000001 MS 0 0 1 [ 4] .symtab SYMTAB 0000000000000000 000001b0 0000000000000090 0000000000000018 5 2 8 [ 5] .strtab STRTAB 0000000000000000 00000240 000000000000001d 0000000000000000 0 0 1 [ 6] .shstrtab STRTAB 0000000000000000 0000025d 000000000000003d 0000000000000000 0 0 1
開始アドレス下位を0x1000から0x160までずらすとバイナリ中の0データ領域が減り、一気に1120バイトまで減りました。劇的な効果です。変更により動作不良を起こしていないかも忘れずにチェックします(動作チェックのログは省略)。
もう一息削れば1000バイトはすぐそこですが、、、
$ gcc -static -nostdlib -Wl,-Ttext=0x400140 a.c /tmp/cc5LlyKi.s: Assembler messages: /tmp/cc5LlyKi.s:4: Warning: ignoring changed section attributes for .text /usr/bin/ld: section .text LMA [0000000000400140,0000000000400170] overlaps section .note.gnu.build-id LMA [0000000000400120,0000000000400143] collect2: error: ld returned 1 exit status
残念ながら.textの開始アドレス下位を0x140にすると「別のセクションと重なってるぞ!」と怒られてリンクエラーになります。.note.gnu.build-idセクションと重なっているとのこと。
とりあえず.note.gnu.build-idセクションは無くても実行できるので、-Wl,--build-id=noneで消し去ります。.note.gnu.build-idセクションがいなくなった分、.textセクションをさらに前の0x100まで詰められます。
$ gcc -static -nostdlib -Wl,-Ttext=0x400100 -Wl,--build-id=none a.c /tmp/cctmYikq.s: Assembler messages: /tmp/cctmYikq.s:4: Warning: ignoring changed section attributes for .text $ ls -la a.out -rwxr-xr-x 1 katsuhiro suzuki 936 2月 25 03:52 a.out
やりました。stripに頼らずとも1000バイトを下回ることができました。最初の4KBを見たときはどうなるかと思いましたが、意外と何とかなるものです。
既に目標は達成しましたが、さらに.commentセクションの排除と、stripするとどこまで減るか試しましょう。まずは.commentセクションの排除です。
$ gcc -static -nostdlib -Wl,-Ttext=0x400100 -Wl,--build-id=none -fno-ident a.c $ ls -la a.out -rwxr-xr-x 1 katsuhiro suzuki 840 2月 25 04:55 a.out
次はstripです。.symtabセクションと.strtabセクションが消えます。
$ strip -s ./a.out $ ls -la a.out -rwxr-xr-x 1 katsuhiro suzuki 520 2月 25 04:56 a.out
サイズは520バイトまで減らせました。1000バイトの約半分です。もっと複雑な処理でも詰め込めるでしょう。私はx86_64アセンブラが得意じゃないので、何か書いてやろうという気は起きませんけど……。
昨年8月ころに国立科学博物館が資金難に陥りクラウドファンディングをしていました(地球の宝を守れ - 国立科学博物館500万点のコレクションを次世代へ(国立科学博物館2023/08/07公開) - クラウドファンディング READYFOR)。科博にはこれからも頑張ってほしいなと思い、微力ながら「【体験】【寄付控除あり】乗り物体験<昔の電気自動車など>」コースに寄付しました。
んで、今日は寄付の返礼品のツアーに行ってきました。
残念ながら建物外観も含め写真を撮ってはダメで、写真がありません。車に乗っているところは写真を撮って良かったのですが、公開してはいけませんとのことで、日記に使える写真はありません。
科博の筑波地域の簡単な歴史や説明、注意事項、ツアーの時間を説明したオリエンテーリングのあと、実験植物園の裏口から理工資料棟に向かいました。こんな入り口あったんだ……。資料棟に入る前には靴底を洗い、靴にカバーを付けてから入場します(資料保護のためです)。
車は6台ありました。
Milburn電気自動車(理工電子資料館:Milburn 電気自動車 - 国立科学博物館)は、名前だけ聞くと電気自動車?EVの仲間?と思いますが、なんと100年前(1920年くらい)のアメリカの車です。
リンク先の写真を見てもらうとわかりますが、形がほぼ「馬車」です。御者台のない馬車というのかな。当時はガソリンエンジンが圧倒的シェアを取る前で、蒸気、電気、ガソリンなど様々な動力で走る車がいたそうです。面白い時代です。
マツダの各車、スバル 360は名前は知ってましたが乗ったのは初めてです。スバル 360は屋根が低すぎます。マツダ R360クーペは車内の狭さが尋常じゃないです。後部座席なんかほぼ壁です。4人乗りと名乗るには無茶が過ぎないか……??
車に乗ったり撮ったりするのが一巡したところでおまけツアーが始まりました。今日のツアー担当の方は3人いらっしゃって、車、天文、物理が専門の方々でした。たぶん2回目のツアーは担当の方が違うはずで、ツアーの内容も変わると思いますので参考程度。
最初は望遠鏡のツアーでした。
ちょうど車と同じフロアに天体望遠鏡が置いてあったため、望遠鏡の説明をしていただきました。今はデジタル撮像素子を使いますが、昔は写真乾板を使って撮影していたため数十分レベルの長時間露光が必要でした。単に数十分露光すると星が動いてしまって正しく撮影できないので、望遠鏡には望遠鏡の撮影部分を星の動きに追尾させる機構があります。
当時はモーターなどが精度良いものはなかったそうで、錘と調速機構(=遠心ガバナー)を使って一定速度で望遠鏡を回すという仕組みだそうです。望遠鏡は何台か所蔵されており上野の日本館の屋上で稼働していたニコンの60cm(だったかな?)反射式望遠鏡も置いてありました。反射式望遠鏡はどれもでかいですねえ……。
望遠鏡の次はコンピュータ系のツアーでした。
地球シミュレータが置いてありました。コンピュータはカッコいい系のデザインにされることが多いですが、地球シミュレータのデザインはかわいいですね。愛嬌があって良いと思います。京コンピュータもあったみたいですが、カバーが掛かったままで良く見えませんでした。
あと日立 HITAC5020もありました。前面パネルを開けて配線を見せてもらいましたが、辿ることすら困難なグチャグチャ配線でゾッとしました。良く配線したものだ、メンテも地獄だったのではなかろうか……。気合と根性の日本って感じがします。
目次: Arduino
M5Stamp C3をBluetooth LEデバイスにして、Linux PCもしくはRaspberry PiなどのLinux SBCとお話する取り組みの続編です。
今回はbluez-dbusを使ったJavaアプリのビルド準備をします。目的は依存するライブラリが全て含まれた(つまり単独で実行可能な)JARファイルを生成することです。ビルドシステムは昔はAntでしたが今はMavenがメジャーでしょうか?Mavenは依存関係の解消が楽で良いですね。
アプリ開発そのものはテキストエディタでもIntelliJ IDEAやEclipseなどのIDEでも、お好きなツールを使っていただければ良いかと思います。
ビルドを試すだけならJavaのソースコードは不要で、プロジェクトのディレクトリにpom.xmlを作成するだけで試せます。今回作成するアプリはbluez-dbusに依存するので依存設定を記述するdependenciesタグはこんな感じにします。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.github.hypfvieh</groupId>
<artifactId>bluez-dbus</artifactId>
<version>0.1.4</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.github.hypfvieh</groupId>
<artifactId>bluez-dbus</artifactId>
</dependency>
</dependencies>
Mavenでは記述を2つに分けるのが推奨のようです、例えば依存関係の記述ならdependenciesタグとdependencyManagementタグに分けます。特に大きなプロジェクトだと複数のpom.xmlに渡ってビルドの設定を記述することが増え、各pom.xmlのdependenciesタグの中でバージョンなどを何度も書くと間違うし更新漏れが発生します。トップレベルにあるpom.xmlのdependencyManagementタグの中でバージョンなどを定義し、子レベルにあるpom.xmlではdependenciesタグから参照する使い方が良いのだとか。
今回はpom.xmlが1ファイルしかないので分けなくても問題ありません。分けない方の例も挙げましょう。プラグイン設定も同様にpluginsタグとpluginMnagementタグの2つに分けますが、ここではあえて1つのpluginsタグに全て書きます。
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>main.java.Main</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
上記に出てくるmaven-assembly-pluginは依存ライブラリを全て含んだJARファイルを生成できるプラグインです。jar-with-dependenciesという定義済みのdescriptorRefを指定すると良いそうです。階層構造といい変な名前といい何かの呪文のようです、とても覚えられません。
テストの設定などを除けば、基本的には依存ライブラリ設定とプラグイン設定で最低限の役目は果たすはずです。ですがビルドしてみたらめっちゃハマりました。Mavenつらい……。
バージョンを特に指定しない場合、現状maven-assembly-pluginは2.2-beta-5というバージョンになります。が、このバージョンは動作がおかしいようでmvn packageがビルドエラーになります。
[INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.211 s [INFO] Finished at: 2024-02-19T19:40:23+09:00 [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-assembly-plugin:2.2-beta-5:single (default) on project bluetooth-test: Failed to create assembly: Error creating assembly archive jar-with-dependencies: A zip file cannot include itself -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
エラーの原因が全くわかりません。Mavenのエラーメッセージは全般的に何が言いたいのかわからず、pom.xmlのデバッグは困難を極めます。色々調べたり試した限りでは、プラグインのバージョンを2.1に固定するとこの現象は発生しなくなることがわかりました。何だそりゃ?どういうことだ……??
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.1</version> ★★バージョン2.1に固定した★★
<executions>
<execution>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>main.java.Main</mainClass>
</manifest>
</archive>
</configuration>
</execution>
</executions>
</plugin>
本当に意味がわからないです。Mavenつらすぎる。
次のハマりポイントはmvn packageを2回連続で実行すると下記のエラーが出て失敗することです。mvn-assembly-pluginどうなってんだお前……。
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ bluetooth-test --- [INFO] Building jar: /home/katsuhiro/ble-test/target/bluetooth-test-0.1.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 1.332 s [INFO] Finished at: 2024-02-20T02:41:20+09:00 [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-jar-plugin:2.4:jar (default-jar) on project bluetooth-test: Error assembling JAR: A zip file cannot include itself -> [Help 1] [ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
なぜかmaven-jar-pluginがエラーを起こします。メッセージの意味がさっぱりわかりません。なぜかmaven-clean-pluginを追加して最初にcleanを実行すると現象が発生しなくなります。ビルド時のゴミが残っているのか?ビルドが毎回全てやり直しになるので遅いですけど、ビルドエラーよりはマシでしょう……。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<executions>
<execution>
<id>auto-clean</id>
<phase>initialize</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
やっとmvn packageが動作するようになりました。思い出していただきたいのは、Mavenの設定ファイルpom.xmlたった1つしか作っておらず、Java言語を1文字足りとも書いていないのにこの有様だということです。Mavenは著名ツールの割にエラーメッセージが意味不明すぎてつらいです。Maven使っている人はpom.xmlをどうやってデバッグしているんでしょうね?
ソースコードという程でもないですが、こちらからどうぞ。
目次: Arduino
以前の日記(2024年2月12日の日記参照)でM5Stamp C3をBluetooth LEデバイスにして、スマホのアプリとお話ができるようになりました。とくれば、次はM5Stamp C3とLinux PCもしくはRaspberry PiなどのLinux SBCとお話したくなります。
LinuxでBluetoothを扱う場合は、BlueZという実装を使います。BlueZはDBusというマシン内のイベントを配信するシステムに依存しており、軽く調べた感じではC言語からDBusを制御するのは結構大変そうです。Pythonだと簡単らしいですがGUIも実装したいんですよね。ちょっと悩みましたが久しぶりにJavaで書くことにしました。
素晴らしいことにJavaからBlueZを制御する場合、bluez-dbusという(bluez-dbusのリポジトリ)求めていた用途そのもののライブラリがあります。DBus制御は同作者さんのdbus-java(dbus-javaのリポジトリ)を使うようです。Unixソケットを使うライブラリにも依存しているようですね。
ちなみにbluez-dbusはJavaで実装されているものの、Javaの外の世界にOSやシステム依存(DBus、Unixソケットの存在に依存)があるため、Linuxでしか動作しないことに注意が必要です。用途次第ではWindowsやMacOSで動作しないと困るのでしょうけど、私が今回想定している用途ではLinux専用で問題ありません。ありがたくbluez-dbusを使わせていただきます。
ドキュメント(bluez-dbusのJavaDoc)を自分でビルドするのは大変なので、javadoc.ioを参照(Overview (bluez-dbus 0.1.4 API))すると楽です。
ちなみにjavadoc.ioって何?と思ったらMavenに収容されているリポジトリのJavaDocを生成して公開してくれているサイトなのだそうです。ありがとうMavenプロジェクト、とても便利です。
< | 2024 | > | ||||
<< | < | 02 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | - | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)