目次: ベンチマーク
前回はループ、再帰なし、1000バイト以下で100万回のHello, World!を実施する問題に対し、バイナリサイズを104バイトまで削るためのアイデアと実装方法をご紹介しました。
100万回のHello, World!プログラムの方は所定の範囲に収まって動作しているので、特に変えなくて良いです。気になるとすれば、プログラムの終了ステータスがエラー(今は60)になっている程度です。原因はexitシステムコールに渡す引数が0ではないからで、syscall命令を呼ぶ前にrdiを0にすれば直ります。まあ、できたら良いな程度で動作には関係ありません。
Linuxのシステムコール呼び出し規約は下記のようになっています。
| syscall num | return | arg1 | arg2 | arg3 | arg4 | arg5 | arg6 |
|---|---|---|---|---|---|---|---|
| rax | rax | rdi | rsi | rdx | r10 | r8 | r9 |
バイナリファイルのサイズをこれ以上短くしようとするなら、ELFヘッダとプログラムヘッダをさらに重ねる必要があります。ELFヘッダとプログラムヘッダを完全に重ねると64バイト(2020年7月5日の日記参照)になりますが、プログラムはSegmentation Faultになってしまって動作しませんから、動作可能な重ね方を探す必要があります。
この記事にコメントする
目次: ベンチマーク
前回はループ、再帰なし、1000バイト以下で100万回のHello, World!を実施する問題に対し、バイナリサイズを112バイトまで削るためのアイデアと実装方法をご紹介しました。今まで112バイトが限界だと思っていましたが、とあるサイト(Tiny ELF Files: Revisited in 2021)からいくつかヒントを得て104バイトにできました。
サイズ104バイトの実行バイナリはこんな感じです。
$ hexdump -C 104byte.out 00000000 7f 45 4c 46 bb 40 42 0f 00 6a 01 58 89 c7 90 68 |.ELF.@B..j.X...h| -> ELF header 00000010 02 00 3e 00 b2 0e eb 10 04 00 3e 00 00 00 00 00 |..>.......>.....| -> ELF header 00000020 30 00 00 00 00 00 00 00 5e 83 c6 46 0f 05 eb 30 |0.......^..F...0| -> ELF header 00000030 01 00 00 00 05 00 38 00 01 00 00 00 00 00 00 00 |......8.........| -> ELF, Program header 00000040 01 00 3e 00 00 00 00 00 48 65 6c 6c 6f 2c 20 57 |..>.....Hello, W| -> Program header 00000050 6f 72 6c 64 21 0a 00 00 6f 72 6c 64 21 0a 00 00 |orld!...orld!...| -> Program header 00000060 ff cb 75 a5 6a 3c eb a3 |..u.j<..| -> Program header 00000068 $ ./104byte.out | head Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! $ ./104byte.out | wc 1000000 2000000 14000000
主な更新点は2つです。
順番に説明したいと思います。
前回はELFヘッダとプログラムヘッダを8バイト重ねましたが、さらに+8バイトつまり16バイト重ねることができます。
| バイナリ位置 | ELFヘッダ | プログラムヘッダ |
|---|---|---|
| 0x30 | e_flags: 何でもOK | p_type: 0x0000_0001 |
| 0x34 | e_ehsize: 何でもOK | p_flags: 下位が0x0005(RX)、上位は何でもOK |
| 0x36 | e_phentsize: 0x0038 | |
| 0x38 | e_phnum: 0x0001 | p_offset_l: 0x0000_0001 |
| 0x3a | e_shentsize: 何でもOK | |
| 0x3c | e_shnum: 何でもOK | p_offset_h: 0x0000_0000 |
| 0x3e | e_shstrndx: 何でもOK |
今までp_type, p_flagsとe_flags〜e_phentsizeは重ねられないと思っていましたが、p_flagsは下位が0x0005(Read, Executable)であれば上位ワードは何でも良く、うまく重ねられることを知りました。いやーこれはすごい。この工夫によってELFヘッダ + プログラムヘッダのサイズが104バイトになります。

値を変更できない部分(黄色: ELFヘッダ由来、緑色: プログラムヘッダ由来)
今回のバイナリで自由に変更してはいけない部分に色を塗るとこんな感じです。黄色がELFヘッダに由来する制約、緑色がプログラムヘッダに由来する制約です。
ちなみにp_flagsにWriteをつけるとSEGVでクラッシュして、カーネルがこんなエラーログを出します。
__vm_enough_memory: pid: 764111, comm: 104byte.out, bytes: 11138535030784 not enough memory for the allocation
Write属性をつけると書き込み用のメモリを確保しようとするのだと思われます。プログラムヘッダのp_fileszやp_memszにめちゃくちゃな値を指定しているため、そんなサイズは確保できずにエラーになります。従ってp_flagsは0x7(RWX)でなく0x5(RX)が必須です。
プログラムヘッダのp_vaddr(オフセット0x40)とp_paddr(オフセット0x48)は同じ値でなければならないと勘違いしていましたが、実はp_paddrはどんな値でも動作することを知りました。ELFとプログラムヘッダの重ね合わせで失われた8バイトを挽回しうる空き地となるでしょう。
ELFヘッダやプログラムヘッダの制約をリストアップするとこんなところです。
前回はC言語の配列で作っていましたが、バイナリコードに変換するのが面倒くさいので最初からアセンブラで書きます。アセンブラ実装はトリッキー度合いが低い方(2024年2月26日の日記参照)を流用しました。
.intel_syntax
.globl _start
.set _top, 0x3e0000
//e_ident
.byte 0x7f, 'E', 'L', 'F'
_start:
mov %ebx, 1000000
_loop:
push 0x01
_last:
pop %rax
mov %edi, %eax
nop
//push imm inst
.byte 0x68
//e_type
.word 0x0002
//e_machine
.word 0x003e
_second:
mov %dl, 0x0e
jmp _third
//e_entry
.quad _top + 4
//e_phoff
.quad 0x30
_third:
pop %rsi
add %esi, 0x46
syscall
jmp _fourth
//e_flags(any), p_type(0x01)
.long 0x01
//e_ehsize(any), p_flags low(0x05)
.word 0x05
//e_phentsize(0x38), p_flags high(any)
.word 0x38
//e_phnum(0x01), e_shentsize, e_shnum, e_shstrndx, p_offset(0x01)
.quad 0x01
//p_vaddr
.quad _top + 1
//p_paddr(any)
.byte 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W'
//p_filesz
.byte 'o', 'r', 'l', 'd', '!', 0xa
.word 0
//p_memsz
.byte 'o', 'r', 'l', 'd', '!', 0xa
.word 0
//p_align(any)
_fourth:
dec %ebx
jne _loop
push 0x3c
jmp _last
トリッキーなことはせず素直に詰め替えましたが、それでも104バイトに収まりました。いいね〜。
$ as 104byte.asm -o 104byte.o $ ld --oformat binary 104byte.o -o 104byte.out $ ./104byte.out | wc (略) $ ./104byte.out | head (略) $ objdump -D 104byte.o -M intel (Intel記法で逆アセンブルする方法です、デフォルトはAT&T記法)
ビルド方法はこんな感じです。
今回はHello, World!の出力にwriteシステムコールを使っていて、rsiレジスタに文字列の先頭アドレスを渡す必要があります。単純にmov命令でesiレジスタに32bit即値をセットすると、5バイト命令が必要で配置に大きな制約が生じますので、小さい命令に分割して配置を容易にする方法を考えます。
単純に分割するとpush 5バイト、pop 1バイト、add 3バイトですが、ELFヘッダ内で変更できない邪魔者であるe_typeとe_machineの前にバイト0x68を置いてpush 0x3e0002命令にしてしまえば、4バイト節約できます。pushした値は下位アドレス2なので文字列の先頭(0x3e0048)を指すため0x46をaddします。
//push imm inst
.byte 0x68
//e_type
.word 0x0002
//e_machine
.word 0x003e
_second:
mov %dl, 0x0e
jmp _third
//e_entry
.quad _top + 4
//e_phoff
.quad 0x30
_third:
pop %rsi //pushした0x3e0002をpop
add %esi, 0x46 //esiの下位を0x02から0x48へ
syscall
jmp _forth
...
もう一つのカギはプログラムヘッダのp_vaddrを0x3e0001にして、プログラムを0x3e0001にロードすることです。下位バイトが1なのはプログラムヘッダのp_offsetが1になっているせいです。
この記事にコメントする
今使っているノートPC(Lenovo ThinkPad E480)のCPUはIntel Core i5 8250Uです。第8世代のCore i5、コードネームKaby Lake RefreshなCPUです。
かろうじてWindows 11 24H2のサポートCPUリストに入っています(Windows プロセッサ要件 - Windows 11でサポートされているIntelプロセッサ - Microsoft Learn)。しかし今年で8年目の古いCPUですし、次の大型アップデートか何かでプロセッササポート範囲の変更が来たら間違いなくサポートが切られる対象でしょう。
前回購入した時はノートPCしかなかったので毎日使っていました。今はデスクトップPCがWindowsのメイン稼働機で、ノートPCはあまり使いません。次のノートPCに性能カスタムはあまり要らないでしょう。
一番悩ましいのは機種です。今までThinkPad一筋でしたが、
特に感圧タッチパッドは押してる感がなくてとても嫌いでして、あれが搭載される機種は絶対買いません。会社で使っているX1 Carbonも旧来のボタン+タッチパッドに戻せるなら戻したいくらいです。
そんなこんなでThinkPadが明後日の方向に旅立ってしまいました、これはグッバイLenovoですかね……。とりあえず他社のノートPCも購入検討対象にしましょう。
この記事にコメントする
目次: ブラウザー/メーラー
Google ChromeでYahoo!を見ているとたまにChromeの広告が表示されます。
こんな感じです。ダウンロードしてどうしろというのか。広告って実はブラウザの種類を判定してないのかなあ?なかなか謎の挙動です。
この記事にコメントする
GNOME48からフォントが変わるニュースを見ていて、フォントを作成するのが割と簡単な英語圏にしては、Cantarellを15年も使っていたのは珍しい?んでしょう。結構息が長かったんだなあと思います。
フォントが長く使われる傾向に関しては日本語フォントもたいがいだよなあ?と思ったのでWindowsの日本語フォント変遷をリストアップしてみました。
やはりWindowsの日本語フォントはみんな息が長いです。次世代にバトンを渡すまでの期間だけ見ても、MSゴシック13年、メイリオ9年、游ゴシックも10年経ったんですね。Windowsの次バージョンが出るか出ないか定かじゃないですけど、もし次があったとしたらフォントを変えてきそうですね。
この記事にコメントする
目次: ブラウザー/メーラー
Thunderbird 115.0(Supernova, 2023/07/11リリース)から導入されたUnified Toolbarですが、使わないのに最上段の一番良いスペースにずっと表示され続けていて邪魔です。だけど利用者(or開発者?)には気に入られているのか、安定版がThunderbird 128系に変わってもUnified Toolbarはウインドウ最上段に鎮座し続けています。
Unified Toolbarを消せないかなと思い、とりあえずToolbarのボタンを全てRemoveしました。しかしツールバーそのものは消えなくて虚無が残ります。

