Как написать hello world на ассемблере под Windows?


94

Я хотел написать что-то базовое в сборке под Windows, использую NASM, но ничего не получается.

Как написать и скомпилировать hello world без помощи функций C в Windows?


3
Кроме того, проверить Стив Гибсон малого прекрасно окна сборки стартера комплект.
Джереми

Отказ от использования c-библиотек - довольно странное ограничение. Необходимо вызвать некоторую библиотеку в операционной системе MS-Windows. вероятно, kernel32.dll. Написал ли Microsoft это на c или на Паскале, это не имеет значения. Означает ли это, что могут быть вызваны только функции, предоставляемые ОС, что в системе типа Unix будет называться системными вызовами?
Альберт ван дер Хорст

Я предполагаю, что с библиотеками C он или она имеет в виду не использовать библиотеки времени выполнения C, подобные тем, которые поставляются с GCC или MSVC. Конечно, ему или ей придется использовать некоторые стандартные библиотеки DLL Windows, например, kernel32.dll.
Руди

2
Различие между kernel32.dll и библиотекой времени выполнения gcc заключается не в формате (оба являются dll) и не в языке (оба, вероятно, c, но это скрыто). Разница в том, поставляется ОС или нет.
Альберт ван дер Хорст

Я тоже искал это, лол, не мог найти ничего с fasm без
включений

Ответы:


39

Примеры NASM .

Вызов libc stdio printf, реализация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

Тогда беги

nasm -fwin32 helloworld.asm
gcc helloworld.obj
a

Также существует The Clueless Newbies Guide to Hello World в Nasm без использования библиотеки C. Тогда код будет выглядеть так.

16-битный код с системными вызовами MS-DOS: работает в эмуляторах DOS или в 32-битной Windows с поддержкой NTVDM . Невозможно запустить "напрямую" (прозрачно) под любой 64-битной Windows, потому что ядро ​​x86-64 не может использовать режим vm86.

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

Встройте это в .comисполняемый файл, чтобы он загружался cs:100hсо всеми сегментными регистрами, равными друг другу (крошечная модель памяти).

Удачи.


28
В вопросе явно упоминается «без использования библиотек C»
Мехрдад Афшари,

25
Неправильно. Сама библиотека C, очевидно, может, так что это возможно. На самом деле это лишь немного сложнее. Вам просто нужно вызвать WriteConsole () с правильными 5 параметрами.
MSalters

12
Хотя во втором примере не вызывается никакая функция библиотеки C, это тоже не программа Windows. Виртуальная машина DOS будет запущена для ее запуска.
Rômulo Ceccon

7
@Alex Hart, его второй пример предназначен для DOS, а не для Windows. В DOS программы в крошечном режиме (файлы .COM, общий код + данные + стек меньше 64 КБ) начинаются с 0x100h, потому что первые 256 байт в сегменте заняты PSP (аргументы командной строки и т. Д.). См. Эту ссылку: en.wikipedia.org/wiki/Program_Segment_Prefix
zvolkov

7
Это не то, о чем просили. В первом примере используется библиотека C, а во втором - MS-DOS, а не Windows.
Пауло Пинто

131

В этом примере показано, как перейти непосредственно к Windows API, а не ссылаться на стандартную библиотеку C.

    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:

Для компиляции вам понадобятся NASM и LINK.EXE (из Visual Studio Standard Edition).

   nasm -fwin32 hello.asm
   ссылка / подсистема: консоль / nodefaultlib / запись: основной hello.obj 

21
вам, вероятно, потребуется включить kernel32.lib, чтобы связать это (я сделал). ссылка / подсистема: консоль / nodefaultlib / запись: main hello.obj kernel32.lib
Зак Бурлингем

5
Как связать obj с ld.exe из MinGW?
DarrenVortex

4
@DarrenVortexgcc hello.obj
Towry

4
Будет ли это работать с бесплатными компоновщиками, такими как Alink из sourceforge.net/projects/alink или GoLink из godevtool.com/#linker ? Я не хочу устанавливать визуальную студию только для этого?
jj_

21

Это примеры Win32 и Win64 с использованием вызовов Windows API. Они предназначены для MASM, а не для NASM, но взгляните на них. Вы можете найти более подробную информацию в этой статье.

Это использует MessageBox вместо вывода на стандартный вывод.

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

Чтобы собрать и связать их с помощью MASM, используйте это для 32-битного исполняемого файла:

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

или это для 64-битного исполняемого файла:

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

Почему Windows x64 необходимо зарезервировать 28 байт пространства стека перед a call? Это 32 байта (0x20) теневого пространства или домашнего пространства, как того требует соглашение о вызовах. И еще 8 байт для повторного выравнивания стека на 16, потому что соглашение о вызовах требует РСП быть 16-байтовый выровнены передcall тем . (Это mainсделал наш вызывающий (в коде запуска CRT). 8-байтовый адрес возврата означает, что RSP находится на расстоянии 8 байтов от 16-байтовой границы при входе в функцию.)

