Windows でのアセンブリで基本的なものを書きたかったのですが、NASM を使用していますが、何も機能しません。
WindowsでC関数の助けを借りずにhello worldを書いてコンパイルする方法は?
この例では、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
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
Cライブラリを使用しないThe Clueless Newbies Guide to Hello World in Nasmもあります。次に、コードは次のようになります。
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
、すべてのセグメント レジスタが互いに等しい状態でロードされるようにします (小さなメモリ モデル)。
幸運を。
これらは、Windows API 呼び出しを使用した Win32 および Win64 の例です。それらは NASM ではなく MASM 用ですが、それらを見てください。詳細については、この記事を参照してください。
これは、stdout に出力する代わりに 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 Windows が .NET ファイルの前に 28h バイトのスタック領域を確保する必要があるのはなぜcall
ですか これは、32 バイト (0x20) のシャドウ スペース、別名ホーム スペースであり、呼び出し規則で必要とされます。さらに、スタックを 16 ずつ再整列するためにさらに 8 バイトが必要ですcall
。(私たちmain
の呼び出し元 (CRT スタートアップ コード内) はそれを行いました。8 バイトの戻りアドレスは、RSP が関数へのエントリで 16 バイト境界から 8 バイト離れていることを意味します。)
関数はシャドウ スペースを使用して、スタック引数 (存在する場合) がある場所の隣にレジスタ引数をダンプできます。Asystem call
には、前述の 4 つのレジスタに加えて、r10 と r11 用のスペースも確保するために 30h (48 バイト) が必要です。syscall
しかし、DLL 呼び出しは、命令のラッパーであっても、単なる関数呼び出しです。
豆知識: 非 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
forstdcall
またはfastcall
whilecinvoke
は forcdecl
または variable argumentfastcall
です。アセンブラは、どちらを使用するかを認識しています。
出力を逆アセンブルして、invoke
展開方法を確認できます。
NASM'compiler と 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 を 1 回実行するか、MS C++ 2010 および mspdb100.dllを参照してください)。)。
いくつかの関数を呼び出さない限り、これは決して些細なことではありません。(そして、真剣に、printfの呼び出しとwin32 api関数の呼び出しの複雑さに実際の違いはありません。)
DOS int 21hでさえ、APIが異なっていても、実際には単なる関数呼び出しです。
ヘルプなしでそれを実行したい場合は、ビデオハードウェアと直接通信する必要があります。おそらく、「Helloworld」の文字のビットマップをフレームバッファーに書き込みます。それでも、ビデオカードはそれらのメモリ値をDisplayPort / HDMI / DVI/VGA信号に変換する作業を行っています。
実際、ハードウェアに至るまでのこれらのものはどれも、CよりもASMで興味深いものではないことに注意してください。「helloworld」プログラムは、関数呼び出しに要約されます。ASMの優れた点の1つは、必要なABIをかなり簡単に使用できることです。あなたはそのABIが何であるかを知る必要があるだけです。
NASM と Visual Studio のリンカー (link.exe) を anderstornvig の Hello World の例で使用する場合は、printf() 関数を含む C ランタイム ライブラリと手動でリンクする必要があります。
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
これが誰かに役立つことを願っています。