Windowsでのアセンブリの基本的なものを書きたかったのですが、NASMを使用していますが、何も動作しません。
WindowsでC関数を使用せずにHello Worldを作成してコンパイルする方法は?
Windowsでのアセンブリの基本的なものを書きたかったのですが、NASMを使用していますが、何も動作しません。
WindowsでC関数を使用せずにHello Worldを作成してコンパイルする方法は?
回答:
libc stdioの呼び出しprintf
、実装int main(){ return printf(message); }
; ----------------------------------------------------------------------------
; helloworld.asm
;
; This is a Win32 console program that writes "Hello, World" on one line and
; then exits. It needs to be linked with a C library.
; ----------------------------------------------------------------------------
global _main
extern _printf
section .text
_main:
push message
call _printf
add esp, 4
ret
message:
db 'Hello, World', 10, 0
次に実行します
nasm -fwin32 helloworld.asm
gcc helloworld.obj
a
ありますNASMでのHello Worldのクルーレス初心者ガイドは、 Cライブラリを使用せずに。その後、コードは次のようになります。
MS-DOSシステムコール付きの16ビットコード:DOSエミュレーターまたはNTVDMサポート付きの32ビットWindowsで動作します。x86-64カーネルはvm86モードを使用できないため、64ビットWindowsで「直接」(透過的に)実行することはできません。
org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'
これを.com
実行可能ファイルにビルドして、cs:100h
すべてのセグメントレジスタが互いに等しくなるようにロードします(小さなメモリモデル)。
幸運を。
この例は、C標準ライブラリにリンクせずに、Windows APIに直接アクセスする方法を示しています。
global _main
extern _GetStdHandle@4
extern _WriteFile@20
extern _ExitProcess@4
section .text
_main:
; DWORD bytes;
mov ebp, esp
sub esp, 4
; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
push -11
call _GetStdHandle@4
mov ebx, eax
; WriteFile( hstdOut, message, length(message), &bytes, 0);
push 0
lea eax, [ebp-4]
push eax
push (message_end - message)
push message
push ebx
call _WriteFile@20
; ExitProcess(0)
push 0
call _ExitProcess@4
; never here
hlt
message:
db 'Hello, World', 10
message_end:
コンパイルするには、NASMとLINK.EXE(Visual Studio Standard Editionから)が必要です
nasm -fwin32 hello.asm リンク/ subsystem:console / nodefaultlib / entry:main hello.obj
gcc hello.obj
これらは、Windows API呼び出しを使用したWin32およびWin64の例です。それらはNASMではなくMASM用ですが、それらを確認してください。詳細については、この記事をご覧ください。
これは、標準出力に出力する代わりにMessageBoxを使用します。
;---ASM Hello World Win32 MessageBox
.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
.data
title db 'Win32', 0
msg db 'Hello World', 0
.code
Main:
push 0 ; uType = MB_OK
push offset title ; LPCSTR lpCaption
push offset msg ; LPCSTR lpText
push 0 ; hWnd = HWND_DESKTOP
call MessageBoxA
push eax ; uExitCode = MessageBox(...)
call ExitProcess
End Main
;---ASM Hello World Win64 MessageBox
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
.data
title db 'Win64', 0
msg db 'Hello World!', 0
.code
main proc
sub rsp, 28h
mov rcx, 0 ; hWnd = HWND_DESKTOP
lea rdx, msg ; LPCSTR lpText
lea r8, title ; LPCSTR lpCaption
mov r9d, 0 ; uType = MB_OK
call MessageBoxA
add rsp, 28h
mov ecx, eax ; uExitCode = MessageBox(...)
call ExitProcess
main endp
End
MASMを使用してこれらをアセンブルおよびリンクするには、32ビット実行可能ファイルにこれを使用します。
ml.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main
または64ビット実行可能ファイルの場合:
ml64.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
なぜx64ウィンドウは28hバイトのスタックスペースを予約する必要があるのcall
ですか? これは、呼び出し規約で必要な32バイト(0x20)のシャドウスペース(ホームスペース)です。呼び出し規約は、RSPを必要とするためとに別の8バイト、16バイトを整列させることが、16でスタックを再合わせの前にcall
。(main
CRTスタートアップコードの)呼び出し元がそれを行いました。8バイトの戻りアドレスは、RSPが関数へのエントリ時に16バイト境界から8バイト離れていることを意味します。)
シャドウスペースは、スタック引数(存在する場合)がある場所の横にあるレジスタ引数をダンプするために関数で使用できます。あsystem call
は、前述の4つのレジスタに加えて、r10およびr11のスペースも予約ために30h(48バイト)を必要とします。ただし、DLL呼び出しは、syscall
命令のラッパーであっても、単なる関数呼び出しです。
おもしろい事実:Windows以外、つまりx86-64 System V呼び出し規約(Linuxなど)はシャドウスペースをまったく使用せず、最大6つの整数/ポインターレジスター引数、および XMMレジスターで最大8つのFP引数を使用します。 。
MASMのinvoke
ディレクティブ(呼び出し規約を知っている)を使用すると、1つのifdefを使用して、32ビットまたは64ビットとしてビルドできるこのバージョンを作成できます。
ifdef rax
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
else
.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
endif
.data
caption db 'WinAPI', 0
text db 'Hello World', 0
.code
main proc
invoke MessageBoxA, 0, offset text, offset caption, 0
invoke ExitProcess, eax
main endp
end
マクロバリアントはどちらも同じですが、この方法でアセンブリを学習することはできません。代わりに、Cスタイルのasmを学習します。invoke
はfor stdcall
またはfastcall
while cinvoke
はfor cdecl
またはvariable引数fastcall
です。アセンブラはどちらを使用するかを認識しています。
出力を逆アセンブルして、invoke
展開方法を確認できます。
title
ラベル名として使用すると、エラーが発生します。ただし、のようなラベル名として他のものを使用するとmytitle
、すべてが正常に動作します。
フラットアセンブラには、追加のリンカは必要ありません。これにより、アセンブラのプログラミングが非常に簡単になります。Linuxでも利用できます。
これはhello.asm
Fasmの例からです:
include 'win32ax.inc'
.code
start:
invoke MessageBox,HWND_DESKTOP,"Hi! I'm the example program!",invoke GetCommandLine,MB_OK
invoke ExitProcess,0
.end start
Fasmは実行可能ファイルを作成します。
> fasm hello.asm フラットアセンブラバージョン1.70.03(1048575キロバイトメモリ) 4パス、1536バイト。
そしてこれはIDAのプログラムです:
3つの呼び出しを確認できます:GetCommandLine
、MessageBox
およびExitProcess
。
NASMコンパイラとVisual Studioのリンカーを使用して.exeを取得するには、このコードは正常に機能します。
global WinMain
extern ExitProcess ; external functions in system libraries
extern MessageBoxA
section .data
title: db 'Win64', 0
msg: db 'Hello world!', 0
section .text
WinMain:
sub rsp, 28h
mov rcx, 0 ; hWnd = HWND_DESKTOP
lea rdx,[msg] ; LPCSTR lpText
lea r8,[title] ; LPCSTR lpCaption
mov r9d, 0 ; uType = MB_OK
call MessageBoxA
add rsp, 28h
mov ecx,eax
call ExitProcess
hlt ; never here
このコードが「test64.asm」などに保存されている場合、コンパイルするには:
nasm -f win64 test64.asm
"test64.obj"を生成し、コマンドプロンプトからリンクするには:
path_to_link\link.exe test64.obj /subsystem:windows /entry:WinMain /libpath:path_to_libs /nodefaultlib kernel32.lib user32.lib /largeaddressaware:no
ここで、path_to_linkはC:\ Program Files(x86)\ Microsoft Visual Studio 10.0 \ VC \ binまたはマシンのlink.exeプログラムであり、 path_to_libsはC:\ Program Files(x86)\ Windows Kits \ 8.1 \です。 Lib \ winv6.3 \ um \ x64またはライブラリはどこでも(この場合、kernel32.libとuser32.libは同じ場所にあります。それ以外の場合は、必要なパスごとに1つのオプションを使用します)、/ largeaddressaware:noオプションはリンカによるアドレスの不満を回避するために必要です(この場合、user32.libの場合)また、ここで行うように、コマンドプロンプトからVisualのリンカーを呼び出す場合は、事前に環境を設定する必要があります(vcvarsall.batを実行するか、またはを参照してください MS C ++ 2010とmspdb100.dllを)。
default rel
ファイルの先頭で使用することを強くお勧めします。これらのアドレッシングモード([msg]
および[title]
)は、32ビット絶対ではなくRIP相対アドレッシングを使用します。
あなたが呼び出す場合を除き、いくつかの機能を、これはすべての些細ではありません。(そして、真剣に、printfを呼び出すこととwin32 api関数を呼び出すことの間の複雑さに実際の違いはありません。)
たとえ異なるAPIであっても、DOS int 21hは実際には単なる関数呼び出しです。
助けなしでやりたい場合は、ビデオハードウェアに直接話しかける必要があります。「Hello world」の文字のビットマップをフレームバッファーに書き込む可能性があります。それでも、ビデオカードはこれらのメモリ値をVGA / DVI信号に変換する作業を行っています。
実際には、ハードウェアに至るまでのこれらのものはどれも、ASMではCよりも興味深いものではないことに注意してください。「hello world」プログラムは関数呼び出しに要約されます。ASMの優れた点の1つは、かなり簡単にしたい任意のABIを使用できることです。あなたはそのABIが何であるかを知る必要があるだけです。
fasmはリンカを使用しないため、fasmを使用した例が最も良い例です。リンカを使用すると、Windowsプログラミングの複雑さが、不透明な別の複雑な層によって隠されます。GUIウィンドウに書き込むプログラムに満足している場合は、fasmのサンプルディレクトリにその例があります。
コンソールプログラムが必要な場合は、標準入力と標準出力のリダイレクトも可能です。GUIを使用せず、コンソールで厳密に動作する(非常に重要なHelasの)簡単なサンプルプログラムがあり、それ自体がfasmです。これは本質的に間引くことができます。(私は別の非GUIの例である4番目のコンパイラーを作成しましたが、それも簡単ではありません)。
このようなプログラムには、適切な実行可能ヘッダーを生成する次のコマンドがあり、通常はリンカーによって実行されます。
FORMAT PE CONSOLE
「.idata」というセクションには、起動時にウィンドウが関数の名前をランタイムアドレスに結び付けるのに役立つテーブルが含まれています。また、WindowsオペレーティングシステムであるKERNEL.DLLへの参照も含まれています。
section '.idata' import data readable writeable
dd 0,0,0,rva kernel_name,rva kernel_table
dd 0,0,0,0,0
kernel_table:
_ExitProcess@4 DD rva _ExitProcess
CreateFile DD rva _CreateFileA
...
...
_GetStdHandle@4 DD rva _GetStdHandle
DD 0
テーブル形式はウィンドウによって課せられ、プログラムの起動時にシステムファイルで検索される名前が含まれます。FASMは、rvaキーワードの背後にある複雑さの一部を隠しています。したがって、_ExitProcess @ 4はfasmラベルであり、_exitProcessはWindowsによって検索される文字列です。
プログラムはセクション「.text」にあります。そのセクションが読み取り可能で書き込み可能および実行可能であると宣言した場合、それは追加する必要がある唯一のセクションです。
section '.text' code executable readable writable
.idataセクションで宣言したすべての機能を呼び出すことができます。コンソールプログラムの場合、標準入力および標準出力のファイル記述子を見つけるために_GetStdHandleが必要です(fasmがインクルードファイルwin32a.incで見つけるSTD_INPUT_HANDLEのようなシンボリック名を使用)。ファイル記述子を取得したら、WriteFileとReadFileを実行できます。すべての関数はkernel32のドキュメントで説明されています。あなたはおそらくそれを知っているか、アセンブラープログラミングを試そうとはしません。
要約すると、Windows OSに対応するASCII名のテーブルがあります。起動時に、これは呼び出し可能なアドレスのテーブルに変換され、プログラムで使用します。