Wie schreibe ich Hallo Welt in Assembler unter Windows?

89

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?

Feiroox
quelle
2
Schauen Sie sich auch Steve Gibsons Starterkit für die Montage von Small Is Beautiful- Fenstern an.
Jeremy
Die Nichtverwendung von C-Bibliotheken ist eine etwas seltsame Einschränkung. Man muss eine Bibliothek innerhalb des MS-Windows-Betriebssystems aufrufen. wahrscheinlich kernel32.dll. Ob Microsoft dies in c oder Pascal geschrieben hat, scheint irrelevant. Bedeutet dies, dass nur vom Betriebssystem bereitgestellte Funktionen aufgerufen werden können, was in einem Unix-System als Systemaufrufe bezeichnet werden würde?
Albert van der Horst
Bei C-Bibliotheken geht er vermutlich ohne C-Laufzeitbibliotheken aus, wie sie mit GCC oder MSVC geliefert werden. Natürlich muss er oder sie einige Standard-Windows-DLLs wie kernel32.dll verwenden.
Rudy Velthuis
2
Die Unterscheidung zwischen kernel32.dll und einer gcc-Laufzeitbibliothek erfolgt nicht im Format (beide sind dll) und nicht in der Sprache (beide sind wahrscheinlich c, aber das ist verborgen.) Der Unterschied besteht zwischen vom Betriebssystem bereitgestellten oder nicht.
Albert van der Horst
Ich habe danach gesucht, aber
ich

Antworten:

37

NASM-Beispiele .

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 .comausführbare Datei ein, damit cs:100halle Segmentregister gleich sind (winziges Speichermodell).

Viel Glück.

Peter Cordes
quelle
25
Die Frage erwähnt ausdrücklich "ohne Verwendung von C-Bibliotheken"
Mehrdad Afshari
25
Falsch. Die C-Bibliothek selbst kann es offensichtlich, also ist es möglich. Tatsächlich ist es nur etwas schwieriger. Sie müssen nur WriteConsole () mit den richtigen 5 Parametern aufrufen.
MSalters
12
Obwohl das zweite Beispiel keine C-Bibliotheksfunktion aufruft, ist es auch kein Windows-Programm. Die virtuelle DOS-Maschine wird ausgelöst, um sie auszuführen.
Rômulo Ceccon
7
@Alex Hart, sein zweites Beispiel ist für DOS, nicht für Windows. Unter DOS beginnen die Programme im winzigen Modus (.COM-Dateien, unter 64 KB Gesamtcode + Daten + Stapel) bei 0x100h, da die ersten 256 Bytes im Segment von der PSP belegt werden (Befehlszeilenargumente usw.). Siehe diesen Link: en.wikipedia.org/wiki/Program_Segment_Prefix
zvolkov
7
Dies ist nicht das, wonach gefragt wurde. Das erste Beispiel verwendet die C-Bibliothek und das zweite ist MS-DOS, nicht Windows.
Paulo Pinto
127

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 
caffiend
quelle
21
Sie müssen wahrscheinlich die kernel32.lib einbinden, um dies zu verknüpfen (ich habe es getan). Link / Subsystem: Konsole / Nodefaultlib / Eintrag: main hello.obj kernel32.lib
Zach Burlingame
5
Wie verknüpfe ich das Objekt mit ld.exe von MinGW?
DarrenVortex
4
@ DarrenVortexgcc hello.obj
Towry
4
Würde dies auch mit kostenlosen Linkern wie Alink von sourceforge.net/projects/alink oder GoLink von godevtool.com/#linker funktionieren ? Ich möchte Visual Studio nicht nur dafür installieren?
jj_
21

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.

Win32 MASM

;---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

Win64 MASM

