Comment écrire Hello World en assembleur sous Windows?

89

Je voulais écrire quelque chose de basique en assemblage sous Windows, j'utilise NASM, mais je ne peux rien faire fonctionner.

Comment écrire et compiler Hello World sans l'aide des fonctions C sous Windows?

Feiroox
la source
3
Consultez également le kit de démarrage de l'assemblage de fenêtres Small Is Beautiful de Steve Gibson .
Jeremy
Ne pas utiliser de c-bibliothèques est une contrainte quelque peu étrange. Il faut appeler une bibliothèque dans le système d'exploitation MS-Windows. probablement kernel32.dll. Que Microsoft ait écrit cela en c ou en Pascal ne semble pas pertinent. Cela signifie-t-il que seules les fonctions fournies par le système d'exploitation peuvent être appelées, qu'est-ce que dans un système de type Unix serait appelé appels système?
Albert van der Horst
Avec les bibliothèques C, je suppose qu'il ou elle veut dire sans utiliser de bibliothèques d'exécution C comme celles fournies avec GCC ou MSVC. Bien sûr, il devra utiliser des DLL Windows standard, comme kernel32.dll.
Rudy Velthuis
2
La distinction entre kernel32.dll et une bibliothèque d'exécution gcc n'est pas dans le format (les deux sont dll) et pas dans le langage (les deux sont probablement c, mais cela est caché.) La différence est entre le système d'exploitation ou non.
Albert van der Horst
J'étais à la recherche de cela aussi lol ne pouvait rien trouver avec fasm sans inclut
bluejayke

Réponses:

37

Exemples de MSNA .

Appel de libc stdio printf, implémentationint 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

Puis cours

nasm -fwin32 helloworld.asm
gcc helloworld.obj
a

Il existe également le guide des débutants Clueless pour Hello World dans Nasm sans l'utilisation d'une bibliothèque C. Ensuite, le code ressemblerait à ceci.

Code 16 bits avec appels système MS-DOS: fonctionne dans les émulateurs DOS ou dans Windows 32 bits avec prise en charge NTVDM . Ne peut pas être exécuté "directement" (de manière transparente) sous n'importe quel Windows 64 bits, car un noyau x86-64 ne peut pas utiliser le mode vm86.

org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'

Construisez-le dans un .comexécutable pour qu'il soit chargé cs:100havec tous les registres de segment égaux les uns aux autres (modèle de mémoire minuscule).

Bonne chance.

Peter Cordes
la source
26
La question mentionne explicitement "sans utiliser les bibliothèques C"
Mehrdad Afshari
25
Faux. La bibliothèque C elle-même le peut évidemment, c'est donc possible. Ce n'est qu'un peu plus difficile, en fait. Il vous suffit d'appeler WriteConsole () avec les 5 bons paramètres.
MSalters
12
Bien que le deuxième exemple n'appelle aucune fonction de bibliothèque C, ce n'est pas non plus un programme Windows. La machine DOS virtuelle sera déclenchée pour l'exécuter.
Rômulo Ceccon
7
@Alex Hart, son deuxième exemple est pour DOS, pas pour Windows. Sous DOS, les programmes en mode minuscule (fichiers .COM, sous 64Kb de code total + données + pile) démarrent à 0x100h car les 256 premiers octets du segment sont pris par la PSP (args de ligne de commande, etc.). Voir ce lien: en.wikipedia.org/wiki/Program_Segment_Prefix
zvolkov
7
Ce n'est pas ce qui a été demandé. Le premier exemple utilise la bibliothèque C et le second est MS-DOS, pas Windows.
Paulo Pinto
128

Cet exemple montre comment accéder directement à l'API Windows sans créer de lien dans la bibliothèque C Standard.

    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:

Pour compiler, vous aurez besoin de NASM et LINK.EXE (de Visual studio Standard Edition)

   nasm -fwin32 bonjour.asm
   lien / sous-système: console / nodefaultlib / entrée: main hello.obj 
