Saltar al contenido

¿Cómo escribir hola mundo en ensamblador en Windows?

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

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *