Ich wollte etwas Grundlegendes in Assembly unter Windows schreiben, ich verwende NASM, aber ich kann nichts zum Laufen bringen.
Wie schreibe und kompiliere ich hallo world ohne die Hilfe von C-Funktionen unter Windows?
Ich wollte etwas Grundlegendes in Assembly unter Windows schreiben, ich verwende NASM, aber ich kann nichts zum Laufen bringen.
Wie schreibe und kompiliere ich hallo world ohne die Hilfe von C-Funktionen unter Windows?
Antworten:
Libc stdio aufrufen printf
, implementierenint 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
Dann renne
nasm -fwin32 helloworld.asm
gcc helloworld.obj
a
Es gibt auch den Leitfaden für ahnungslose Neulinge zu Hello World in Nasm ohne Verwendung einer C-Bibliothek. Dann würde der Code so aussehen.
16-Bit-Code mit MS-DOS-Systemaufrufen: Funktioniert in DOS-Emulatoren oder in 32-Bit-Windows mit NTVDM-Unterstützung . Kann unter 64-Bit-Windows nicht "direkt" (transparent) ausgeführt werden, da ein x86-64-Kernel den VM86-Modus nicht verwenden kann.
org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'
Bauen Sie dies in eine .com
ausführbare Datei ein, damit cs:100h
alle Segmentregister gleich sind (winziges Speichermodell).
Viel Glück.
Dieses Beispiel zeigt, wie Sie direkt zur Windows-API wechseln und keine Verknüpfung in der C-Standardbibliothek herstellen.
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:
Zum Kompilieren benötigen Sie NASM und LINK.EXE (von Visual Studio Standard Edition).
nasm -fwin32 hallo.asm link / subsystem: console / nodefaultlib / entry: main hello.obj
gcc hello.obj
Dies sind Win32- und Win64-Beispiele mit Windows-API-Aufrufen. Sie sind eher für MASM als für NASM, aber sehen Sie sie sich an. Weitere Details finden Sie in diesem Artikel.
Dies verwendet MessageBox, anstatt auf stdout zu drucken.
;---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
Verwenden Sie dies für die ausführbare 32-Bit-Datei, um diese mithilfe von MASM zusammenzustellen und zu verknüpfen:
ml.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main
oder dies für eine ausführbare 64-Bit-Datei:
ml64.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
Warum muss x64 Windows vor a 28 Stunden Byte Stapelspeicher reservieren call
? Das sind 32 Byte (0x20) Schattenraum, auch bekannt als Heimatraum, wie es die aufrufende Konvention vorschreibt. Und weitere 8 Bytes, um den Stapel um 16 neu auszurichten, da die Aufrufkonvention erfordert, dass RSP 16 Byte vor a ausgerichtet ist call
. ( main
Der Anrufer von Our (im CRT-Startcode) hat dies getan. Die 8-Byte-Rücksprungadresse bedeutet, dass RSP beim Eintritt in eine Funktion 8 Byte von einer 16-Byte-Grenze entfernt ist.)
Der Schattenraum kann von einer Funktion verwendet werden, um ihre Registerargumente neben den Stapelargumenten (falls vorhanden) abzulegen. A system call
benötigt 30 Stunden (48 Bytes), um zusätzlich zu den zuvor erwähnten 4 Registern auch Platz für r10 und r11 zu reservieren. DLL-Aufrufe sind jedoch nur Funktionsaufrufe, selbst wenn sie syscall
Anweisungen umschließen .
Unterhaltsame Tatsache: Nicht-Windows, dh die x86-64-System V-Aufrufkonvention (z. B. unter Linux) verwendet überhaupt keinen Schattenraum und verwendet bis zu 6 Ganzzahl- / Zeigerregister-Argumente und bis zu 8 FP-Argumente in XMM-Registern .
Mit der MASM- invoke
Direktive (die die Aufrufkonvention kennt) können Sie ein ifdef verwenden, um eine Version davon zu erstellen, die als 32-Bit oder 64-Bit erstellt werden kann.
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
Die Makrovariante ist für beide gleich, aber Sie werden das Zusammenbauen auf diese Weise nicht lernen. Sie lernen stattdessen Asm im C-Stil. invoke
ist für stdcall
oder fastcall
während cinvoke
ist für cdecl
oder variables Argument fastcall
. Der Assembler weiß, welche er verwenden soll.
Sie können die Ausgabe zerlegen, um zu sehen, wie invoke
erweitert.
title
Fehler auf, wenn ich ihn als Beschriftungsnamen verwende. Wenn ich jedoch etwas anderes als Markennamen verwende mytitle
, funktioniert alles einwandfrei.
Flat Assembler benötigt keinen zusätzlichen Linker. Dies macht die Assembler-Programmierung ziemlich einfach. Es ist auch für Linux verfügbar.
Dies ist hello.asm
aus den Fasm-Beispielen:
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 erstellt eine ausführbare Datei:
> fasm hallo.asm Flat Assembler Version 1.70.03 (1048575 Kilobyte Speicher) 4 Durchgänge, 1536 Bytes.
Und das ist das Programm in IDA :
Sie können die drei Anrufe sehen: GetCommandLine
, MessageBox
und ExitProcess
.
Um eine .exe mit NASM'compiler und dem Linker von Visual Studio zu erhalten, funktioniert dieser Code einwandfrei:
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
Wenn dieser Code zB auf "test64.asm" gespeichert ist, müssen Sie Folgendes kompilieren:
nasm -f win64 test64.asm
Erzeugt "test64.obj". Zum Verknüpfen über die Eingabeaufforderung:
path_to_link\link.exe test64.obj /subsystem:windows /entry:WinMain /libpath:path_to_libs /nodefaultlib kernel32.lib user32.lib /largeaddressaware:no
wo path_to_link könnte C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ ist oder wo auch immer Ihr link.exe Programm in Ihrer Maschine, path_to_libs könnte C: \ Program Files (x86) \ Windows - Kits \ 8.1 \ Lib \ winv6.3 \ um \ x64 oder wo immer sich Ihre Bibliotheken befinden (in diesem Fall befinden sich sowohl kernel32.lib als auch user32.lib an derselben Stelle, andernfalls verwenden Sie eine Option für jeden Pfad, den Sie benötigen) und / largeaddressaware: Keine Option notwendig, um zu vermeiden, dass Linker sich zu lange über Adressen beschweren (in diesem Fall für user32.lib). Wenn hier der Linker von Visual über die Eingabeaufforderung aufgerufen wird, muss die Umgebung zuvor eingerichtet werden (einmal vcvarsall.bat ausführen und / oder MS C ++ 2010 und mspdb100.dll anzeigen)).
default rel
oben in Ihrer Datei zu verwenden, damit diese Adressierungsmodi ( [msg]
und [title]
) die RIP-relative Adressierung anstelle der absoluten 32-Bit-Adresse verwenden.
Es sei denn , Sie rufen eine Funktion ist dies keineswegs trivial. (Und im Ernst, es gibt keinen wirklichen Unterschied in der Komplexität zwischen dem Aufrufen von printf und dem Aufrufen einer win32-API-Funktion.)
Selbst DOS int 21h ist eigentlich nur ein Funktionsaufruf, auch wenn es sich um eine andere API handelt.
Wenn Sie dies ohne Hilfe tun möchten, müssen Sie direkt mit Ihrer Videohardware sprechen und wahrscheinlich Bitmaps der Buchstaben von "Hello world" in einen Framebuffer schreiben. Selbst dann übersetzt die Grafikkarte diese Speicherwerte in VGA / DVI-Signale.
Beachten Sie, dass in ASM keines dieser Dinge bis hin zur Hardware interessanter ist als in C. Ein "Hallo Welt" -Programm läuft auf einen Funktionsaufruf hinaus. Eine schöne Sache bei ASM ist, dass Sie jedes gewünschte ABI ziemlich einfach verwenden können. Sie müssen nur wissen, was dieser ABI ist.
Die besten Beispiele sind solche mit fasm, da fasm keinen Linker verwendet, wodurch die Komplexität der Windows-Programmierung durch eine andere undurchsichtige Komplexitätsebene verborgen wird. Wenn Sie mit einem Programm zufrieden sind, das in ein GUI-Fenster schreibt, finden Sie ein Beispiel dafür im Beispielverzeichnis von fasm.
Wenn Sie ein Konsolenprogramm wünschen, können Sie auch Standard-In und Standard-Out umleiten, was ebenfalls möglich ist. Es gibt ein (helas höchst nicht triviales) Beispielprogramm, das keine GUI verwendet und streng mit der Konsole arbeitet, das ist fasm selbst. Dies kann auf das Wesentliche ausgedünnt werden. (Ich habe einen vierten Compiler geschrieben, der ein weiteres Nicht-GUI-Beispiel ist, aber auch nicht trivial).
Ein solches Programm verfügt über den folgenden Befehl, um einen ordnungsgemäßen ausführbaren Header zu generieren, der normalerweise von einem Linker ausgeführt wird.
FORMAT PE CONSOLE
Ein Abschnitt mit dem Namen '.idata' enthält eine Tabelle, mit deren Hilfe Windows beim Start Namen von Funktionen mit den Laufzeitadressen koppeln kann. Es enthält auch einen Verweis auf KERNEL.DLL, das Windows-Betriebssystem.
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
Das Tabellenformat wird von Windows vorgegeben und enthält Namen, die beim Starten des Programms in Systemdateien nachgeschlagen werden. FASM verbirgt einen Teil der Komplexität hinter dem Schlüsselwort rva. _ExitProcess @ 4 ist also ein fasm-Label und _exitProcess ist eine Zeichenfolge, die von Windows nachgeschlagen wird.
Ihr Programm befindet sich im Abschnitt '.text'. Wenn Sie diesen Abschnitt als lesbar, beschreibbar und ausführbar deklarieren, ist dies der einzige Abschnitt, den Sie hinzufügen müssen.
section '.text' code executable readable writable
Sie können alle Einrichtungen aufrufen, die Sie im Abschnitt .idata deklariert haben. Für ein Konsolenprogramm benötigen Sie _GetStdHandle, um die Dateideskriptoren für Standard-In und Standardout zu finden (unter Verwendung symbolischer Namen wie STD_INPUT_HANDLE, die fasm in der Include-Datei win32a.inc findet). Sobald Sie die Dateideskriptoren haben, können Sie WriteFile und ReadFile ausführen. Alle Funktionen sind in der Kernel32-Dokumentation beschrieben. Sie sind sich dessen wahrscheinlich bewusst, oder Sie würden die Assembler-Programmierung nicht ausprobieren.
Zusammenfassend: Es gibt eine Tabelle mit ASCI-Namen, die mit dem Windows-Betriebssystem gekoppelt sind. Während des Startvorgangs wird dies in eine Tabelle mit aufrufbaren Adressen umgewandelt, die Sie in Ihrem Programm verwenden.
Wenn Sie den Linker von NASM und Visual Studio (link.exe) mit dem Hello World-Beispiel von anderstornvig verwenden möchten, müssen Sie manuell eine Verknüpfung mit der C Runtime Libary herstellen, die die Funktion printf () enthält.
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
Hoffe das hilft jemandem.