← hyperanoka.com

アセンブリ①

/assembly1 · 最終更新 2026-09-16 13:05

さて、、また戻ってきてしまいました。。 低レイヤ③まで頭に入れて、Binary Hacks Rebootedに再度挑んだものの、 アセンブリの知識がなさすぎて詰んだのです、、まあそりゃそうですよね、、、 ということで、アセンブリ基礎、、たたきこみ、、始めます、、、 すらすら読み書きできるみたいなレベルは目指していません。 読みたい本にしがみついていけるだけの基礎知識が欲しいのです、、

アセンブリ

CPUが直接理解できるのは、機械語ですよね。 人間には機械語は読めません。 CPUにとって意味のある01の羅列に、人間が読める名前を1対1でつけたものがアセンブリと呼ばれます。

アセンブル:アセンブリ→機械語 逆アセンブル:機械語→アセンブリ

私たちが低レイヤに触れる時には逆アセンブルをして出てきたアセンブリを読む。 ということになりますね。

AT&T記法 と Intel記法

1対1とは書きましたが、アセンブリにも種類があり、 Linuxでよく使われるのは、objdumpやgdd -Sらしいのですが、それはAT&T記法というものらしいです。 で、windowsではintel記法という方法で書かれているらしいです。

何が違うかというと、結構厄介で、、

・代入の向き Intel記法は右から左で、mov dst, srcと書きます。 AT&T記法は左から右でmov src, dstと書きます。 これ知らなきゃやばいね!!!

・レジスタや値の書き方 Intel記法は「rax」や「1」、AT&T記法は「%rax」や「$1」

・メモリ参照 Intel記法は「[rbp-8]」、AT&T記法は「-8(%rbp)」

などのちがいがあるそうです。困ります。 ※objdumpのオプションでどっち表記にもできるそうです。

レジスタ

レジスタとはメモリより桁違いに速い、CPU内蔵の作業用の記憶領域です。 レジスタには2種類あり、役割が決まってるレジスタと用途が固定されていないレジスタがあります。

汎用レジスタ

レジスタとはメモリより桁違いに速い、CPU内蔵の作業用の記憶領域です。 その中で汎用レジスタとは、用途が決まっていないレジスタのことを言います。 数値を入れて足し算してもいいし、アドレスを入れてポインタにしてもいいということです。 ※役割が決まっているもののことを専用レジスタ、特殊レジスタという。「命令ポインタ(rip)」「フラグレジスタ(rflags)」のことで、個別で呼ばれることが多い。

x86-64 の汎用レジスタは16本ある。 rax:戻り値・演算の主役。syscall番号もここ rbx:汎用(呼び出し先が保存する側) rcx:第4引数 / ループカウンタの名残 rdx:第3引数 / 戻り値の上位64bit rsi:第2引数 rdi:第1引数 rbp:ベースポインタ(スタックフレームの基準) rsp:スタックポインタ(スタックの先端) r8〜r15:汎用。r8,r9 は第5,第6引数

この一覧を見て、汎用レジスタなのに決まってるじゃんと思ったそこのあなた。。仲間です。。 実は、専用レジスタとかの役割が決まっているというのは、CPU回路などの問題で、物理的に決まっているもののことらしいです。 汎用でジスタの上記の使い道の決まりは、習慣や人間の取り決めでそうなっている、ということです。

なるほど、、、

部分レジスタ

汎用レジスタの「下の方だけを覗く小窓」です。 大昔の8086は16bit(ax)、386で32bit(eax)に拡張、AMD64で64bit(rax)に拡張…と、 後方互換のために古い名前を残したまま上に積み上げてきました。なので、 同じ箱に何段も名前があるよう。 1バイトの文字を扱うだけなら al で足りる、みたいに必要な幅だけ触れるのが利点だそうです。 例えば、 eax(32bit)に書き込むと → 上位32bitが自動でゼロになる ax / al(16/8bit)に書き込んでも → 上位はそのまま残る

フラグレジスタ RFLAGS

演算の結果を覚えておくビット群です。条件分岐がこれを読むです。 ZF(zero):結果が0 SF(sign):結果が負(最上位ビットが1) CF(carry):符号なしで桁あふれ OF(overflow):符号ありで桁あふれ

基本の命令

mov dst, src コピー(move だが元は消えない) lea dst, [式] アドレス計算だけして入れる push src スタックに積む pop dst スタックから降ろす add dst, src dst += src sub dst, src dst -= src inc/dec dst +1 / −1 and/or/xor dst, src ビット演算 xor rax, rax 定番。raxを0クリア(自分とXORは0)。mov rax,0より短い cmp a, b a - b を計算してフラグだけ更新(結果は捨てる) test a, b a & b でフラグ更新。test rax, rax で「raxは0か?」 jmp ラベル 無条件ジャンプ je / jne 直前が等しい / 等しくない ならジャンプ(ZFを見る) jl / jg 符号ありで < / >(signed) jb / ja 符号なしで < / >(unsigned, below/above) call ラベル 関数呼び出し。戻りアドレスを積んでジャンプ ret 関数から戻る nop 何もしない(詰め物・パッチで多用。#18 バイナリパッチ)

メモリアドレッシング

オペランドには3種類あります。 1.即値(immediate):mov rax, 5 の 5。定数そのもの 2.レジスタ:mov rax, rbx。箱の中身 3.メモリ:mov rax, [rbx]。[ ] は「そのアドレスの中身」=ポインタの * に相当

Intel記法ではメモリ参照の一般形は全部 [ base + index*scale + disp ] と書ける。 base ベースになるレジスタ(例 rbp) index 添字レジスタ(配列の i) scale 1 / 2 / 4 / 8(要素のバイト数) disp 定数のズレ(displacement)

AT&T の -8(%rbp) = Intel の [rbp-8]。この [rbp-8] みたいなのが「ローカル変数」の正体 rbpはベースポインタ(スタックフレームの基準)で、そこから8ずらしたものです。 ベースポインタは、ずれることのない決まった基準となるポインタです。 そこからいくつずれたところに変数があるか、ということです。 -だとローカル変数、+だと戻りアドレスというものを示すそうです。