目次: GCC
以前(2020年5月24日の日記参照)、変数の代入操作をexpandで展開する際、代入を分割するか分割しないか、を決める条件の1つとして、optab_handler() という関数が出てきました。この関数の動作に関わるgenopinitというツールの動作を調べます。
前回(2020年6月3日の日記参照)は分岐条件に関わるenum optab_tagの生成について調べました。今回はもう少し複雑なpats[].scodeの生成について調べます。
突然pats[].scodeと言われても何だかわからないと思います。私もこんなひどい名前の変数、全く覚えられません。復習も兼ねてoptab_handler() を見直します。
// gcc/optabs-query.h
/* Return the insn used to implement mode MODE of OP, or CODE_FOR_nothing
if the target does not have such an insn. */
inline enum insn_code
optab_handler (optab op, machine_mode mode)
{
unsigned scode = (op << 16) | mode; //★★scodeの意味はここにある通り
gcc_assert (op > LAST_CONV_OPTAB);
return raw_optab_handler (scode); //★★これ
}
// build_gcc/insn-opinit.c
enum insn_code
raw_optab_handler (unsigned scode)
{
int i = lookup_handler (scode); //★★これ
return (i >= 0 && this_fn_optabs->pat_enable[i]
? pats[i].icode : CODE_FOR_nothing);
}
static int
lookup_handler (unsigned scode)
{
int l = 0, h = ARRAY_SIZE (pats), m;
while (h > l)
{
m = (h + l) / 2;
if (scode == pats[m].scode) //★★これ
return m;
else if (scode < pats[m].scode) //★★これ
h = m;
else
l = m + 1;
}
return -1;
}
//★★pats[] の定義
struct optab_pat {
unsigned scode;
enum insn_code icode;
};
static const struct optab_pat pats[NUM_OPTAB_PATTERNS] = {
{ 0x010405, CODE_FOR_extendqihi2 },
{ 0x010406, CODE_FOR_extendqisi2 },
{ 0x010407, CODE_FOR_extendqidi2 },
{ 0x010505, CODE_FOR_extendhihi2 },
...
{ 0x021c1b, CODE_FOR_truncdfsf2 }, //★★この変な数字(scode)0x021c1bは誰が作るのか?
...
コードからscodeの意味、上位16ビットがoptabで、下位16ビットがmachine mode、は理解できると思います。patsにはscodeが無数に並んでいますが、この数字を誰が作るかというとgenopinitです。
前に見たoptabsの定義は色々ありますが、必ず2つ以上の引数を取り、1番目がname(mov_optab, trunc_optabなど)、2番目がpatternとなっており、この2つは全ての定義に存在します。
// gcc/optabs.def
OPTAB_CL(trunc_optab, "trunc$b$a2", TRUNCATE, "trunc", gen_trunc_conv_libfunc)
'-- name `-- pattern
このうちnameはenum optab_tagの変数名として使っていました。その他にもcode_to_optab_[] という配列の定義にも使いますが、今はどうでもいいです。前後に文字が付くくらいで、基本的に名前がそのまま使われます。
一方2番目のpattern(例えば "mov$a", "trunc$b$a2" など)はちょっと変わっています。$aや $bという不思議な文字が入ります。
前回説明したとおりgenopinitの入力はgcc/common.md, gcc/config/riscv/riscv.md, build_gcc/insn-conditions.mdの3つの *.mdファイルです(mdはmachine descriptorの略)。Lispっぽい記法で、define_insnやdefine_expandを追加したファイルです。
基本的なgenopinitの動作は、
パターンマッチのコードはこんな感じです。
// gcc/gensupport.h
/* Information about an .md define_* rtx. */
class md_rtx_info {
public:
/* The rtx itself. */
rtx def;
/* The location of the first line of the rtx. */
file_location loc;
/* The unique number attached to the rtx. Currently all define_insns,
define_expands, define_splits, define_peepholes and define_peephole2s
share the same insn_code index space. */
int index;
};
// gcc/genopinit.c
int
main (int argc, const char **argv)
{
...
if (!init_rtx_reader_args_cb (argc, argv, handle_arg)) //★★ *.mdを解析
return (FATAL_EXIT_CODE);
...
/* Read the machine description. */
md_rtx_info info;
while (read_md_rtx (&info)) //★★RTXを1つずつ取り出す
switch (GET_CODE (info.def))
{
case DEFINE_INSN: //★★define_insn, define_expandだけ注目
case DEFINE_EXPAND:
gen_insn (&info); //★★これ
break;
static void
gen_insn (md_rtx_info *info)
{
optab_pattern p;
if (find_optab (&p, XSTR (info->def, 0))) //★★これ
patterns.safe_push (p);
}
// gcc/gensupport.c
bool
find_optab (optab_pattern *p, const char *name)
{
...
/* See if NAME matches one of the patterns we have for the optabs
we know about. */
for (unsigned int pindex = 0; pindex < ARRAY_SIZE (optabs); pindex++) //★★全てのpatternを試す
{
p->m1 = p->m2 = 0;
if (match_pattern (p, name, optabs[pindex].pattern)) //★★これ
{
p->name = name;
p->op = optabs[pindex].op;
p->sort_num = (p->op << 16) | (p->m2 << 8) | p->m1; //★★scodeを作る
return true;
}
// gcc/gensupport.h
/* Information about an instruction name that matches an optab pattern. */
struct optab_pattern
{
/* The name of the instruction. */
const char *name;
/* The matching optab. */
unsigned int op;
/* The optab modes. M2 is only significant for conversion optabs;
it is zero otherwise. */
unsigned int m1, m2;
/* An index that provides a lexicographical sort of (OP, M2, M1).
Used by genopinit.c. */
unsigned int sort_num;
};
申し訳ないですが *.mdファイルの解析関数init_rtx_reader_args_cb(), read_md_rtx() 辺りは調べる予定がありません。どなたか調べてくれたら嬉しいです。
パターンマッチはnameに対して行われ、全てのpatternとマッチするか試します。マッチしなければmatch_pattern() はfalseを返しますから、次のpatternを試します。マッチしたら、結果はoptab_pattern *pに格納され、match_patter() が作り出すp->m1, p->m2という謎の数からscodeが生成されます。
なぜgenopinitではsort_numという変数名にしたんでしょうね?scodeにすればもう少しわかりやすいのに。
パターンマッチのルールと、m1, m2が何者か?については、文章で説明できる気がしないので、例としてname = "truncdfsf2" を見ながら説明したいと思います。マッチするpatternは "trunc$b$a2" です。他のpatternはマッチしません。
// gcc/config/riscv/riscv.md
//★★nameの例
(define_insn "truncdfsf2" ★★名前
[(set (match_operand:SF 0 "register_operand" "=f")
(float_truncate:SF
(match_operand:DF 1 "register_operand" " f")))]
"TARGET_DOUBLE_FLOAT"
"fcvt.s.dt%0,%1"
[(set_attr "type" "fcvt")
(set_attr "mode" "SF")])
// gcc/optab.def
//★★ マッチするpatternの定義部分(2番目の引数)
OPTAB_CL(trunc_optab, "trunc$b$a2", TRUNCATE, "trunc", gen_trunc_conv_libfunc)
// gcc/gensupport.c
//★★
//pは結果格納用の変数
//name = "truncdfsf2"
//pat = "trunc$b$a2"
static bool
match_pattern (optab_pattern *p, const char *name, const char *pat)
{
...
★★match_pattern() がtrueを返した後、find_optab() がreturn trueで終了する直前でダンプ (gdb) p optabs[pindex] $13 = { name = 0x4574b5 "trunc_optab", pattern = 0x4574c1 "trunc$b$a2", base = 0x4574cc ""trunc"", suffix = 0x457478 "'\0'", libcall = 0x4574d4 "gen_trunc_conv_libfunc", op = 2, fcode = TRUNCATE, rcode = UNKNOWN, kind = 1 } (gdb) p/x *p $15 = { name = 0x4dd270, op = 0x2, m1 = 0x1b, m2 = 0x1c, sort_num = 0x21c1b } pは結果格納用のoptab_pattern *p
この定義のpatternは2番目の引数 "trunc$b$a2" です。全部繋がっていてわかりにくいですが、下記の4つの要素から構成されます。
このうち $aや $bはmachine modeの名前にマッチします。具体的には下記のようになります。
nameとpatが name = "truncdfsf2" pat = "trunc$b$a2" の場合、 $b -> df $a -> sf にマッチします。$a, $bのマッチを調べるときはmode_nameを先頭から検索(大文字小文字の違いは無視)します。 全モードの名前はmode_name[] という配列に入っています。 $bはmode_name[28] = "DF" と一致する。 $aはmode_name[27] = "SF" と一致する。 match_pattern() は一致したモードの番号をm1, m2に格納します。 つまりoptab_patternのm1, m2の意味はそれぞれ m1: $aがマッチしたモードのインデックス、今回だと27 = 0x1b (E_SFmode) m2: $bがマッチしたモードのインデックス、今回だと28 = 0x1c (E_DFmode) 結果scodeはこうなります。 p->sort_num = (p->op << 16) | (p->m2 << 8) | p->m1; = (2 << 16) | (28 << 8) | 27; = 0x21c1b // build_gcc/insn-opinit.c static const struct optab_pat pats[NUM_OPTAB_PATTERNS] = { ... { 0x021c1b, CODE_FOR_truncdfsf2 }, //★★この値が生成された
謎のpats[].scodeはこんな仕組みで生成されていたのでした。ややこしいですね。
目次: GCC
FSF (Free Software Foundation) のCopyright Assignmentにサインしてみました。とりあえずGCCとbinutilsです。ツールチェーン仲間としてはglibcもやっておけば良かったかも??まあいいか。
手続きは特に難しくなく、
これで完了です。
FSFからの返事はそれぞれ数日〜1週間程度、という感じでした。GNUのソフトウェアにパッチを送りたいときは、個人でこの契約を交わす(今回はこっち)か、会社の業務で作成している場合は、会社でこの契約を交わす(こっちのやり方は知らない)必要があります。
FSFがなぜこんな面倒なことをしているのかについては、なぜFSFは貢献者に著作権の譲渡をお願いしているのか - GNUプロジェクトに書いてあります。過去に訴えられたり何か嫌なことがあったんでしょうね。
テンプレートは Copyright Papers (Information for Maintainers of GNU Software) を見るとGNUのメンテナーに要求してくれと書いてあります。gnulibのリポジトリにも入っていて、私はこのファイルを送ったらOKでした(gnulibのgitリポジトリにあるrequest-assign.future)
FSFのCopyright Assignmentにはいろいろ書いてありますが、
読んでいると割と怖い内容ですが、会社で書いたコードを投稿しなければ問題はないです。
契約の範囲はソフトウェア毎(GCC, binutils, glibc, などなど)に契約する必要があります。契約の種類は「1回限り(request-assign.changes)」と「今後ずっと(request-assign.future)」があります。ソフトウェアを改善し続ける場合、毎回契約するのは面倒なので「今後ずっと」を選択すると思います。
契約の種類によってFSFに送るメールのテンプレートが異なります。詳細は Copyright Papers (Information for Maintainers of GNU Software) に書いてあります。和訳ないのかなあ、これ。
目次: Kindle
割と昔からですがKindle Fireがストアで買った本を認識しない(ダウンロードしない、一覧に出てこない)病気が頻発し、買ったはずの本が消えて、非常に困っています。
Kindle Fireがロストした本は、Kindle for PCだとあっさり捕捉できますから(「新しい商品」順に並べると確認しやすい)、Kindle Fireのバグだと思われます。
根本対策は不明ですが、対症療法はいくつかあります。お手軽な順に、
Kindleストアも割と曲者で、複数のダウンロードボタンがある変な仕様(2018年11月17日の日記参照)です。1回押してダメでも諦めず、他の種類のダウンロードボタンも押してください。どれかが成功すればラッキーです。
最終手段ファクトリリセットには1度だけお世話になりました。しかし再設定、再ダウンロードにかなり時間を浪費して辛いので、できればもう二度とやりたくありません。
Kindle Fireはちょっと変な挙動が多いです。仕様かバグか良くわからないものもあります。
Kindleのメインであるはずの本の機能さえ、この有様なので、本以外(アプリ、ミュージック、ムービーなど)の機能は一体どうなっていることやら??
メモ: 技術系の話はFacebookから転記しておくことにした。大幅に追記。
はるか昔(2007年6月7日の日記参照)に購入したピアノ音源PMI Boesendorfer 290を現在使っているPCにインストールしました。
当時の日本代理店はクリプトンでしたが、2008年に違う会社に変わったようです。
それはさておき、ソフトは古くても(13年前!)音源自体はインストールでき、音も正常に鳴ります。Windowsの後方互換性は本当に素晴らしいなと思います。しかしアクティベーションだけがどうしてもできなくて困っています。
Boesendorfer 290はインストール時にシリアル番号の入力が必要で、インストール後はアクティベーションが必要です。アクティベーションは各PC固有の番号と紐付いていて、別のPCにインストールしたときは再アクティベーションが必要となります。面倒くさいですね。
アクティベーションをしない状態で使い続けると、インストール後5日間に動かなくなってしまいます。
購入当時はアクティベート画面の「REGISTER NOW」を押せば、オンラインでアクティベーションできましたが、現在このボタンを押すとエラーになります。
アクティベーション用のサイトのURLが変わったのか、ブラウザの接続警告が出ますし、転送先のサイトでは「そんなページは知らない」って言われます。これはひどい。
オンラインがダメならオフラインアクティベーションを試そうと思い「FILL OUT FORM」を押してみましたが、やっぱりダメでした。
エラーメッセージを見てもInternet browserの何がダメかわかりません。当然、ユーザーとしても対処しようがありません。このソフトを開発した人たちも、まさか13年後にアクティベーションする奴がいるとは思っていなかったでしょう。
これ以上どうにもならないので、サポートに連絡することにしました。Boesendorfer 290の販売はNative Instruments社、開発はEastWest社とありますが、どこに連絡すれば良いかわかりません。とりあえず販売Native Instruments社のサポートに連絡してみようと思います。
以前(2020年6月12日の日記参照)にインストール&アクティベートで失敗したBoesendorfer 290ですが、Native Instrumentsのサポートの方とのQAにより、インストール方法がわかりました。
カギはNative Accessというツールと、Kontakt Playerというアプリケーションです。
Native Accessはインストーラ&情報管理ツールです。アップデートもお知らせしてくれるようです。動きに癖があり、やや使いにくいです。
Kontakt Playerは音源データから音色を合成するプレイヤーです。なんと無料、凄い。私はプレイヤーとしてしか使っていませんが、本来はもっと高機能なのかな?
私の理解が正しければ、Boesendorfer 290にはピアノ音源データと、音色合成プレイヤーの2つがセットになっていますが、音色合成プレイヤーはもはやアクティベーション不可能で利用できません。代わりにKontakt 6 Playerを使うのが正解です。
最初にNative Accessをインストールします。インストーラを起動するだけです。このアプリはたまにアップデート確認画面から進まなくなることがあります。私の場合、Native Accessを再起動するか、Windowsを再起動したら直りました。
Kontakt 6 Playerをインストールしようとすると、こんな画面になると思います。
これがNative Accessの良くないところですね……、ディレクトリの設定に失敗しているとこのエラーになりますが、エラーメッセージを見ても、何が悪いのか全くわからないです。
Preferencesというメニューからディレクトリの設定ができます。パスを指定するだけではダメで、ディレクトリが存在していないとさっきのエラーが出ます。パスの最後のディレクトリがなかったら、勝手に作ってくれれば良いのに。これもNative Accessのイマイチなところです……。
次にKontakt 6 Playerをインストールします。
ダウンロードにはかなり時間が掛かりますが、待っていれば終わります。これはNative Accessの良いところですね。とても楽です。
最後にKontakt 6 PlayerにBoesendorfer 290がインストールされている位置を教える必要があります。CDからBoesendorfer 290をインストールした後に、下記のBrowseを押します。
スクリーンショットではBoesendorferがINSTALLED PRODUCTSのカテゴリに居ますが、初回の設定だとNOT INSTALLEDのカテゴリに居るはずです。1回インストールすると二度とNOT INSTALLEDに戻らないみたいです。これもNative Accessのイマイチなところ……。
このパス指定がまた曲者で、どのディレクトリを指定したら良いのかさっぱりわかりません。
正解はBoesendorferをインストールしたディレクトリの直下にある「Boesendorfer 290 Library」というディレクトリです。Boesendorfer_part1.nksやBoesendorfer_part2.nksというファイルが入っているはずです。
おそらく *.nksがKontakt用の音色データで、Kontakt Playerは音色データのあるディレクトリを知りたいんだろう、くらいの想像はつきますけど、正直に言ってわかりにくいです。
設定がうまくいけばKontakt Playerの画面にBoesendorfer 290が出現するはずです。
Kontakt PlayerにBoesendorfer 290が出れば成功
無事、動作したので良かったですけど、初心者殺しのポイント(Preferences, *.nksのパス)が多くて疲れました……。
半年くらい前からSAS(睡眠時無呼吸症候群)の対症療法としてCPAP(経鼻的持続陽圧呼吸器)を使っています。しかし、こいつがどうも息苦しくて合ってない気がしてなりません。
呼吸が止まるとSpO2(血中酸素飽和度)が下がります。医療機器以外でSpO2を測る手段はありませんから、先日、思い切ってメモリ機能付きのパルスオキシメーター(=SpO2計測器)Ubi-x LUKLA 2800mを購入しました。お値段6万円です。さすが医療機器、高いなー。
添付のグラフはSpO2(青、左目盛り)と、脈拍(オレンジ、右目盛り)です。正常時のSpO2は98〜99%ですが、深夜4時3分に突如88%まで下がっています。
SpO2 88%がどれほど危険なのかわからなかったので、わざと息を止めてSpO2を下げる実験をしたところ「酸欠で目がチカチカして暗くなってきて、このまま死ぬんじゃねー?」と思うくらいでやっとSpO2 92%でした。88%とは一体……??
CPAPなしだとどうなるかも測りました。CPAPありのときよりも頻繁にSpO2が低下しています。
寝苦しくて辛いけどCPAPには意味があるということなんでしょうかねえ?
まだ1度ずつしか測っていないので、CPAPの効力については何とも言えません。今後CPAPあり・なし、仰向き、横向きなど条件を変えて何度か測ってみようと思います。
メモ: 技術系の話はFacebookから転記しておくことにした。大幅に追記。
目次: Linux
最近、というほどでもないのですが、Linux KernelのデバイスツリーのドキュメントはYAMLで書く方が主流らしいです。
ドキュメントの書き方が良くわからず、合っているのか間違っているのかわからなくて困っていたんですが、ドキュメント Writing DeviceTree Bindings in json-schema を見ていたところ、YAMLチェッカーの存在を知りました。
$ make dt_binding_check DT_SCHEMA_FILES=Documentation/devicetree/bindings/xxxx.yaml
こんな感じで使います。DT_SCHEMA_FILESを指定せずに実行すると全てのYAMLをチェックしようとしますが、16並列でもかなり時間が掛かりますから、自分が変更したファイルだけチェックしたほうが効率的でしょうね。
目次: ゲーム
STATIONflowのしょうもない小技。
将来的に、どこに駅の入り口と電車の乗り場が出てくるか?を見たい場合は「マップ作成」で見たいマップを「複製」「マップ編集」すれば、全ての入り口、乗り場を見ることができます。
STATIONflowの面白さは、どこに増設されるかわからない入り口や乗り場に対応することではあるのですが、記録を狙ったりするときや、事前に完璧な駅建築計画を立てたい人は、覗いてみると役立つかもしれません。
目次: ゲーム
STATIONflowの基本は理解したつもりなので、実績解除に挑んでますが、難しくて取れないものがいくつかあります。
今のところ一番難しいと思うのは「エレベスト」です。条件は「駅ランク20以上で階段とエスカレーターを設置せずにA+ 評価」です。
ありがちな条件ですが、STATIONflowのエレベーターはとてもヘボいため達成は困難です。
一番問題なのは3つ目の条件です。通常、待ち行列から離脱した客は階段やエスカレーターを利用しますが、移動手段がエレベーターしかない場合、不可解な動きをします。
(※)彷徨っている間はエレベーターが到着しても乗りません。一番訳が分からない動きです。しかもこの彷徨う時間が長い。
このようなおかしな行動をし続けた挙句、勝手に怒り始めて、駅の評価を下げてきます。理不尽極まりないですね。
ランク20到達後も駅と人が増え続けるため、どんどん客が捌ききれなくなり条件が悪くなる一方ですから、「エレベスト」実績を達成する最大のチャンスは「ランク20になる瞬間」でしょう。
が、私の腕では評価Aが限界でした。
もっと頑張ればできるのかもしれないですが、何回も同じマップをランク20までやり直すのは時間掛かって辛いし面白くないのでもうやりません……。
目次: ゲーム
STATIONflowのしょうもない小技 その2です。
実績の解除が非常に難しい「エレベスト」や「効率の鬼」のような難しい実績が、超簡単に取れる方法です。
例えば、エレベストの条件は「駅ランク20以上で階段とエスカレーターを設置せずにA+ 評価」です。前回(2020年6月27日の日記参照)お伝えした通り、エレベーターと通勤客の変な挙動が合わさって、まともにやるとかなり難しいです。
しかしSTATIONflowの実績判定は甘々で「00:00になった瞬間」しか見ません。従って、
これだけで達成できます。正直、こんな低レベルな小細工が通じると思わなかったので、逆にびっくりしました……。
類似した実績「階段抜き」「ノンエスカレーター」「エレベスト」「効率の鬼」ならば同じ手が通用するはずです。
作成者の想定とは違うだろうという意味で「邪道」な感じはしますが、バグを突いた挙動でもなさそうだし、早解きしたい方は利用してみても良いでしょう。
目次: Windows
その2はこちら。
ノートPCでゲームをしていると、筐体が焼けそうなほど熱くなり、キーボードまで熱くなってキーを打つのが辛いです。
発熱の原因はグラフィックスチップですが、どうもCPUも無罪ではないらしく、TurboBoostを無効にするとややマシになることがわかりました。
私のマシンでは「電源オプション」 - 「プロセッサの電源管理」 - 「最大のプロセッサの状態」にて、87%以下にするとTurboBoostがOFFになりました。
下記は87%設定(1.49GHz)と88%設定(3.38GHz)にしたときのCPU動作周波数の変化です。
当然ながら1.49GHzと3.38GHzでは性能に天と地ほどの差があって、1.49GHzだとSTATIONflowの画面はめちゃくちゃカクつきます。
しかしシミュレーションゲームでは、待っているだけの時もありますし、常に爆熱で動いてくれる必要はありません。TurboBoostを任意にOFFにできるのは非常に便利です。
CPUのクロック周波数の上限は何段階かあるようなので、変化点を調べました。
こんな感じでした。Core i5-8250Uはベース周波数1.6GHz、ブースト周波数3.4GHzなのにベース周波数である1.6GHzに張り付く設定は存在しません。謎です。
タスクマネージャーに「基本速度1.8GHz」と表示されているのも謎です。どこから1.8GHz出てきた……??
目次: ゲーム
以前(2020年5月28日の日記参照)STATIONflowで速度3にすると駅の評価が下がることをお伝えしましたが、若干間違っていて「画面の処理落ちで評価が下がる」方が実態に近そうです。
速度2でA+ 評価の駅を使って実験したところ、ノートPCのクロック周波数を低くするだけで、駅の評価がAに下がりました。
駅の評価と見せかけて、実はマシンの評価も入ってるんでしょうか?余計なお世話なので、勘弁してくれよ。
目次: ゲーム
STATIONflowのランクが100になりました。何か実績と紐づいているかなと思いましたが、特に何も起きませんでした。しょんぼり。
普通のマップだと重いし、時間も掛かりすぎてやってられないので、専用の軽量マップを作ってひたすら待ちました。それでも相当時間が掛かります。普通に遊ぶ分にはランク50で十分ですね。
STATIONflowでランク100にする途中でいくつかわかったことがありました。
ランク100まで行ってもまだ取れない実績が残っていて(ラッキーセブン、トウキョウ)、なかなか気が遠くなるゲームです。
(※)出入口が6 x 9 = 54、乗り場が6 x 2 = 12、集客レベルは2段階上がるため (54 + 12) x 2 = 132です。
STATIONflowの実績にtypoがありました。
漢字だけ間違ってます。惜しい……!
目次: ゲーム
Steamの買い物カートの中身一覧を見る方法を探していたんですが、全くわかりませんでした。だいぶ彷徨いましたが、未だトップ画面からカートを見る方法はわからないままです。Steamは本当にUIがひどい。Amazonを見習ってくれ……。
その際に副産物として、秘密の実績を確認する方法を見つけたのでメモしておきます。
と辿ると自分が所持しているゲームの一覧が出ますから、実績を見たいゲームの
と辿ると全ての実績が表示されます。
秘密の実績の意味とは一体……??
SEGVを出すのがTwitterで流行しているみたいなので、少し考えてみました。Twitterで見かけたのは、*a;main(){*a=0;}(16文字)です。最適化オプションにもよりますが、異常なアドレスへのmovか、未定義命令ud2が出力されてSEGVします。
$ echo -n '*a;main(){*a=0;}' > a.c && gcc a.c a.c:1:1: warning: data definition has no type or storage class 1 | *a;main(){*a=0;} | ^ a.c:1:2: warning: type defaults to 'int' in declaration of 'a' [-Wimplicit-int] 1 | *a;main(){*a=0;} | ^ a.c:1:4: warning: return type defaults to 'int' [-Wimplicit-int] 1 | *a;main(){*a=0;} | ^~~~ $ ./a.out Segmentation fault
ただSEGVするだけで良ければ、mainのアドレスを .textではないアドレスにすれば良いので、main;(5文字)でも、達成できます。
$ echo -n 'main;' > a.c && gcc a.c a.c:1:1: warning: data definition has no type or storage class 1 | main; | ^~~~ a.c:1:1: warning: type defaults to 'int' in declaration of 'main' [-Wimplicit-int] $ ./a.out Segmentation fault
この例はmainを関数ではなく変数として定義し、mainをbssセクションに配置します。最近のLinuxならばデータが置かれているセグメントは実行禁止にするはずなので、mainが指すデータが何であろうと、ジャンプした瞬間にSEGV します。
趣旨とは外れますがSEGVを避けて正常終了させたければ、
$ echo -n 'main=0xc3;' > a.c && gcc -z execstack a.c a.c:1:1: warning: data definition has no type or storage class 1 | main=0xc3; | ^~~~ a.c:1:1: warning: type defaults to 'int' in declaration of 'main' [-Wimplicit-int] $ ./a.out (SEGVしない)
変数mainが指す位置にretq命令(0xc3)を置いて、-z execstackオプションでデータ領域を実行可能にしています。attributeで .text領域に置いても実行できますが、そんなことするくらいならmain() 関数を書いた方が短いです。
変化球を使ってよければ0文字でSEGVできます。nostdlibオプションを使います。
$ echo -n > a.c && gcc -nostdlib a.c /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000 $ ./a.out Segmentation fault
GNU ld(他のリンカーでも同じだと思いますけど)はELFのエントリアドレスに _startというシンボルのアドレスを使います。通常はcrt.oなど、リンク時に自動的に追加されるオブジェクトが _startを定義しますが、nostdlibオプションによりcrt.oがリンクされなくなって、_startは未定義になります。
するとldは _startを適当なアドレスに設定(上記の例では0x1000に)します。当然 _startが指すアドレスにコードはありませんから、実行するとクラッシュします。
目次: ベンチマーク
昨日(2020年7月3日の日記参照)試したところによると、C言語は0文字でSEGVするプログラムを書けました。
では、出力されたバイナリだと最短はいくつでしょうか?とりあえずベースとしてC言語の0文字SEGVプログラムの出力を見ます。
$ gcc -nostdlib a.c /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000 $ ls -la a.out -rwxr-xr-x 1 katsuhiro katsuhiro 9528 Jul 3 04:11 a.out
なんと9KBもあります。readelfで見るとダイナミックリンカー関連のシンボルが含まれているようなので、staticオプションを付けてダイナミックリンカー関連のシンボルを消します。
$ echo -n > a.c && gcc -nostdlib -static a.c /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000 $ ls -la a.out -rwxr-xr-x 1 katsuhiro katsuhiro 968 Jul 3 04:12 a.out $ strace ./a.out execve("./a.out", ["./a.out"], 0x7ffc92c2b390 /* 46 vars */) = 0 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x401000} --- +++ killed by SIGSEGV +++ Segmentation fault
ログが大量に出るはずのstraceも、わずか1行しかログが出ません。何もしないバイナリにも関わらずa.outのサイズは1KB近くあります。
お手軽なバイナリのダイエットとして、実行に不要なセクションをstripします。
$ strip -R ".note.gnu.build-id" -R ".comment" a.out $ ls -la a.out -rwxr-xr-x 1 katsuhiro katsuhiro 376 Jul 3 04:18 a.out
それでも376バイト。まだでかいですね。
この376バイトのファイルを元にして、バイナリを手で削ります。加工の方針としては、
結果92バイトになりました。
$ ls -la a.out -rwxrwxr-x+ 1 katsuhiro katsuhiro 92 Jul 3 05:43 a.out
もっとアグレッシブにELFヘッダとプログラムヘッダを重ねれば、64バイトにできるかもしれません。
このファイルを実行してみると、システムコールexecveがエラーを返さないで進むので、ELFファイルとして認識してもらえているようです。
$ strace ./a.out execve("./a.out", ["./a.out"], 0x7fff2b4f74a0 /* 47 vars */) = 0 --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x400000001} --- +++ killed by SIGSEGV +++ Segmentation fault
$ readelf -a a.out ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400000001 Start of program headers: 36 (bytes into file) Start of section headers: 0 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 1 Size of section headers: 0 (bytes) Number of section headers: 0 Section header string table index: 0 There are no sections in this file. There are no sections to group in this file. Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align NULL 0x0000000000000000 0x0000000100380040 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x1000 There is no dynamic section in this file. There are no relocations in this file. The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported. Dynamic symbol information is not available for displaying symbols. No version information found in this file.
一応readelfでも読めますし、そこそこ真っ当なファイルをキープできていると思います。
< | 2020 | > | ||||
<< | < | 06 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | 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 | 30 | - | - | - | - |
合計:
本日: