Я хотел написать что-то базовое в сборке под Windows, использую NASM, но ничего не получается.
Как написать и скомпилировать hello world без помощи функций C в Windows?
Я хотел написать что-то базовое в сборке под Windows, использую NASM, но ничего не получается.
Как написать и скомпилировать hello world без помощи функций 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
Также существует 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
со всеми сегментными регистрами, равными друг другу (крошечная модель памяти).
Удачи.
В этом примере показано, как перейти непосредственно к 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
gcc hello.obj
Это примеры Win32 и Win64 с использованием вызовов Windows API. Они предназначены для MASM, а не для NASM, но взгляните на них. Вы можете найти более подробную информацию в этой статье.
Это использует MessageBox вместо вывода на стандартный вывод.
;---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, потому что соглашение о вызовах требует РСП быть 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
расширен.
title
имя метки, я сталкиваюсь с ошибками. Однако, когда я использую что-то другое в качестве названия ярлыка mytitle
, все работает нормально.
Плоский Ассемблер не требует дополнительного компоновщика. Это упрощает программирование на ассемблере. Он также доступен для 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 находятся в одном месте, в противном случае используйте один вариант для каждого нужного опции необходимо, чтобы линкер не жаловался на адреса слишком долго (в данном случае для user32.lib). Кроме того, как это сделано здесь, если компоновщик Visual вызывается из командной строки, необходимо предварительно настроить среду (запустить один раз vcvarsall.bat и / или просмотреть MS C ++ 2010 и mspdb100.dll пути) и / largeaddressaware: no ).
default rel
в верхней части файла, чтобы эти режимы адресации ( [msg]
и [title]
) использовали относительную адресацию RIP вместо 32-битной абсолютной.
Если вы не вызовете какую-либо функцию, это совсем не тривиально. (И, серьезно, нет реальной разницы в сложности между вызовом printf и вызовом функции api win32.)
Даже DOS int 21h на самом деле просто вызов функции, даже если это другой API.
Если вы хотите сделать это без посторонней помощи, вам нужно напрямую поговорить с вашим видеооборудованием, вероятно, записывая растровые изображения букв «Hello world» во фреймбуфер. Даже тогда видеокарта выполняет работу по преобразованию этих значений памяти в сигналы VGA / DVI.
Обратите внимание, что на самом деле ничто из этого, вплоть до аппаратного обеспечения, в ASM не интереснее, чем в C. Программа "hello world" сводится к вызову функции. В ASM есть одна приятная особенность: вы можете довольно легко использовать любой ABI; вам просто нужно знать, что это за ABI.
Лучшие примеры - с 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. Во время запуска она преобразуется в таблицу вызываемых адресов, которую вы используете в своей программе.
Если вы хотите использовать NASM и компоновщик Visual Studio (link.exe) с примером Hello World от anderstornvig, вам придется вручную установить связь с библиотекой времени выполнения C, содержащей функцию printf ().
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
Надеюсь, это кому-то поможет.