caffiend
la source
21
vous devrez probablement inclure le kernel32.lib pour lier ceci (je l'ai fait). lien / sous-système: console / nodefaultlib / entrée: main hello.obj kernel32.lib
Zach Burlingame
5
Comment lier l'obj avec ld.exe depuis MinGW?
DarrenVortex
4
@DarrenVortexgcc hello.obj
Towry
4
Cela fonctionnerait-il également en utilisant des liens gratuits comme Alink de sourceforge.net/projects/alink ou GoLink de godevtool.com/#linker ? Je ne veux pas installer Visual Studio uniquement pour ça?
jj_
21

Ce sont des exemples Win32 et Win64 utilisant des appels d'API Windows. Ils sont pour MASM plutôt que NASM, mais regardez-les. Vous pouvez trouver plus de détails dans cet article.

Cela utilise MessageBox au lieu d'imprimer sur stdout.

MASM Win32

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

MASM Win64

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

Pour les assembler et les lier à l'aide de MASM, utilisez ceci pour un exécutable 32 bits:

ml.exe [filename] /link /subsystem:windows 
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main

ou ceci pour un exécutable 64 bits:

ml64.exe [filename] /link /subsystem:windows 
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main

Pourquoi Windows x64 doit-il réserver 28h octets d'espace de pile avant un call? Cela représente 32 octets (0x20) d'espace fantôme, c'est-à-dire l'espace d'accueil, comme l'exige la convention d'appel. Et 8 octets supplémentaires pour réaligner la pile de 16, car la convention d'appel exige que RSP soit aligné sur 16 octets avant un call. ( mainL'appelant de notre (dans le code de démarrage CRT) l'a fait. L'adresse de retour de 8 octets signifie que RSP est à 8 octets d'une limite de 16 octets à l'entrée d'une fonction.)

L'espace fantôme peut être utilisé par une fonction pour vider ses arguments de registre à côté de l'endroit où se trouveraient les arguments de la pile (le cas échéant). A system callnécessite 30h (48 octets) pour réserver également de l'espace pour r10 et r11 en plus des 4 registres mentionnés précédemment. Mais les appels DLL ne sont que des appels de fonction, même s'ils enveloppent les syscallinstructions.

Fait amusant: non-Windows, c'est-à-dire que la convention d'appel x86-64 System V (par exemple sous Linux) n'utilise pas du tout d'espace fantôme, et utilise jusqu'à 6 arguments de registre entier / pointeur, et jusqu'à 8 arguments FP dans les registres XMM .


En utilisant la invokedirective MASM (qui connaît la convention d'appel), vous pouvez utiliser un ifdef pour en faire une version qui peut être construite en 32 bits ou 64 bits.

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

La variante de macro est la même pour les deux, mais vous n'apprendrez pas l'assemblage de cette façon. Vous apprendrez plutôt l'asm de style C. invokeest pour stdcallou fastcallwhile cinvokeest pour cdeclou argument variable fastcall. L'assembleur sait lequel utiliser.

Vous pouvez désassembler la sortie pour voir son invokeextension.

PhiS
la source
1
+1 pour votre réponse. Pouvez-vous également ajouter un code d'assemblage pour Windows sur ARM (WOA)?
Annie
1
Pourquoi rsp nécessite-t-il 0x28 octets et non 0x20? Toutes les références sur la convention d'appel indiquent qu'il devrait être 32 mais il semble en exiger 40 en pratique.
douggard
Dans votre code de boîte de message 32 bits, pour une raison quelconque lorsque j'utilise titlecomme nom d'étiquette, je rencontre des erreurs. Cependant, lorsque j'utilise autre chose comme nom d'étiquette mytitle, tout fonctionne bien.
user3405291
comment le faire sans inclure?
bluejayke
13

Flat Assembler n'a pas besoin d'un linker supplémentaire. Cela rend la programmation de l'assembleur assez facile. Il est également disponible pour Linux.

Ceci est hello.asmtiré des exemples 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 crée un exécutable:

> fasme bonjour.asm
assembleur plat version 1.70.03 (mémoire 1048575 kilo-octets)
4 passes, 1536 octets.

Et voici le programme de l' IDA :

entrez la description de l'image ici

Vous pouvez voir les trois appels: GetCommandLine, MessageBoxet ExitProcess.

plafond
la source
cela utilise un include et une interface graphique comment pouvons-nous le faire juste à CMD sans inclure du tout?
bluejayke
Vous avez essayé de lire le manuel? flatassembler.net/docs.php?article=manual#2.4.2
ceving
pouvez-vous me diriger vers une section qui écrit sur la console sans aucune DLL?
bluejayke
11

Pour obtenir un .exe avec le compilateur NASM et l'éditeur de liens de Visual Studio, ce code fonctionne correctement:

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

Si ce code est enregistré sur par exemple "test64.asm", alors pour compiler:

nasm -f win64 test64.asm

Produit "test64.obj" Puis pour créer un lien à partir de l'invite de commande:

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 pourrait être C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ bin ou où se trouve votre programme link.exe sur votre ordinateur , path_to_libs pourrait être C: \ Program Files (x86) \ Windows Kits \ 8.1 \ Lib \ winv6.3 \ um \ x64 ou où se trouvent vos bibliothèques (dans ce cas, kernel32.lib et user32.lib sont au même endroit, sinon utilisez une option pour chaque chemin dont vous avez besoin) et / largeaddressaware: aucune option n'est nécessaire pour éviter de se plaindre des adresses trop longues (pour user32.lib dans ce cas). De plus, comme cela est fait ici, si l'éditeur de liens de Visual est appelé à partir de l'invite de commande, il est nécessaire de configurer l'environnement précédemment (exécutez une fois vcvarsall.bat et / ou voir MS C ++ 2010 et mspdb100.dll).

rarias
la source
2
Je recommande fortement d'utiliser default relen haut de votre fichier afin que ces modes d'adressage ( [msg]et [title]) utilisent l'adressage relatif RIP au lieu de 32 bits absolu.
Peter Cordes
5

À moins que vous n'appeliez une fonction, ce n'est pas du tout trivial. (Et, sérieusement, il n'y a pas de réelle différence de complexité entre l'appel de printf et l'appel d'une fonction api win32.)

Même DOS int 21h n'est en réalité qu'un appel de fonction, même s'il s'agit d'une API différente.

Si vous voulez le faire sans aide, vous devez parler directement à votre matériel vidéo, probablement en écrivant des bitmaps des lettres "Hello world" dans un framebuffer. Même alors, la carte vidéo effectue le travail de traduction de ces valeurs de mémoire en signaux VGA / DVI.

Notez qu'en réalité, rien de tout cela jusqu'au matériel n'est plus intéressant en ASM qu'en C. Un programme "hello world" se résume à un appel de fonction. Une bonne chose à propos de l'ASM est que vous pouvez utiliser n'importe quel ABI que vous voulez assez facilement; vous avez juste besoin de savoir ce qu'est cet ABI.

Capitaine Segfault
la source
C'est un excellent point --- ASM et C reposent tous deux sur une fonction fournie par le système d'exploitation (_WriteFile sous Windows). Alors, où est la magie? Il se trouve dans le code du pilote de périphérique de la carte vidéo.
Assad Ebrahim
2
C'est tout à fait autre chose. L'affiche demande un programme assembleur qui s'exécute «sous Windows». Cela signifie que les fonctionnalités de Windows peuvent être utilisées (par exemple kernel32.dll), mais pas d'autres fonctionnalités comme la libc sous Cygwin. Pour crier à haute voix, l'affiche dit explicitement qu'il n'y a pas de c-bibliothèques.
Albert van der Horst
5

Les meilleurs exemples sont ceux avec fasm, car fasm n'utilise pas d'éditeur de liens, ce qui cache la complexité de la programmation Windows par une autre couche opaque de complexité. Si vous êtes satisfait d'un programme qui écrit dans une fenêtre d'interface graphique, alors il y a un exemple pour cela dans le répertoire d'exemples de fasm.

Si vous voulez un programme console, qui permet la redirection de l'entrée standard et de la sortie standard, cela est également possible. Il existe un programme d'exemple (hélas très non trivial) disponible qui n'utilise pas d'interface graphique et qui fonctionne strictement avec la console, c'est-à-dire le fasm lui-même. Cela peut être réduit à l'essentiel. (J'ai écrit un quatrième compilateur qui est un autre exemple non-gui, mais ce n'est pas non plus trivial).

Un tel programme a la commande suivante pour générer un en-tête exécutable approprié, normalement effectué par un éditeur de liens.

FORMAT PE CONSOLE 

Une section appelée «.idata» contient une table qui aide les fenêtres au démarrage à coupler les noms de fonctions aux adresses d'exécution. Il contient également une référence à KERNEL.DLL qui est le système d'exploitation Windows.

 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

Le format de table est imposé par les fenêtres et contient des noms qui sont recherchés dans les fichiers système, au démarrage du programme. FASM cache une partie de la complexité derrière le mot-clé rva. Ainsi, _ExitProcess @ 4 est une étiquette fasm et _exitProcess est une chaîne recherchée par Windows.

Votre programme est dans la section '.text'. Si vous déclarez que cette section est accessible en écriture et exécutable, c'est la seule section que vous devez ajouter.

    section '.text' code executable readable writable

Vous pouvez appeler toutes les installations que vous avez déclarées dans la section .idata. Pour un programme console, vous avez besoin de _GetStdHandle pour trouver les descripteurs de fichiers pour les entrées standard et standardout (en utilisant des noms symboliques tels que STD_INPUT_HANDLE que fasm trouve dans le fichier include win32a.inc). Une fois que vous avez les descripteurs de fichiers, vous pouvez faire WriteFile et ReadFile. Toutes les fonctions sont décrites dans la documentation de kernel32. Vous en êtes probablement conscient ou vous n'essaieriez pas de programmer l'assembleur.

En résumé: il existe un tableau avec des noms asci qui correspondent au système d'exploitation Windows. Au démarrage, cela se transforme en une table d'adresses appelables, que vous utilisez dans votre programme.

Albert van der Horst
la source
FASM peut ne pas utiliser d'éditeur de liens mais il doit encore assembler un fichier PE. Ce qui signifie qu'en fait, il ne se contente pas d'assembler du code, mais qu'il assume également un travail normalement effectué par un éditeur de liens, et en tant que tel, il est, à mon humble avis, trompeur d'appeler l'absence d'un éditeur de liens "cacher la complexité", bien au contraire - le travail d'un assembleur est d'assembler un programme, mais laissez à l'éditeur de liens le soin d'incorporer le programme dans une image de programme qui peut dépendre de beaucoup de choses. En tant que tel, je trouve que la séparation entre un lieur et un assembleur est une bonne chose, sur laquelle il semble que vous n'êtes pas d'accord.
amn
@amn Pensez-y de cette façon. Si vous utilisez un éditeur de liens pour créer le programme ci-dessus, cela vous donne-t-il plus d'informations sur ce que fait le programme ou en quoi il consiste? Si je regarde la source du fasm, je connais la structure complète du programme.
Albert van der Horst le
Bon point. D'un autre côté, séparer les liens de tout le reste a également ses avantages. Vous avez normalement accès à un fichier objet (ce qui permet également d'inspecter la structure d'un programme, indépendamment du format de fichier image du programme), vous pouvez invoquer un éditeur de liens différent de votre préférence, avec différentes options. Il s'agit de réutilisabilité et de composabilité. Dans cet esprit, FASM fait tout parce que c'est «pratique» enfreint ces principes. Je ne suis pas principalement contre - je vois leur justification - mais pour ma part, je n'en ai pas besoin.
amn le
obtenir une erreur pour une isntruction illégale sur la ligne supérieure dans les fenêtres fasm 64 bits
bluejayke
3

Si vous souhaitez utiliser NASM et l'éditeur de liens de Visual Studio (link.exe) avec l'exemple Hello World d'anderstornvig, vous devrez créer un lien manuel avec la bibliothèque C Runtime qui contient la fonction printf ().

nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib

J'espère que cela aide quelqu'un.

tboerman
la source