Як написати hello world в асемблері під Windows?


94

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

Як писати та компілювати привіт світ без допомоги функцій C у Windows?


3
Також ознайомтесь із початковим набором збірки вікон Small Is Beautiful Windows Стіва Гібсона .
— Джеремі,

Не використання c-бібліотек - це дещо дивне обмеження. Потрібно викликати якусь бібліотеку в операційній системі MS-Windows. ймовірно kernel32.dll. Якщо Microsoft писала це на c або Pascal, це здається неактуальним. Чи означає це, що можна викликати лише функції, що постачаються з ОС, що в системі типу Unix називатимуть системними викликами?
— Альберт ван дер Горст,

З бібліотеками C я припускаю, що він або вона має на увазі без використання бібліотек середовища виконання, таких як ті, що постачаються з GCC або MSVC. Звичайно, йому або їй доведеться використовувати деякі стандартні бібліотеки DLL для Windows, такі як kernel32.dll.
— Руді Велтуїс,

2
Різниця між kernel32.dll та бібліотекою середовища виконання gcc полягає не у форматі (обидва є dll) і не в мові (обидва, ймовірно, c, але це приховано.) Різниця полягає в тому, що постачається з ОС чи ні.
— Альберт ван дер Горст

Я шукав це також хаха не міг знайти нічого з fasm без включає
— bluejayke

Відповіді:


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

Існує також «Безглуздий посібник для початківців для Hello World» в Назмі без використання бібліотеки 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

Цей приклад показує, як перейти безпосередньо до API Windows, а не посилатись у стандартній бібліотеці 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 привіт.asm
   посилання / підсистема: console / nodefaultlib / entry: main hello.obj 

21
вам, ймовірно, потрібно включити kernel32.lib, щоб зв’язати це (я це зробив). посилання / підсистема: console / nodefaultlib / entry: main hello.obj kernel32.lib
— Zach Burlingame

5
Як зв'язати obj з ld.exe від MinGW?
— DarrenVortex

4
@DarrenVortexgcc hello.obj
— towry

4
Чи буде це також працювати з використанням безкоштовних посилань, таких як Alink з sourceforge.net/projects/alink або GoLink з godevtool.com/#linker ? Я не хочу встановлювати Visual Studio лише для цього?
— jj_

21

Це приклади Win32 та Win64 із використанням викликів Windows API. Вони скоріше для MASM, ніж NASM, але подивіться на них. Більше деталей ви можете знайти в цій статті.

Тут використовується MessageBox замість друку на 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

Щоб зібрати та зв’язати їх за допомогою 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, оскільки умова виклику вимагає, щоб RSP вирівнювалося до 16 байт перед a call. (Нашіmain зробив абонент (у коді запуску CRT). 8-байтова адреса повернення означає, що RSP знаходиться на 8 байт від 16-байтової межі при вході до функції.)

Тіньовий простір може використовуватися функцією для скидання своїх аргументів реєстру поруч із місцем розташування будь-яких аргументів стека (якщо такі є). A system callвимагає 30 годин (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

14

Flat Assembler не потребує додаткового компонувача. Це робить програмування на асемблері досить простим. Він також доступний для 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 знаходяться в одному місці, інакше використовуйте один параметр для кожного потрібного вам шляху) та / largeaddressaware: жодного параметра необхідно, щоб уникнути скарг лінкера на довгі адреси (у цьому випадку для user32.lib). Крім того, як це робиться тут, якщо посилання Visual викликається з командного рядка, необхідно попередньо налаштувати середовище (запустити один раз vcvarsall.bat та / або див. MS C ++ 2010 та mspdb100.dll).


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

Дякуємо за пояснення, як зв’язати! Ти врятував моє психічне здоров'я. Я починав виривати волосся за "помилку LNK2001: невирішений зовнішній символ ExitProcess" та подібні помилки ...
— Нік,

5

Якщо ви не викликаєте якусь функцію, це зовсім не тривіально. (І, серйозно, немає реальної різниці в складності між викликом printf і викликом функції win32 api.)

Навіть DOS int 21h насправді є лише викликом функції, навіть якщо це інший API.