Unified Toolbarのボタンは消せるが、Unified Toolbarそのものは消せない
何か他の方法は?と調べたら割とすぐに「消すの無理」と回答されているフォーラムQA(How to remove the new toolbar in version 115? - Thunderbird Support Forum - Mozilla Support)を見つけてしまいました。うーん、イマイチだ〜。
GUIはイマイチでも、ThunderbirdのUXの神髄はメールの削除速度にあると思います。
昔のThunderbirdはもう本当にメール削除が遅くて、途中で寝てしまえるレベルでした。たぶん秒間10〜20通程度だったと記憶しています。しかし正確な時期はわかりませんが、しばらく前にメール削除の速度が超々々々改善されまして、今では1,000通overを一瞬で削除できるようになっています。
メール削除スピード改善は自分にとっては最高のUX改善でした。GUIなどの他のイマイチな点が全て霞むレベルです。
この記事にコメントする
| < | 2025 | > | ||||
| << | < | 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 | - |
wiki
Linux JM
Java API
2002年
2003年
2004年
2005年
2006年
2007年
2008年
2009年
2010年
2011年
2012年
2013年
2014年
2015年
2016年
2017年
2018年
2019年
2020年
2021年
2022年
2023年
2024年
2025年
過去日記について
アクセス統計
サーバ一覧
サイトの情報合計:
本日: