Я хотів написати щось базове в збірці під Windows, я використовую NASM, але не можу нічого працювати.
Як писати та компілювати привіт світ без допомоги функцій C у Windows?
Я хотів написати щось базове в збірці під Windows, я використовую NASM, але не можу нічого працювати.
Як писати та компілювати привіт світ без допомоги функцій C у Windows?
Відповіді:
Виклик 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
з усіма регістрами сегментів, рівними один одному (крихітна модель пам'яті).
Удачі.
Цей приклад показує, як перейти безпосередньо до 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
gcc hello.obj
Це приклади Win32 та Win64 із використанням викликів Windows API. Вони скоріше для MASM, ніж NASM, але подивіться на них. Більше деталей ви можете знайти в цій статті.
Тут використовується MessageBox замість друку на 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
Щоб зібрати та зв’язати їх за допомогою 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
розширено.
title
як ім'я мітки, я стикаюся з помилками. Однак коли я використовую щось інше як назву етикетки, як-от mytitle
, все працює нормально.
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
.
Щоб отримати .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).
default rel
у верхній частині файлу, тому ці режими адресації ( [msg]
і [title]
) використовують RIP-відносну адресацію замість 32-бітової абсолютної.
Якщо ви не викликаєте якусь функцію, це зовсім не тривіально. (І, серйозно, немає реальної різниці в складності між викликом printf і викликом функції win32 api.)
Навіть DOS int 21h насправді є лише викликом функції, навіть якщо це інший API.
Якщо ви хочете зробити це без допомоги, вам потрібно поговорити безпосередньо з вашим обладнанням для відео, ймовірно, ввівши растрові зображення літер "Hello world" у буфер кадрів. Навіть тоді відеокарта виконує роботу з перетворення цих значень пам’яті у сигнали VGA / DVI.
Зауважте, що насправді ніщо з цього матеріалу аж до апаратного забезпечення не є цікавішим у ASM, ніж у C. Програма "привіт світ" зводиться до виклику функції. Одна приємна річ про ASM полягає в тому, що ви можете досить легко використовувати будь-який ABI; Вам просто потрібно знати, що це за ABI.
Найкращими прикладами є ті, що мають 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. Під час запуску це перетворюється в таблицю адрес, що викликаються, які ви використовуєте у своїй програмі.
Якщо ви хочете використовувати NASM та зв’язувач Visual Studio (link.exe) з прикладом Hello World від anderstornvig, вам доведеться вручну пов’язувати бібліотеку середовища виконання C, що містить функцію printf ().
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
Сподіваюся, це комусь допомагає.