Якщо ви хочете зробити це без допомоги, вам потрібно поговорити безпосередньо з вашим обладнанням для відео, ймовірно, ввівши растрові зображення літер "Hello world" у буфер кадрів. Навіть тоді відеокарта виконує роботу з перетворення цих значень пам’яті у сигнали VGA / DVI.

Зауважте, що насправді ніщо з цього матеріалу аж до апаратного забезпечення не є цікавішим у ASM, ніж у C. Програма "привіт світ" зводиться до виклику функції. Одна приємна річ про ASM полягає в тому, що ви можете досить легко використовувати будь-який ABI; Вам просто потрібно знати, що це за ABI.


Це чудовий момент - як ASM, так і C покладаються на функцію, що надається ОС (_WriteFile в Windows). То де ж магія? Це в коді драйвера пристрою для відеокарти.
— Асад Ебрагім

2
Це ґрунтовно, крім суті. Плакат запитує програму-асемблер, яка працює "під Windows". Це означає, що можна використовувати засоби Windows (наприклад, kernel32.dll), але не інші засоби, такі як libc під Cygwin. За плач уголос, плакат прямо говорить, що немає c-бібліотек.
— Альберт ван дер Горст,

5

Найкращими прикладами є ті, що мають fasm, оскільки fasm не використовує компоновщик, який приховує складність програмування вікон за іншим непрозорим шаром складності. Якщо ви задоволені програмою, яка пише у вікно графічного інтерфейсу, тоді є приклад для цього у прикладі каталогу fasm.

Якщо вам потрібна консольна програма, яка дозволяє переспрямувати стандартний вхід і стандартний вихід, що також можливо. Доступна (насправді вкрай нетривіальна) прикладна програма, яка не використовує графічний інтерфейс і працює суворо з консоллю, тобто сама fasm. Це можна звести до найнеобхіднішого. (Я написав четвертий компілятор, який є ще одним не-графічним прикладом, але він також нетривіальний).

Така програма має наступну команду для створення належного заголовка для 32-розрядного виконуваного файлу, як правило, виконуваного лінкером.

FORMAT PE CONSOLE 

Розділ під назвою '.idata' містить таблицю, яка допомагає Windows під час запуску поєднувати імена функцій з адресами середовища виконання. Він також містить посилання на 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. Усі функції описані в документації до ядра 32. Ви, мабуть, це знаєте, або не хотіли б спробувати програмування на асемблері.

Підсумовуючи: Існує таблиця з іменами asci, які поєднуються з ОС Windows. Під час запуску це перетворюється в таблицю адрес, що викликаються, які ви використовуєте у своїй програмі.


FASM може не використовувати компоновщик, але він все одно повинен зібрати файл PE. Що означає, що насправді він не просто збирає код, а й бере на себе роботу, яку зазвичай виконує лінкер, і, як така, на мою скромну думку, оманливо називати відсутність лінкера "приховуванням складності", а навпаки - робота асемблера полягає в тому, щоб зібрати програму, але залишити лінкеру вбудувати програму в образ програми, який може залежати від багатьох речей. Таким чином, я вважаю, що розмежування між компонувальником та асемблером - це добре , і, як видається, ви не погоджуєтесь.
— амн

@amn Подумайте про це таким чином. Якщо ви використовуєте компоновщик для створення вищезазначеної програми, це дає вам більше розуміння того, що робить програма, або з чого вона складається? Якщо я подивлюсь на джерело fasm, то знаю повну структуру програми.
— Альберт ван дер Горст,

Справедливий момент. З іншого боку, відокремлення посилання від усього іншого також має свої переваги. Зазвичай у вас є доступ до об'єктного файлу (що значно сприяє тому, що людина також може перевіряти структуру програми, незалежно від формату файлу зображення програми), ви можете викликати інший компонувальник за вашим уподобанням, з різними параметрами. Йдеться про багаторазове використання та композицію. Маючи це на увазі, FASM, роблячи все, тому що це "зручно" порушує ці принципи. Я принципово не проти - я бачу в цьому їх виправдання, - але мені, наприклад, це не потрібно.
— ранку

отримати помилку за незаконне невикористання верхніх рядків у 64-розрядних вікнах
— fasm

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