Intel x86 Assembly Language & Microarchitecture チュートリアル
Intel x86アセンブリ言語とマイクロアーキテクチャの概要
サーチ…
備考
このセクションでは、x86の概要と、開発者がそれを使用する理由について概説します。
また、x86内の大きなテーマについても言及し、関連するトピックにリンクする必要があります。 x86のドキュメントは新しいものなので、これらの関連トピックの初期バージョンを作成する必要があります。
x86アセンブリ言語
x86アセンブリ言語ファミリは、元のインテル8086アーキテクチャの何十年もの進歩を表しています。使用されているアセンブラに基づいていくつかの異なる方言が存在することに加えて、1980年代に使用された16ビットアセンブリと下位互換性を維持しながら、追加のプロセッサ命令、レジスタおよびその他の機能が長年にわたって追加されています。
x86アセンブリの作業の最初のステップは、目標が何であるかを判断することです。たとえば、オペレーティングシステム内でコードを記述する場合は、スタンドアロンアセンブラを使用するか、Cなどの高級言語の組み込みインラインアセンブリ機能を使用するかをさらに決定する必要があります。オペレーティングシステムなしで「ベアメタル」をコード化したい場合は、選択したアセンブラをインストールし、フラッシュメモリ、ブート可能なイメージに変換するか、そうでなければ実行を開始する適切な場所。
多くのプラットフォームでよくサポートされている非常に一般的なアセンブラはNASM(Netwide Assembler)です。これはhttp://nasm.us/から入手できます。 NASMサイトでは、ご使用のプラットフォーム用の最新リリースビルドをダウンロードすることができます。
Windows
Windowsでは、32ビット版と64ビット版のどちらも利用できます。 NASMには、Windowsホスト上でアセンブラを自動的にインストールするのに便利なインストーラが付属しています。
Linux
あなたのLinuxのバージョンにはすでにNASMがインストールされているかもしれません。チェックするには、以下を実行します。
nasm -v
コマンドが見つからない場合は、インストールを実行する必要があります。最先端のNASM機能を必要としていない限り、Linuxディストリビューションに組み込まれているパッケージ管理ツールを使用してNASMをインストールするのが最善の方法です。たとえば、UbuntuなどのDebian系システムでは、コマンドプロンプトから次のコマンドを実行します。
sudo apt-get install nasm
RPMベースのシステムの場合は、次のように試してみてください。
sudo yum install nasm
Mac OS X
最近のバージョンのOS X(YosemiteとEl Capitanを含む)には、古いバージョンのNASMがプリインストールされています。たとえば、El Capitanにはバージョン0.98.40がインストールされています。これはほぼすべての通常の目的ではうまくいくはずですが、実際はかなり古くなっています。この記事では、NASMバージョン2.11がリリースされ、2.12には多数のリリース候補が用意されています。
上記のリンクからNASMソースコードを入手することができますが、ソースからインストールする必要がある場合を除いて、OS Xリリースディレクトリからバイナリパッケージをダウンロードして解凍する方がはるかに簡単です。
解凍したら、NASMのシステムインストールバージョンを上書きしないことを強くお勧めします。代わりに、/ usr / localにインストールすることができます:
$ sudo su
<user's password entered to become root>
# cd /usr/local/bin
# cp <path/to/unzipped/nasm/files/nasm> ./
# exit
この時点で、NASMは/usr/local/binにありますが、あなたのパスにはありません。プロファイルの最後に次の行を追加する必要があります。
$ echo 'export PATH=/usr/local/bin:$PATH' >> ~/.bash_profile
これにより、 /usr/local/binがパスに/usr/local/binれます。コマンドプロンプトでnasm -vを実行すると、適切な新しいバージョンが表示されます。
x86 LinuxのHello Worldの例
これは32ビットx86 Linux用NASMアセンブリの基本的なHello Worldプログラムで、システムコールを直接(libc関数呼び出しなし)使用しています。取り込むのは大変ですが、時間の経過とともに理解できるようになります。セミコロン( ; )で始まる行はコメントです。
低レベルのUnixシステムプログラミングを知らないのであれば、関数をasmで書くだけで、CまたはC ++プログラムから呼び出すことができます。次に、POSIXシステムコールAPIとそれを使用するためのABIを学ぶことなく、レジスタとメモリをどのように扱うかを学ぶことだけを心配できます。
これにより、stdioバッファをフラッシュするexit(3) libcラッパーではなく、 write(2)と_exit(2) 2つのシステムコールが生成されます。 (技術的には、 _exit()はsys_exitではなくsys_exit_groupを呼び出しますが、それはマルチスレッドプロセスでのみ重要です)システムコールに関する一般的な説明は、 syscalls(2)を参照してください。ラッパー関数。
要約すると、システムコールは適切なレジスタにargsを入れ、システムコール番号をeaxで指定し、 int 0x80命令を実行することによって行われます。 See also アセンブリ内のシステムコールの戻り値は何ですか? asm syscallインターフェースが主にCの構文でどのように文書化されているかの詳細については、
32ビットABIのシステムコール番号は/usr/include/i386-linux-gnu/asm/unistd_32.h ( /usr/include/x86_64-linux-gnu/asm/unistd_32.h内容と同じです)。
#include <sys/syscall.h>は最終的に正しいファイルを含むので、 echo '#include <sys/syscall.h>' | gcc -E - -dM | less実行することができますecho '#include <sys/syscall.h>' | gcc -E - -dM | lessマクロのdefを見ることがecho '#include <sys/syscall.h>' | gcc -E - -dM | less ( Cヘッダーのasmの定数の検索については、 この答えを参照してください)
section .text ; Executable code goes in the .text section
global _start ; The linker looks for this symbol to set the process entry point, so execution start here
;;;a name followed by a colon defines a symbol. The global _start directive modifies it so it's a global symbol, not just one that we can CALL or JMP to from inside the asm.
;;; note that _start isn't really a "function". You can't return from it, and the kernel passes argc, argv, and env differently than main() would expect.
_start:
;;; write(1, msg, len);
; Start by moving the arguments into registers, where the kernel will look for them
mov edx,len ; 3rd arg goes in edx: buffer length
mov ecx,msg ; 2nd arg goes in ecx: pointer to the buffer
;Set output to stdout (goes to your terminal, or wherever you redirect or pipe)
mov ebx,1 ; 1st arg goes in ebx: Unix file descriptor. 1 = stdout, which is normally connected to the terminal.
mov eax,4 ; system call number (from SYS_write / __NR_write from unistd_32.h).
int 0x80 ; generate an interrupt, activating the kernel's system-call handling code. 64-bit code uses a different instruction, different registers, and different call numbers.
;; eax = return value, all other registers unchanged.
;;;Second, exit the process. There's nothing to return to, so we can't use a ret instruction (like we could if this was main() or any function with a caller)
;;; If we don't exit, execution continues into whatever bytes are next in the memory page,
;;; typically leading to a segmentation fault because the padding 00 00 decodes to add [eax],al.
;;; _exit(0);
xor ebx,ebx ; first arg = exit status = 0. (will be truncated to 8 bits). Zeroing registers is a special case on x86, and mov ebx,0 would be less efficient.
;; leaving out the zeroing of ebx would mean we exit(1), i.e. with an error status, since ebx still holds 1 from earlier.
mov eax,1 ; put __NR_exit into eax
int 0x80 ;Execute the Linux function
section .rodata ; Section for read-only constants
;; msg is a label, and in this context doesn't need to be msg:. It could be on a separate line.
;; db = Data Bytes: assemble some literal bytes into the output file.
msg db 'Hello, world!',0xa ; ASCII string constant plus a newline (0x10)
;; No terminating zero byte is needed, because we're using write(), which takes a buffer + length instead of an implicit-length string.
;; To make this a C string that we could pass to puts or strlen, we'd need a terminating 0 byte. (e.g. "...", 0x10, 0)
len equ $ - msg ; Define an assemble-time constant (not stored by itself in the output file, but will appear as an immediate operand in insns that use it)
; Calculate len = string length. subtract the address of the start
; of the string from the current position ($)
;; equivalently, we could have put a str_end: label after the string and done len equ str_end - str
Linuxでは、このファイルをHello.asmとして保存し、次のコマンドで32ビットの実行ファイルをビルドすることができます。
nasm -felf32 Hello.asm # assemble as 32-bit code. Add -Worphan-labels -g -Fdwarf for debug symbols and warnings
gcc -nostdlib -m32 Hello.o -o Hello # link without CRT startup code or libc, making a static binary
32ビットまたは64ビットの静的または動的にリンクされたLinux実行可能ファイルへのアセンブリの構築、NASM / YASM構文、またはGNU asディレクティブを使用したGNU AT&T構文の詳細については、 この回答を参照してください。 (要点:64ビットホストに32ビットコードをビルドする場合は、必ず-m32または同等のものを使用して-m32ないと、実行時に混乱する問題が発生します)。
straceを使って実行をトレースすると、システムコールを見ることができます:
$ strace ./Hello
execve("./Hello", ["./Hello"], [/* 72 vars */]) = 0
[ Process PID=4019 runs in 32 bit mode. ]
write(1, "Hello, world!\n", 14Hello, world!
) = 14
_exit(0) = ?
+++ exited with 0 +++
stderr上のトレースとstdout上の通常の出力はどちらも、ここではターミナルに送られているので、システムコールをwrite行に干渉します。気にする場合は、ファイルにリダイレクトまたはトレースしてください。これにより、syscallの戻り値が簡単に表示され、それらを出力するコードを追加する必要がなくなり、実際にはgdbのような通常のデバッガを使用するよりも簡単です。
このプログラムのx86-64バージョンは、同じレジスタを別のレジスタに入れて、同じシステムコールに同じ引数を渡して、非常に似ています。また、 int 0x80代わりにsyscall命令を使用しsyscall 。