Теневое пространство может использоваться функцией для выгрузки своих аргументов регистра рядом с тем местом, где могут быть любые аргументы стека (если есть). A system callтребует 30h (48 байтов), чтобы также зарезервировать место для r10 и r11 в дополнение к ранее упомянутым 4 регистрам. Но вызовы DLL - это просто вызовы функций, даже если они являются оболочкой для syscallинструкций.

Интересный факт: не Windows, то есть соглашение о вызовах x86-64 System V (например, в Linux) вообще не использует теневое пространство и использует до 6 целочисленных аргументов / указателей регистров и до 8 аргументов FP в регистрах XMM. .


Используя invokeдирективу MASM (которая знает соглашение о вызовах), вы можете использовать один ifdef, чтобы создать его версию, которая может быть 32-битной или 64-битной.

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

Вариант макроса одинаков для обоих, но так вы не научитесь сборке. Вместо этого вы изучите asm в стиле C. invokeдля stdcallили fastcallпока cinvokeявляется для cdeclили переменного аргумента fastcall. Ассемблер знает, что использовать.

Вы можете разобрать вывод, чтобы увидеть, насколько invokeрасширен.


1
+1 за ваш ответ. Не могли бы вы добавить код сборки для Windows и на ARM (WOA)?
Энни

1
Почему rsp требует 0x28 байт, а не 0x20? Все ссылки на соглашение о вызовах говорят, что должно быть 32, но, похоже, на практике требуется 40.
douggard

В вашем 32-битном коде окна сообщения по какой-то причине, когда я использую titleимя метки, я сталкиваюсь с ошибками. Однако, когда я использую что-то другое в качестве названия ярлыка mytitle, все работает нормально.
user3405291

как это сделать без включения?
bluejayke

13

Плоский Ассемблер не требует дополнительного компоновщика. Это упрощает программирование на ассемблере. Он также доступен для Linux.

Это hello.asmиз примеров 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 создает исполняемый файл:

> fasm hello.asm
плоский ассемблер версии 1.70.03 (1048575 килобайт памяти)
4 прохода, 1536 байт.

А это программа в IDA :

введите описание изображения здесь

Вы можете увидеть три вызова: GetCommandLine , MessageBoxи ExitProcess.


здесь используется включение и графический интерфейс, как сделать это только для CMD без включения вообще?
bluejayke

Пробовали читать мануал? flatassembler.net/docs.php?article=manual#2.4.2
декабрь

вы можете указать мне раздел, который пишет на консоль без каких-либо dll?
bluejayke

12

Чтобы получить .exe с помощью компилятора NASM и компоновщика Visual Studio, этот код отлично работает:

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

Если этот код сохранен, например, в "test64.asm", то для компиляции:

nasm -f win64 test64.asm

Производит "test64.obj" Затем для ссылки из командной строки:

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 может быть C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ bin или где бы ни была ваша программа link.exe на вашем компьютере, path_to_libs может быть C: \ Program Files (x86) \ Windows Kits \ 8.1 \ Lib \ winv6.3 \ um \ x64 или где бы то ни было ваши библиотеки (в этом случае и kernel32.lib, и user32.lib находятся в одном месте, в противном случае используйте один вариант для каждого нужного опции необходимо, чтобы линкер не жаловался на адреса слишком долго (в данном случае для user32.lib). Кроме того, как это сделано здесь, если компоновщик Visual вызывается из командной строки, необходимо предварительно настроить среду (запустить один раз vcvarsall.bat и / или просмотреть MS C ++ 2010 и mspdb100.dll пути) и / largeaddressaware: no ).


2
Я настоятельно рекомендую использовать его default relв верхней части файла, чтобы эти режимы адресации ( [msg]и [title]) использовали относительную адресацию RIP вместо 32-битной абсолютной.
Питер Кордес

Спасибо, что объяснили, как связать! Вы спасли мое психическое здоровье. Я начал выдирать волосы из-за «ошибки LNK2001: неразрешенный внешний символ ExitProcess» и подобных ошибок ...
Ник,

5

Если вы не вызовете какую-либо функцию, это совсем не тривиально. (И, серьезно, нет реальной разницы в сложности между вызовом printf и вызовом функции api win32.)

Даже DOS int 21h на самом деле просто вызов функции, даже если это другой API.

Если вы хотите сделать это без посторонней помощи, вам нужно напрямую поговорить с вашим видеооборудованием, вероятно, записывая растровые изображения букв «Hello world» во фреймбуфер. Даже тогда видеокарта выполняет работу по преобразованию этих значений памяти в сигналы VGA / DVI.

Обратите внимание, что на самом деле ничто из этого, вплоть до аппаратного обеспечения, в ASM не интереснее, чем в C. Программа "hello world" сводится к вызову функции. В ASM есть одна приятная особенность: вы можете довольно легко использовать любой ABI; вам просто нужно знать, что это за ABI.


Это отличный момент - ASM и C полагаются на функцию, предоставляемую ОС (_WriteFile в Windows). Так где же волшебство? Он находится в коде драйвера устройства для видеокарты.
Асад Ибрагим

