Solución:
Este ejemplo muestra cómo ir directamente a la API de Windows y no vincularlo a la biblioteca estándar de C.
global _main
extern [email protected]
extern [email protected]
extern [email protected]
section .text
_main:
; DWORD bytes;
mov ebp, esp
sub esp, 4
; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
push -11
call [email protected]
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 [email protected]
; ExitProcess(0)
push 0
call [email protected]
; never here
hlt
message:
db 'Hello, World', 10
message_end:
Para compilar, necesitará NASM y LINK.EXE (de Visual Studio Standard Edition)
nasm -fwin32 hello.asm link /subsystem:console /nodefaultlib /entry:main hello.obj
Ejemplos de NASM.
Llamar a libc stdio printf
, implementar 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
Entonces corre
nasm -fwin32 helloworld.asm
gcc helloworld.obj
a
También está la guía Clueless Newbies para Hello World en Nasm sin el uso de una biblioteca C. Entonces el código se vería así.
Código de 16 bits con llamadas al sistema MS-DOS: funciona en emuladores de DOS o en Windows de 32 bits con soporte NTVDM. No se puede ejecutar “directamente” (de forma transparente) en cualquier Windows de 64 bits, porque un kernel x86-64 no puede usar el modo vm86.
org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'
Construye esto en un .com
ejecutable por lo que se cargará en cs:100h
con todos los registros de segmento iguales entre sí (modelo de memoria diminuta).
Buena suerte.
Estos son ejemplos de Win32 y Win64 que utilizan llamadas a la API de Windows. Son para MASM en lugar de NASM, pero échales un vistazo. Puede encontrar más detalles en este artículo.
Esto usa MessageBox en lugar de imprimir en stdout.
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
Para ensamblar y vincular estos usando MASM, use esto para el ejecutable de 32 bits:
ml.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main
o esto para el ejecutable de 64 bits:
ml64.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
¿Por qué Windows x64 necesita reservar 28h bytes de espacio de pila antes de call
? Eso es 32 bytes (0x20) de espacio de sombra, también conocido como espacio doméstico, como lo requiere la convención de llamadas. Y otros 8 bytes para realinear la pila en 16, porque la convención de llamada requiere que RSP esté alineado con 16 bytes antes de a call
. (Nuestro main
La persona que llamó (en el código de inicio CRT) hizo eso. La dirección de retorno de 8 bytes significa que RSP está a 8 bytes de un límite de 16 bytes al ingresar a una función).
Una función puede usar el espacio de sombra para volcar sus argumentos de registro junto a donde estaría cualquier argumento de pila (si lo hubiera). A system call
requiere 30 h (48 bytes) para reservar también espacio para r10 y r11 además de los 4 registros mencionados anteriormente. Pero las llamadas a DLL son solo llamadas a funciones, incluso si son envolturas syscall
instrucciones.
Dato curioso: no Windows, es decir, la convención de llamadas de System V x86-64 (por ejemplo, en Linux) no usa espacio de sombra en absoluto, y usa hasta 6 argumentos de registro de enteros / punteros, y hasta 8 argumentos FP en registros XMM.
Usando MASM’s invoke
directiva (que conoce la convención de llamada), puede usar un ifdef para hacer una versión de esto que se puede construir como 32 bits o 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 macro es la misma para ambos, pero no aprenderá a ensamblar de esta manera. En su lugar, aprenderá ASM estilo C. invoke
es para stdcall
o fastcall
tiempo cinvoke
es para cdecl
o argumento variable fastcall
. El ensamblador sabe cuál usar.
Puede desmontar la salida para ver cómo invoke
expandido.