Коротко о FASM, ассемблере, WinAPI
-
Что такое FASM? — Это компилятор ассемблера (flat assembler).
-
Что такое ассемблер? — это машинные инструкции, то есть команды что делать процессору.
-
Что такое Windows API/WinAPI? — Это функции Windows, без них нельзя работать с Windows.
Что дают WinAPI функции? — Очень много чего:
-
Работа с файлами.
-
Работа с окнами, отрисовка картинок, OpenGL, DirectX, GDI, и все в таком духе.
-
Взаимодействие с другими процессами.
-
Работа с портами.
-
Работа с консолью Windows
-
И еще очень много интересных функций.
Зачем нужен ассемблер?
На нем можно сделать все что угодно, от ОС до 3D игр.
Вот плюсы ассемблера:
-
Он очень быстрый.
-
На нем можно сделать любую программу.
А вот минусы ассемблера:
-
Долго делать программу. (относительно)
-
Сложен в освоении.
Что нужно для программирования на ассемблере (FASM)?
-
FASM компилятор — https://flatassembler.net/
-
FASM Editor 2.0 — Удобная IDE для FASM, от fasmworld.ru (asmworld), качаем от сюда: https://fasmworld.ru/content/files/tools/FEditor-v2.0.rar
-
OlyDbg — удобный отладчик ассемблера от ollydbg.de: https://www.ollydbg.de/odbg201.zip
Это все мероприятие весит всего лишь 8.5MB.
Установка компонентов (если можно так назвать)
Архив FASM-а распаковуем в C:\\FASM\ или любой другой, но потом не забудьте настроить FASMEditor.
Архив FASMEdit-a распаковуем куда-то, в моем случае C:\\FASM Editor 2.0\
Архив OlyDbg распаковуем тоже куда-то, в моем случае C:\\Users\****\Documents\FasmEditorProjects\
Настройка FASM Editor-a
Для этого его нужно запустить.
Сразу вас приветствует FASM Editor соей заставкой.
Теперь вам нужно зайти в вкладку «Сервис» (на картинке выделил синим) -> «Настройки…»
Жмем на кнопку с названием «…» и выбираем путь к файлам или папкам.
Теперь мы полностью готовы. К началу.
Пишем «Hello world!» на FASM
В Fasm Editor нужно нажать на кнопку слева сверху или «файл» -> «новый». Выбираем любое, но можно выбрать «Console»
По началу вас это может напугать, но не боимся и разбираемся.
format PE Console ; говорим компилятору FASM какой файл делать
entry start ; говорим windows-у где из этой каши стартовать программу.
include 'win32a.inc' ; подключаем библиотеку FASM-а
;можно и без нее но будет очень сложно.
section '.data' data readable writeable ; секция данных
hello db 'hello world!',0 ; наша строка которую нужно вывести
section '.code' code readable writeable executable ; секция кода
start: ; метка старта
invoke printf, hello ; вызываем функцию printf
invoke getch ; вызываем её для того чтоб программа не схлопнулась
;то есть не закрылась сразу.
invoke ExitProcess, 0 ; говорим windows-у что у нас программа закончилась
; то есть нужно программу закрыть (завершить)
section '.idata' data import readable ; секция импорта
library kernel, 'kernel32.dll',\ ; тут немного сложней, объясню чуть позже
msvcrt, 'msvcrt.dll'
import kernel,\
ExitProcess, 'ExitProcess'
import msvcrt,\
printf, 'printf',\
getch, '_getch'
На самом деле из всей этой каши текста, команд всего 3: на 16, 18, 21 строках. (и то это не команды, а макросы. Мы к командам даже не подобрались)
Все остальное это просто подготовка программы к запуску.
Программа при запуске должна выглядеть так:
Самое интересное то что программа весит 2КБ. (Можно сократить и до 1КБ, но для упрощения и так пойдет)
Разбор: что значат этот весь текст?
На 1 строчке: «format PE Console» — это строчка говорит FASM-у какой файл скомпилировать, точнее 1 слово, все остальные слова это аргументы (можно так сказать).
PE — EXE файл, программа.
Console — говорим что это у нас консольная программа, но вам некто не мешает сделать из консольной программы оконную и наоборот.
Но есть кроме это остальные:
-
format MZ — EXE-файл НО под MS-DOS
-
format PE — EXE-файл под Windows, аналогично format PE GUI 4.0
-
format PE64 — EXE-файл под Windows, 64 битное приложение.
-
format PE GUI 4.0 — EXE-файл под Windows, графическое приложение.
-
format PE Console — EXE-файл под Windows, консольная программа. (просто подключается заранее консоль)
-
format PE Native — драйвер
-
format PE DLL — DLL-файл Windows, поясню позднее.
-
format COFF — OBJ-файл Linux
-
format MS COFF — аналогично предыдущему
-
format ELF — OBJ-файл для gcc (Linux)
-
format ELF64 — OBJ-файл для gcc (Linux), 64-bit
Сразу за командой (для компилятора) format PE Console
идет ;
это значит комментарий. К сожалению он есть только однострочный.
3 строка: entry start
-
Говорим windows-у где\в каком месте стартовать. «start» это метка, но о метках чуть позже.
5 строка: include 'win32a.inc'
-
Подключает к проекту файл, в данном случае «win32a.inc» он находиться в папке INCLUDE (в папке с FASM). этот файл создает константы и создает макросы для облегчения программирования.
8 строка: section '.data' data readable writeable
-
Секция данных, то есть программа делиться на секции (части), к этим секциям мы можем дать разрешение, имя.
Флаг «data» (Флаг это бит\байт\аргумент хранившей в себе какую-то информацию) говорит то что эта секция данных.
Флаги «readable writeable» говорят то что эта секция может читаться кем-то и записываться кем-то.
Текст ‘.data’ — имя секции
10 строка: hello db 'hello world!',0
hello — это метка, она может быть любого имени (почти, есть некоторые зарезервированные имена), эта метка хранит в себе адрес строки, это не переменная, а просто адрес, но чтобы не запоминать адреса в ручную, помогает FASM он запоминает адрес и потом когда видит эту метку снова, то он заменяет слово на адрес.
db — говорит то что под каждый символ резервируем 1 байт. То есть 1 символ храниться в одном байте.
‘hello world!’ — наша строка в кодировке ASCII
Что значит «,0» в конце строки? — это символ с номером 0 (или просто ноль), у вас на клавиатуре нет клавиши которая имела символ с номером 0, по этому этот символ используют как показатель конца строки. То есть это значит конец строки. Просто ноль записываем в байт после строки.
12 строка: section '.code' code readable writeable executable
Флаг «code» — говорит то что это секция кода.
Флаг «executable» — говорит то что эта секция исполняема, то есть в этой секции может выполняться код.
Все остальное уже разобрали.
14 строка: start:
Это второй вид меток. Просто эта метка указывает на следующую команду. Обратите внимание на то что в 3 строке мы указали start как метку входа в программу, это она и есть. Может иметь эта метка любое имя, главное не забудьте ваше новое имя метки вписать в entry
15 строка: invoke printf, hello
-
Функция printf — выводит текст\число в консоль. В данном случае текст по адресу «hello»
Это штото на подобие команды, но это и близко не команда ассемблера, а просто макрос.
Макрос — Это макро команда для компилятора, то есть вместо имени макроса подставляется что-то другое.
Например, макро команда invoke делиться на такие команды: (взят в пример команда с 15 строки)
push hello
call [printf]
Не переживайте если нечего не поняли.
17 строка: invoke getch
-
getch — функция получения нажатой кнопки, то есть просто ждет нажатия кнопки и потом возвращает нажатую кнопку.
20 строка: invoke ExitProcess, 0
-
ExitProcess — WinAPI функция, она завершает программу. Она принимает значение, с которым завершиться, то есть код ошибки, ноль это нет ошибок.
23 строка: section '.idata' data import readable
Флаг «import» — говорит то что это секция импорта библиотек.
24-25 строки:
library kernel, 'kernel32.dll',\
msvcrt, 'msvcrt.dll'
-
Макро команда «library» загружает DLL библиотеки в виртуальную память (не в ОЗУ, вам ОЗУ не хватит чтоб хранить всю виртуальную память).
Что такое DLL объясню позже.
kernel — имя которое привязывается к библиотеке, оно может быть любым.
Следующий текст после запятой: 'kernel32.dll'
— это имя DLL библиотеки который вы хотите подключить.
Дальше есть знак \
это значит что текст на следующей строке нужно подставить в эту строку.
То есть код:
library kernel, 'kernel32.dll',\
msvcrt, 'msvcrt.dll'
Заменяется на:
library kernel, 'kernel32.dll', msvcrt, 'msvcrt.dll'
Это нужно потому что у ассемблера 1 строка это 1 команда.
27-28 строка:
import kernel,\
ExitProcess, 'ExitProcess'
import
— Макро команда, которая загружает функции из DLL.
kernel
— Имя к которой привязана DLL, может быть любым.
ExitProcess
— Как будет называться функция в программе, это имя будет только в вашей программе, и по этому имени вы будете вызывать функцию. (WinAPI функция)
'ExitProcess'
— Это имя функции которое будет загружено из DLL, то есть это имя функции которое прописано в DLL.
Дальше думаю не стоит объяснять, вроде все понятно.
Что такое DLL библиотека?
Это файл с расширением DLL. В этом файле прописаны функции (какие ни будь). Это обычная программа, но которая не запускается по двойному щелчку, а загружается к программе в виртуальную память, и потом вызываются функции находящиеся в этой DLL.
Подводим итог
На ассемблере писать можно не зная самого языка, а используя всего лишь макро команды компилятора. За всю статью я упомянул всего 2 команды ассемблера это push hello
и call [printf]
. Что это значит расскажу в следующей статье.
Introduction
Before telling you what this post is, let me tell you what this is not:
-
- It is not a step-by-step guide to Assembly language programming.
- It is not a step-by-step guide to Win32 programming.
- It is not a replacement for the official NASM documentation.
- It is not a replacement for the official GCC documentation.
Having said that, let me tell you what this post is: this post is a collection of my experiences, the sum total of my mistakes, hard earned, while trying to program in assembly, C and Win32. Hopefully, someone will find it useful.
In order to understand and gain the maximum use out of this series, you will need:
- Good experience in C programming
- Some experience in calling Win32 API (from within C/C++ or other language)
- Some exposure to assembly programming, hopefully in NASM
Assembly programming is hard. Win32 programming is hard. Mixing them both is harder. Add to that the 32-bit/64-bit mix-ups and it’s going to be a veritable nightmare. That is why this blog exists. It will outline where I’ve gone wrong, and why I’ve gone wrong. But most importantly, it will log how I’ve corrected those mistakes.
On we go, then.
Getting the Tools
Editors
I like to use Notepad++ as my primary editor, but when I’m doing multi-file Windows programming it makes much sense to use DevCPP. Keep in mind that DevCPP uses MinGW port of GCC under the hood. You might need that fact later.
Assembler
There are a good lot of assemblers out there. I was strongly tempted to try Microsoft Macro Assembler (MASM), but in the end I decided against it. After all, if I wanted it easy, I’d have gone for C#.NET: the whole purpose of this excursion was to take a deep dive. Reminding myself of that, I chose to go with Netwide Assembler. In all programs in this blog, I will be using version 2.11.05, but you’re free to download the latest version here.
Although I am not a big fan of the argument “Command line tools build character”, I decided not to go with IDE tools for assembly. However, there are good tools out there, and I can mention at least one here: SASM. You can download for Windows here.
C Compiler
GCC or MinGW? You can read all about the difference between GNU, GCC and MinGW in this excellent stackoverflow question (and this as well).
I decided to go with gcc, simply for the sake of trust on the community. I use version 5.1.0 in this document.
Linker
Since I chose the GCC suite, I already get ld for free. But if you want an alternative, I can suggest ALINK.
“Hello, World”–from NASM, the Wrong Way
I’m sure most of you have seen some version of the following code as the “first Hello, World” program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
section .text global _start _start: mov ecx,msg mov ebx,1 mov eax,4 int 0x80 mov eax,1 int 0x80 section .data msg db 'Hello, Windows!',0xa len equ $ - msg |
This will work fine in Linux and pretty much any *nix. But, the sad news is that this won’t work under Windows at all! Why? Because you’re calling the Linux interrupt code (syscall) 0x80, which doesn’t exist in Windows. In Windows (or rather, DOS), the correct interrupt function would be 0x21 (or 21h), but that would mean you’re forced to write in 16-bit. Either way, calling the kernel directly in Windows seems to be not the way forward.
If we cannot directly call the kernel, then what options are available to us? The obvious choice is C runtime libraries. And why not the trusted printf? Let’s see that version.
“Hello, Windows”–Take Two, with printf
This time, we call printf. We’ll have a deep look at the way the arguments are passed in Part 2 of this article.
1 2 3 4 5 6 7 8 9 10 11 |
global _main extern _printf section .text _main: push message call _printf add esp, 4 ret message: db 'Hello, World', 10, 0 |
You need to compile this with the following line.
C:\work>nasm -f win32 hello.asm
And then link it with gcc like so:
C:\work>gcc -m32 hello.obj -o hello.exe
Remember that both NASM and GCC succeed silently. That means, unless there’s an error, you get no output on console.
Couple of points to note here:
- It is important to use the flag -f win32 here. (Both -fwin32 and -f win32 will work.) Unless you do so, NASM will happily try to compile your assembly file into a binary format (*.bin), find that it has an external reference, and fail with “error: binary output does not support external references“.
- It is important to use the option -m32 with gcc here. (Unlike nasm, gcc will not let you put a space between -m and 32.) If you do not specify -m32, then gcc will try to build a 64-bit exe, fail, and complain that “i386 architecture of input file hello.obj is incompatible with i386:x86-64 output.” In addition, you’ll get an error saying “undefined reference to WinMain“.
- The -o option lets gcc know the name of the output file. Quite inconsistently, here, gcc will not mind the space between -o and hello.exe. If you don’t specify the output file name, you’ll get a file called “a.exe”.
Assuming everything went right, you should get a file called “hello.exe” in your directory, which you can execute like so:
C:\work>hello Hello, World C:\work>
Cheers! You just called a C library routine from assembly, and made some basic I/O work happen.
However, note that we’re still using DOS subsystem. Our aim was to program for Win32, not call a C routine. Let’s do so now.
“Hello, World”–Take Three, with _WriteFile@20
This time, we’re going to use Win32 API to directly access the console.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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: |
Like in the previous examples, you have to assemble, link and run. However, let me introduce another way to do all 3 in one line:
1 |
C:\work>nasm -fwin32 hellow.asm && gcc -m32 hellow.obj -o hellow.exe && hellow |
If all went well, you should see something like this:
1 2 3 |
C:\work>nasm -fwin32 hellow.asm && gcc -m32 hellow.obj -o hellow.exe && hellow Hello, World C:\work> |
Again, couple of important points:
- Where is GetStdHandle declared in? The answer is, Kernel32.dll. The immediate next question is, how did gcc know to link with Kernel32.lib? Aren’t we supposed to get an error like this? The short answer is, because we specified the -m32 flag.
- Why the funny names, like _GetStdHandle@4? The answer has to do something with the way method names are mangled or decorated in Win32. That’s juicy material for a next article. For now, remember that the calling convention for Win32 is known as __stdcall, which defines the way functions are made available to public after compilation. Specifically, the MSDN article says, that under __stdcall, “an underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list.“
- Why the constant (-11)? That’s how we tell _GetStdHandle@4 to get us the “Standard Output” (which in this case, is the screen). Consider this the equivalent way of grabbing a handle to stdout in C, and cout in C++.
Again, knowing how a Win32 function is mangled, or decorated after compiling seems like far too much to expect at this level. After all, all C programmers get to happily write “ExitProcess” instead of “_ExitProcess@4”.
In the next step, we will look at how this can be done.
“Hello, World”–Take Four, with WriteFile
I would like to warn you beforehand: we’ll run into a (rather annoying) known issue with NASM here. This will force us to so something out of the ordinary. Also, this will force us to use ALINK instead of gcc/ld as our linker.
Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
[BITS 32] extern ExitProcess import ExitProcess kernel32.dll extern GetStdHandle import GetStdHandle kernel32.dll extern WriteFile import WriteFile kernel32.dll segment .data use32 Text db "Hello, World",0 segment .code use32 ..start: push -11 call [GetStdHandle] mov ebx, eax ; WriteFile( hstdOut, message, length(message), &bytes, 0); push dword 0 lea eax, [ebp-4] push eax push (message_end - message) push message push ebx call [WriteFile] ; ExitProcess(0) push dword 0 call [ExitProcess] ; never here hlt message: db 'Hello, World', 10 message_end: |
Couple of things to note:
- Note that this is identical to the previous version, Take #3, except for this section:
1 2 3 4 5 6 7 8
extern ExitProcess import ExitProcess kernel32.dll extern GetStdHandle import GetStdHandle kernel32.dll extern WriteFile import WriteFile kernel32.dll
What this does is to import function names directly as defined in the DLLs, thereby freeing us from having to mangle the function names ourselves.
- Note that we’re using the function names within square brackets, like to: [ExitProcess]
However, if you try to assemble this with NASM in the usual way, using -fwin32, NASM will throw an error:
C:\work>nasm -fwin32 hellow2.asm hellow2.asm:4: error: parser: instruction expected hellow2.asm:7: error: symbol `import' redefined hellow2.asm:7: error: parser: instruction expected hellow2.asm:10: error: symbol `import' redefined hellow2.asm:10: error: parser: instruction expected
Unfortunately, there is nothing we can do about this, so we will use a workaround. We are going to use -fobj instead of -fwin32. At least this will give us an obj file.
C:\work>nasm -fobj hellow2.asm
Now, if we use our usual way of gcc to link this, we will get an error.
C:\work>gcc -m32 hellow2.obj -o hellow2.exe hellow2.obj: file not recognized: File format not recognized collect2.exe: error: ld returned 1 exit status
This means that ld (which is the linker under the hood of gcc) did not like our obj format. And it’s right. We did indeed supply a wrong file format. What we should now do is to find a less restricting linker that will overlook this fact. Enter alink.
C:\work>alink -subsys console -oPE hellow2.obj ALINK v1.6 (C) Copyright 1998-9 Anthony A.J. Williams. All Rights Reserved Loading file hellow2.obj matched Externs matched ComDefs Generating PE file hellow2.exe C:\work>
Here, -subsys can have two values: console and gui. Since this is a console application, we will go with -subsys console. Also, similar to -m32 in gcc, we will need to specify that we want a Win32 PE format executable file: hence the flag -oPE.
If all went well, you should get an output like so:
C:\work>hellow2 Hello, World C:\work>
Good!
Now, we will finally look at how to go to the other mode, the GUI mode. The simplest way of displaying text output in Win32 is MessageBox function, which comes in 2 flavors: MessageBoxA for ANSI strings and MessageBoxW for UNICODE strings. We will go with ANSI version for now.
“Hello, World”–Take Five, with WriteConsoleA
In the previous example, we used the function WriteFile as the method to write to the console. Technically, the console is a logical file, so there is nothing wrong with that. However, there is a dedicated routine to write text to the console, known as WriteConsoleA. Let us use that now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
[BITS 32] extern ExitProcess import ExitProcess kernel32.dll extern GetStdHandle import GetStdHandle kernel32.dll extern WriteConsoleA import WriteConsoleA kernel32.dll segment .data use32 msg db "Hello, World",0 written dword ? segment .code use32 ..start: push -11 call [GetStdHandle] mov ebx, eax push 0 lea eax, written push eax push 13 push offset msg push ebx call [WriteConsoleA] push 0 call [ExitProcess] |
“Hello, World”–Take Six, with MessageBoxA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
[BITS 32] extern ExitProcess import ExitProcess kernel32.dll extern MessageBoxA import MessageBoxA user32.dll segment .data use32 Caption db 'From Assembly',0 Text db "Hello, World",0 segment .code use32 ..start: push dword 0 push dword Caption push dword Text push dword 0 call [MessageBoxA] push dword 0 call [ExitProcess] |
We assemble, link and execute in one step, like so:
I:\Work\Shellcode\Samples\0>nasm -fobj hellowin.asm && alink -subsys gui -oPE he llowin.obj && hellowin ALINK v1.6 (C) Copyright 1998-9 Anthony A.J. Williams. All Rights Reserved Loading file hellowin.obj matched Externs matched ComDefs Generating PE file hellowin.exe I:\Work\Shellcode\Samples\0>
Note the flag -subsys -gui to alink. There will be no console output, but you should see a familiar message box popping up.
This concludes the first part. In the next part, we’ll take a deeper look at interfacing with C library functions, with special attention to what are known as calling conventions.
Hey there!
This year I studied computer’s organisation II in College. I’ve been fascinated thus far with how computation is done on an assembler language and I want to share with you a little of my knowledge.
Maybe this will become a series because I’m finding myself really captivated with this subject and I think a lot of people will understand better how programming works with this info.
But first things first
❓ What is ASM?
ASM, short for Assembler (or assembly), is not a unique language such as C, Java, Go or whatever, it is instead a program that converts code into machine language. This means there’s assembler languages for the different types of machines. For example: There is assembler for the Intel and AMD processor’ architectures (x86_64) and there’s another for ARM architectures.
This tutorial is going to be oriented toward Intel’s Architecture
🔧 What are we going to use?
For this short program we are going to use NASM and whatever text editor you like. In my case, I’m going to use VS Code since it has some nice plugins.
To install NASM on Debian systems (Ubuntu, PopOs!, Linux Mint, etc..)
sudo apt-get update -y sudo apt-get install -y nasm
I’m only going to show this example in a linux system since the sys calls are different for mac, hence the example won’t work in that system (believe me, this post was intended for mac as well…)
🏗️ Structure of an ASM program
Ok, now we have our assembler and our Text Editor or IDE. What now?
Let’s create a new file and name it helloWorld.asm
Now that we have our empty file. We need to determine how the file is going to be used. In ASM each file has 4 sections. This sections will always exist even if you don’t define them. However, if you need one, you will have to do it.
The 4 sections are:
-
.data : where we are going to declare our global initialised variables
-
.rodata : where we are going to declare our global un-itialised constants
-
.bss : where we are going to declare our global un-initialised variables
-
.text : where we are going to define our code
👏 Hands On
Ok, so what we are trying to build here is a CLI program that prints Hello World!. Sounds fairly easy. But in order to do so, we need to inform the processor that this function that we are going to name ‘start’ is global to all the system. so we add our .text section with the ‘start’ function and the global statement outside the section. Like this:
Since we don’t want to use any fancy C functions, nor none of those other high level languages functions for the matter, we are going to rely on Syscalls.
Without digging that deep, Syscalls are just calls to the OS. We need to call the 0x80 interruption (on UNIX systems) and pass to that interruption the parameters we want it to handle.
For the function that we are going to use (sys_write) the interruption receives 4 parameters:
- The function number (RAX)
- Where do we want it to execute (RBX)
- The direction of the memory we want to execute (RCX)
- The size of the message in bytes (RDX)
RAX, RBX, RCX and RDX are just multi-purpose registers that we are going to use and that I’m going to explain in further chapters of this series. So bare with me for now.
So let’s define the message first and let’s call int 0x80 after that.
A lot of new info here. Let’s go line by line.
This is the section were we are going to define our ‘Hello World!’ string variable. Since it will be already initialised, we declare in .data
msg: DB 'Hello World!', 10
This is our new string. It’s declared under the name msg and we initialise it with DB (define byte) the characters that will be displayed and a ‘, 10’ which is going to be our \n character. What I want you to get out of this step is that Each char comprising ‘Hello World!’ takes one byte of memory. So by using DB we are asking the processor for a memory slot that will take 13 bytes (counting the space and \n char).
This one is a little bit tougher. We are declaring a variable call msgSize that is going to step on the right end of Hello World! ($) and will subtract the address were your msg variable began. Thus leaving us with the bytes used for msg
We have our message, let’s display it now!
Again, let’s explain what is happening here
mov rax, 4 ; function 4
mov rbx, 1 ; stdout
mov rcx, msg ; msg
mov rdx, msgSize ; size
int 0x80
Intel has a very weird way of doing things most of the time. So each line of text will be divided into 4 fragments again
- Mov : an instruction which moves the elements from B to A
- A : the destiny Register/Memory
- B : the origin Register/Memory
- comments : where the comments are :p
So what we are doing in here is moving the number 4 to our RAX register (because sys_write is our function number 4 on UNIX). We move the number 1 to RBX (representing STDOUT). Then the memory in which msg is defined will be stored on RCX and finally the size on RCX. By calling int 0x80 we are asking the interruption 0x80 to handle all the parameters we threw to it and do what it’s supposed to do.
Our final step is to exit the program. And guess what? that requires another Syscall. In this case, our function will be number 1 (exit) and our parameter will be 0 (because that’s the number we want to return. 0 usually means that the program was executed successfully while 1 means that it wasn’t)
mov rax, 1 ; function 1
mov rbx, 0 ; code
int 0x80
🔗 Assembling and Linking
Let’s save our file as helloWorld.asm and head over to the terminal.
If you have already installed NASM, head to the folder where you saved your .asm file and assemble and link it.
Linux:
nasm -f elf64 -g -F DWARF helloWorld.asm ld -e start -o helloWorld helloWorld.o ./helloWorld
And that’s it for today. You should get a ‘Hello World message on your terminal.
If you want to see the full code, here is the Repository: Hello World!
Автор: xrnd | Рубрика: Учебный курс | 16-03-2010 | Распечатать запись
В этой части наконец-то напишем долгожданный «Hello, world!». Теперь почти всё должно быть понятно. Для начала необходимо с помощью директивы db объявить строку, содержащую сообщение «Hello, word!». Лучше сделать это в конце программы, за последней командой, иначе процессор может принять строку за код и попытаться её выполнить.
Для вывода строки используется системная функция DOS. Чтобы напечатать строку, нужно поместить 9 в регистр AH, а в регистр DX поместить адрес строки, которая должна заканчиваться символом ‘$’. Обращение к функциям DOS осуществляется с помощью команды int 21h. Вот код программы:
1 2 3 4 5 6 7 8 9 10 11 |
use16 ;Генерировать 16-битный код org 100h ;Программа начинается с адреса 100h mov dx,hello ;В DX адрес строки. mov ah,9 ;Номер функции DOS. int 21h ;Обращение к функции DOS. mov ax,4C00h ;\ int 21h ;/ Завершение программы ;------------------------------------------------------- hello db 'Hello, world!$' |
В четвёртой строке FASM подставит адрес строки вместо hello. Не трудно догадаться, что завершение программы — это тоже функция DOS с номером 4Ch. Перед её вызовом в регистр AL помещается код завершения программы (ноль соответствует успешному завершению). Можно объединить эти две операции и сразу поместить в AX значение 4C00h.
В учебном курсе я не буду подробно описывать функции DOS, лишь кратко расскажу о тех функциях, которые мы будем использовать. Если вы захотите узнать больше, в Интернете можно найти подробное описание 🙂
Чтобы увидеть работу программы, надо запустить её из командной строки, иначе она печатает строку и сразу закрывается. Или можно написать простенький bat-файл для запуска:
Результат работы программы:
Если вы запустите программу в отладчике, то просмотреть выводимую строку можно, нажав Alt+F5 или выбрав в меню Turbo Debuger пункт Window->User Screen.
Следующая часть »
В этой части наконец-то напишем долгожданный «Hello, world!». Теперь почти всё должно быть понятно. Откроем в текстовом редакторе новый файл и сохраним его как hello.asm в директории C:\fasm. Для начала необходимо с помощью директивы db объявить строку, содержащую сообщение «Hello, word!». Лучше сделать это в конце программы, за последней командой, иначе процессор может принять строку за код и попытаться её выполнить.
Для вывода строки используется системная функция DOS. Чтобы напечатать строку, нужно поместить 9 в регистр AH, а в регистр DX поместить адрес строки, которая должна заканчиваться символом ‘$’. Обращение к функциям DOS осуществляется с помощью команды int 21h. Вот код программы:
use16 ;Генерировать 16—битный код org 100h ;Программа начинается с адреса 100h mov dx,hello ;В DX адрес строки. mov ah,9 ;Номер функции DOS. int 21h ;Обращение к функции DOS. mov ax,4C00h ;\ int 21h ;/ Завершение программы ;———————————————————————————— hello db ‘Hello, world!$’ |
В четвёртой строке FASM подставит адрес строки вместо hello. Не трудно догадаться, что завершение программы — это тоже функция DOS с номером 4Ch. Перед её вызовом в регистр AL помещается код завершения программы (ноль соответствует успешному завершению). Можно объединить эти две операции и сразу поместить в AX значение 4C00h.
В учебном курсе я не буду подробно описывать функции DOS, лишь кратко расскажу о тех функциях, которые мы будем использовать. Если вы захотите узнать больше, в Интернете можно найти подробное описание
Чтобы увидеть работу программы, надо дать команду fasm для компиляции исходного кода в файл COM, а затем запустить её из командной строки DOSBox.
Если вы помните, в третьем уроке про отладчик мы разбирали нашу первую программу в дебагере. Для работы дебагера мы редактировали dosbox.conf, чтобы закоментировать DPMI хост. Теперь нам нужно снова вызвать хост и раскомменировать эти две строки:
#cd CWSDPMI/BIN/ #CWSDPMI -p -s- |
Уберите знаки решетки в начале строк и сохраните dosbox.conf. Теперь запускаем DOSBox и вводим команды:
Результат работы программы:
Если вы запустите программу в отладчике Turbo Debugger, то просмотреть выводимую строку можно, нажав Alt+F5 или выбрав в меню Turbo Debugger пункт Window->User Screen.