2
Это совершенно не по существу. Плакат просит ассемблерную программу, работающую «под Windows». Это означает, что можно использовать средства Windows (например, kernel32.dll), но нельзя использовать другие средства, такие как libc в Cygwin. Для крика вслух плакат явно говорит, что нет c-библиотек.
Альберт ван дер Хорст

5

Лучшие примеры - с fasm, потому что fasm не использует компоновщик, который скрывает сложность программирования Windows другим непрозрачным уровнем сложности. Если вас устраивает программа, которая записывает данные в окно графического интерфейса пользователя, то есть пример для этого в каталоге примеров fasm.

Если вам нужна консольная программа, которая позволяет перенаправлять стандартный вход и стандартный выход, что также возможно. Доступен (очень нетривиальный) пример программы, которая не использует графический интерфейс и работает строго с консолью, то есть с самим fasm. Это можно проредить до самого необходимого. (Я написал четвертый компилятор, который является еще одним примером, не связанным с графическим интерфейсом, но он также нетривиален).

В такой программе есть следующая команда для создания правильного заголовка для 32-разрядного исполняемого файла, обычно выполняемая компоновщиком.

FORMAT PE CONSOLE 

Раздел под названием '.idata' содержит таблицу, которая помогает окнам во время запуска связывать имена функций с адресами среды выполнения. Он также содержит ссылку на KERNEL.DLL, которая является операционной системой 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

Формат таблицы определяется окнами и содержит имена, которые просматриваются в системных файлах при запуске программы. FASM скрывает некоторые сложности за ключевым словом rva. Итак, _ExitProcess @ 4 - это метка fasm, а _exitProcess - это строка, которую ищет Windows.

Ваша программа находится в разделе '.text'. Если вы объявляете этот раздел доступным для чтения, записываемым и исполняемым, это единственный раздел, который вам нужно добавить.

    section '.text' code executable readable writable

Вы можете вызвать все объекты, которые вы заявили в разделе .idata. Для консольной программы вам понадобится _GetStdHandle, чтобы найти файловые дескрипторы для стандартных входов и стандартных выходов (с использованием символических имен, таких как STD_INPUT_HANDLE, которые fasm находит во включаемом файле win32a.inc). Когда у вас есть файловые дескрипторы, вы можете выполнять WriteFile и ReadFile. Все функции описаны в документации kernel32. Вы, вероятно, знаете об этом, иначе вы не стали бы пробовать программировать на ассемблере.

В итоге: есть таблица с именами asci, которые связаны с ОС Windows. Во время запуска она преобразуется в таблицу вызываемых адресов, которую вы используете в своей программе.


FASM может не использовать компоновщик, но он все равно должен собрать PE-файл. Это означает, что на самом деле он не просто ассемблирует код, но и берет на себя работу, которую обычно выполняет компоновщик, и поэтому, по моему скромному мнению, ошибочно называть отсутствие компоновщика «скрывающей сложностью», как раз наоборот. - задача ассемблера - собрать программу, но предоставить компоновщику возможность встроить программу в образ программы, который может зависеть от многих вещей. Таким образом, я считаю, что разделение между компоновщиком и ассемблером - это хорошо , и, похоже, вы не согласны с этим.
атп

@amn Подумайте об этом иначе. Если вы используете компоновщик для создания вышеуказанной программы, дает ли он вам больше информации о том, что программа делает или из чего она состоит? Если я посмотрю на исходный код fasm, я знаю полную структуру программы.
Альберт ван дер Хорст,

Честная оценка. С другой стороны, отделение ссылок от всего остального тоже имеет свои преимущества. Обычно у вас есть доступ к объектному файлу (который имеет большое значение для проверки структуры программы, независимо от формата файла образа программы), вы можете вызвать другой компоновщик по своему усмотрению с различными параметрами. Речь идет о возможности повторного использования и компоновки. Имея это в виду, FASM делает все, потому что это «удобно», нарушает эти принципы. Я принципиально не против - я вижу у них оправдание, - но мне, например, это не нужно.
атп

получить ошибку из-за незаконного нарушения в верхней строке в 64-битных окнах fasm
bluejayke

@bluejayke Возможно, у вас не было под рукой документации по fasm. FORMAT PE генерирует 32-битный исполняемый файл, который 64-битные окна отказываются запускать. Для 64-битной программы вам понадобится FORMAT PE64. Также убедитесь, что вы используете правильные 64-битные инструкции в своей программе.
Альберт ван дер Хорст,

3

Если вы хотите использовать NASM и компоновщик Visual Studio (link.exe) с примером Hello World от anderstornvig, вам придется вручную установить связь с библиотекой времени выполнения C, содержащей функцию printf ().

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

Надеюсь, это кому-то поможет.


Автор вопросов хочет знать, как кто-то напишет printf на основе средств, предоставляемых Windows, так что это опять не имеет значения.
Альберт ван дер Хорст,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.