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?
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?
Réponses:
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 .com
exécutable pour qu'il soit chargé cs:100h
avec tous les registres de segment égaux les uns aux autres (modèle de mémoire minuscule).
Bonne chance.
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
gcc hello.obj
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.
;---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
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
. ( main
L'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 call
né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 syscall
instructions.
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 invoke
directive 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. invoke
est pour stdcall
ou fastcall
while cinvoke
est pour cdecl
ou argument variable fastcall
. L'assembleur sait lequel utiliser.
Vous pouvez désassembler la sortie pour voir son invoke
extension.
title
comme nom d'étiquette, je rencontre des erreurs. Cependant, lorsque j'utilise autre chose comme nom d'étiquette mytitle
, tout fonctionne bien.
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.asm
tiré 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 :
Vous pouvez voir les trois appels: GetCommandLine
, MessageBox
et ExitProcess
.
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
où 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).
default rel
en haut de votre fichier afin que ces modes d'adressage ( [msg]
et [title]
) utilisent l'adressage relatif RIP au lieu de 32 bits absolu.
À 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.
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.
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.