;---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. ( mainDer 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 callbenö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 syscallAnweisungen 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- invokeDirektive (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. invokeist für stdcalloder fastcallwährend cinvokeist für cdecloder variables Argument fastcall. Der Assembler weiß, welche er verwenden soll.

Sie können die Ausgabe zerlegen, um zu sehen, wie invokeerweitert.

PhiS
quelle
1
+1 für deine Antwort. Können Sie bitte auch Assembler-Code für Windows unter ARM (WOA) hinzufügen?
Annie
1
Warum benötigt rsp 0x28 Bytes und nicht 0x20? Alle Referenzen auf der Aufrufkonvention besagen, dass es 32 sein sollte, aber es scheint in der Praxis 40 zu erfordern.
Douggard
In Ihrem 32-Bit-Nachrichtenfeldcode treten aus irgendeinem Grund titleFehler auf, wenn ich ihn als Beschriftungsnamen verwende. Wenn ich jedoch etwas anderes als Markennamen verwende mytitle, funktioniert alles einwandfrei.
user3405291
Wie geht das ohne?
Bluejayke
13

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.asmaus 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 :

Geben Sie hier die Bildbeschreibung ein

Sie können die drei Anrufe sehen: GetCommandLine, MessageBoxund ExitProcess.

ceving
quelle
Dies verwendet ein Include und eine GUI. Wie machen wir das nur mit CMD ohne Includes?
Bluejayke
Haben Sie versucht, das Handbuch zu lesen? flatassembler.net/docs.php?article=manual#2.4.2
13.
Können Sie mich auf einen Abschnitt verweisen, der ohne DLLs in die Konsole schreibt?
Bluejayke
11

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)).

Rarias
quelle
2
Ich empfehle dringend, default reloben in Ihrer Datei zu verwenden, damit diese Adressierungsmodi ( [msg]und [title]) die RIP-relative Adressierung anstelle der absoluten 32-Bit-Adresse verwenden.
Peter Cordes
5

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.

Kapitän Segfault
quelle
Dies ist ein ausgezeichneter Punkt - ASM und C basieren beide auf einer vom Betriebssystem bereitgestellten Funktion (_WriteFile in Windows). Wo ist also die Magie? Es befindet sich im Gerätetreibercode für die Grafikkarte.
Assad Ebrahim
2
Dies ist durchaus nebensächlich. Das Poster fragt ein Assembler-Programm, das "unter Windows" ausgeführt wird. Das bedeutet, dass Windows-Funktionen verwendet werden können (z. B. kernel32.dll), jedoch keine anderen Funktionen wie libc unter Cygwin. Zum lauten Schreien steht auf dem Plakat ausdrücklich keine C-Bibliothek.
Albert van der Horst
5

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.

Albert van der Horst
quelle
FASM verwendet möglicherweise keinen Linker, muss jedoch eine PE-Datei zusammenstellen. Das bedeutet, dass es nicht nur Code zusammensetzt, sondern auch einen Job übernimmt, den normalerweise ein Linker ausführen würde, und als solche ist es meiner bescheidenen Meinung nach irreführend, die Abwesenheit eines Linkers als "versteckte Komplexität" zu bezeichnen, ganz im Gegenteil - Die Aufgabe eines Assemblers besteht darin, ein Programm zusammenzustellen, es jedoch dem Linker zu überlassen, das Programm in ein Programmabbild einzubetten, das von vielen Dingen abhängen kann. Daher finde ich die Trennung zwischen einem Linker und einem Assembler eine gute Sache, über die Sie offenbar nicht einig sind.
Amn
@amn Denk so darüber nach. Wenn Sie einen Linker verwenden, um das obige Programm zu erstellen, erhalten Sie dann mehr Einblick in die Funktionsweise des Programms oder woraus besteht es? Wenn ich mir die Fasm-Quelle anschaue, kenne ich die vollständige Struktur des Programms.
Albert van der Horst
Gutes Argument. Auf der anderen Seite hat die Trennung der Verknüpfung von allem anderen auch Vorteile. Normalerweise haben Sie Zugriff auf eine Objektdatei (was einen großen Beitrag dazu leistet, dass Sie unabhängig vom Format der Programmbilddatei auch die Struktur eines Programms überprüfen können). Sie können einen anderen Linker Ihrer Wahl mit verschiedenen Optionen aufrufen. Es geht um Wiederverwendbarkeit und Zusammensetzbarkeit. In diesem Sinne tut FASM alles, weil es "bequem" ist, diese Prinzipien zu brechen. Ich bin nicht grundsätzlich dagegen - ich sehe ihre Rechtfertigung dafür -, aber ich brauche es nicht.
Amn
Erhalten Sie Fehler für illegale isntruction in der obersten Zeile in fasm 64-Bit-Fenstern
bluejayke
3

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.

tboerman
quelle