Sasm

Содержание:

Введение

В отличие от других языков для программирования на ассемблере необходимо знание архитектуры процессора машины, для которой выполняется программирование. Программы на ассемблере совершенно непортируемы, часто очень трудоемки в поддержке и понимании и, как правило, содержат большое количество строк кода. Однако наряду с этими ограничениями они имеют преимущество в скорости и размере запускаемого на машине двоичного кода.

Хотя по программированию под Linux на уровне ассемблера доступно большое количество информации, эта статья подробно показывает разницу между синтаксисами двух вариантов ассемблера в таком виде, чтобы помочь вам проще выполнять преобразование из одного вида ассемблера в другой. Эта статья родилась на основе моих собственных попыток освоить такое преобразование.

В этой статье используется ряд примеров программ. Каждая программа иллюстрирует те или иные особенности и сопровождается обсуждением и сравнением синтаксиса. Несмотря на то, что охватить все различия NASM и GAS невозможно, я попытаюсь раскрыть основные моменты и заложить основу для дальнейшего изучения этого вопроса. Даже если вы знакомы и с NASM, и с GAS, вы можете найти здесь что-нибудь полезное, например, макросы.

В этой статье предполагается, что вы по меньшей мере знакомы с терминологией ассемблера и программировали на ассемблере с использованием синтаксиса Intel, возможно, с помощью NASM под Linux или Windows. Из этой статьи вы не узнаете, как вводить код в редактор или как транслировать и компоновать код (однако во врезке приведено ). Вы должны быть знакомы с операционной системой Linux (подойдет любой дистрибутив Linux; я использовал Red Hat и Slackware) и основными инструментами GNU, такими как gcc и ld, а также вы должны программировать на машине x86.

Теперь я расскажу о том, что вы сможете найти в этой статье, а чего не сможете.

Сборка примеров

Трансляция: GAS:

NASM:

Компоновка (общая для обоих типов ассемблера):

Компоновка при использовании внешней библиотеки C:

В этой статье описываются:

  • Основные синтаксические различия NASM и GAS
  • Общие конструкции на уровне ассемблера, такие как переменные, циклы, метки и макросы
  • Немного о вызове внешних подпрограмм на C и использовании функций
  • Использование и различия мнемоник ассемблера
  • Методы адресации памяти

В этой статье не рассматриваются:

  • Набор инструкций процессора
  • Различные виды макросов и компоненты, специфичные для того или иного ассемблера
  • Директивы ассемблера, характерные только для NASM либо GAS
  • Возможности, не являющиеся широко употребительными, или имеющиеся в одном ассемблере и отсутствующие в другом

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

Примеры программы Hello, world! под разные ОС

Примеры программы Hello, world!, которая выводит соответствующее сообщение и завершается.

Под операционную систему Linux

SECTION .data
msg     db "Hello, world!",0xa  
len     equ $ - msg
SECTION .text
global _start           ; the program entry point
_start:
        mov eax, 4      ; 'write' syscall
        mov ebx, 1      ; file descr. 1 (stdout)
        mov ecx, msg    ; pointer to the data
        mov edx, len    ; amount of data
        int 0x80        ; call to the kernel
        mov eax, 1      ; '_exit' syscall
        mov ebx,       ; zero exit code (success)
        int 0x80        ; call to the kernel

Под операционную систему Linux (x64)

global  _start

section .text
_start:
        mov     rax, 1                  ; system call 1 is write
        mov     rdi, 1                  ; file handle 1 is stdout
        mov     rsi, message            ; address of string to output
        mov     rdx, 13                 ; number of bytes
        syscall                         ; invoke operating system to do the write

        mov     eax, 60                 ; system call 60 is exit
        xor     rdi, rdi                ; exit code 0
        syscall                         ; invoke operating system to exit
message:
        db      "Hello, World", 10      ; note the newline at the end

Под операционную систему DOS

SECTION .text
org 0x100		; эта директива нужна только в случае .com файла, в котором нет никаких	секций
	mov ah, 0x9
	mov dx, hello
	int 0x21
	mov ax, 0x4c00		; ah == 0x4c al == 0x00
	int 0x21
SECTION .data
	hello DB "Hello, world!",0xd,0xa,'$'

Под операционную систему Windows (obj)

%include 'WIN32N.INC'
EXTERN MessageBoxA
Import MessageBoxA user32.dll
EXTERN ExitProcess
Import ExitProcess kernel32.dll
SECTION CODE USE32 CLASS=CODE
..start:
	push UINT MB_OK
	push LPCTSTR title
	push LPCTSTR banner
	push HWND NULL
	call MessageBoxA
	push UINT NULL
	call ExitProcess
SECTION DATA USE32 CLASS=DATA
	banner db 'Hello, world!',0xD,0xA,
	title db 'Hello',

Под операционную систему Windows x64 (obj)

; Hello.asm
EXTERN MessageBoxW
EXTERN ExitProcess
SECTION .text USE64
start:
	sub rsp, 28h         ; Microsoft x64 calling convention "shadow space"
	xor rcx, rcx         ; HWND hWnd = NULL
        lea rdx, banner    ; LPCTSTR lpText = banner
        lea r8, title      ; LPCTSTR lpCaption = title
        xor r9, r9           ; UINT uType =  MB_OK
        call MessageBoxW     ; MessageBox(hWnd, lpText, lpCaption, uType)
        xor rcx, rcx         ; UINT uExitCode = 0
        call ExitProcess     ; ExitProcess(uExitCode)
SECTION .data
        banner dw __utf16__('Hello, world!'),
        title dw __utf16__('Hello!'),

>nasm -f win64 Hello.asm
>golink Hello.obj kernel32.dll user32.dll

Под операционную систему FreeBSD

SECTION .data
msg     db "Hello, world!",0xa  
len     equ $ - msg
SECTION .text
global _start            ; the program entry point
_start:
        push dword len
        push dword msg
        push dword 1     ; 1 is the file descriptor of stdout
        mov eax, 4       ; 4 is the 'write' syscall
        push eax         ; we must leave an extra dword on the stack
        int 0x80         ; call to the kernel
        add esp, 16      ; clean up the stack
        push dword      ; 0 is the exit code (success)
        mov eax, 1       ; 1 is the '_exit' syscall
        push eax         ; extra dword on the stack
        int 0x80         ; call to the kernel
                         ; no cleanup - we will never return

Под операционную систему KolibriOS

bits 32
     %include 'mos.inc'
     section .text
     MOS_HEADER01 main,image_end,memory_end,stacktop,,
main:
redraw:
       call draw_window
wait_event:
	MOS_WAITEVENT
	dec eax
	jz redraw
	dec eax
	jz key
;button pressed; we have only one button, close
	MOS_EXIT
key:
;key pressed, read it and ignore
	mov eax, MOS_SC_GETKEY
	int 0x40
	jmp wait_event
draw_window:
	MOS_STARTREDRAW
	xor eax, eax
	mov ebx, 10*65536 + 150
	mov ecx, 40*65536 + 50
	mov edx, 0x33FFFFFF
	mov edi, header
	int 0x40 ;define&draw window
	mov eax, MOS_SC_WRITETEXT
	mov ebx, 30*65536 + 10
	mov ecx, 0x80000000
	mov edx, string
	int 0x40 ;display string
	MOS_ENDREDRAW
	ret
	section	.data
header db 'HelloWorld test',
string db 'Hello, World!',
image_end:
	section .bss
alignb 4
stack resb 1024
stacktop:
memory_end:

Examples of programs for various operating systems

This is a «Hello, world!» program for the DOS operating system.

section .text
org 0x100
	mov	ah, 0x9
	mov	dx, hello
	int	0x21

	mov	ax, 0x4c00
	int	0x21

section .data
hello:	db 'Hello, world!', 13, 10, '$'

An equivalent program for Linux:

global _start

section .text
_start:
	mov	eax, 4 ; write
	mov	ebx, 1 ; stdout
	mov	ecx, msg
	mov	edx, msg.len
	int	0x80   ; write(stdout, msg, strlen(msg));

	xor	eax, msg.len ; invert return value from write()
	xchg eax, ebx ; value for exit()
	mov	eax, 1 ; exit
	int	0x80   ; exit(...)

section .data
msg:	db	"Hello, world!", 10
.len:	equ	$ - msg

An example of a similar program for Microsoft Windows:

global _main
extern _MessageBoxA@16
extern _ExitProcess@4

section code use32 class=code
_main:
	push	dword       ; UINT uType = MB_OK
	push	dword title  ; LPCSTR lpCaption
	push	dword banner ; LPCSTR lpText
	push	dword       ; HWND hWnd = NULL
	call	_MessageBoxA@16

	push	dword       ; UINT uExitCode
	call	_ExitProcess@4

section data use32 class=data
	banner:	db 'Hello, world!', 
	title:	db 'Hello', 

Below is a 64-bit program for Apple OS X that inputs a keystroke and shows it on the screen

global _start

section .data

	query_string:		db	"Enter a character:  "
	query_string_len:	equ	$ - query_string
	out_string:			db	"You have input:  "
	out_string_len:		equ	$ - out_string

section .bss

	in_char:			resw 4

section .text

_start:

	mov	rax, 0x2000004	 	; put the write-system-call-code into register rax
	mov	rdi, 1				; tell kernel to use stdout
	mov	rsi, query_string	; rsi is where the kernel expects to find the address of the message
	mov	rdx, query_string_len	; and rdx is where the kernel expects to find the length of the message 
	syscall

	; read in the character
	mov	rax, 0x2000003		; read system call
	mov	rdi, 				; stdin
	mov	rsi, in_char		; address for storage, declared in section .bss
	mov	rdx, 2				; get 2 bytes from the kernel's buffer (one for the carriage return)
	syscall

	; show user the output
	mov	rax, 0x2000004		; write system call
	mov	rdi, 1				; stdout
	mov	rsi, out_string
	mov	rdx, out_string_len
	syscall

	mov	rax, 0x2000004		; write system call
	mov	rdi, 1				; stdout
	mov	rsi, in_char
	mov	rdx, 2				; the second byte is to apply the carriage return expected in the string
	syscall

	; exit system call
	mov	rax, 0x2000001		; exit system call
	xor     rdi, rdi
	syscall

Прочее

Программа, приведенная в листинге 5, считывает перечень аргументов командной строки, записывает их в память и выводит на экран.

Листинг 5. Программа, которая считывает перечень аргументов командной строки, записывает их в память и выводит на экран.
Строка NASM GAS
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
section .data

; Таблица команд для хранения до
;  10 аргументов командной строки
   cmd_tbl:
      %rep 10
         dd 0
      %endrep

section .text

   global _start

   _start:
; Создание кадра стека
      mov   ebp, esp
; На вершине стека содержится количество
;  аргументов командной строки.
; По умолчанию значение 1
      mov   ecx, 

; Выход, если аргументов больше 10
      cmp   ecx, 10
      jg    _exit

      mov   esi, 1
      mov   edi, 0

; Сохранение аргументов командной
;  строки в таблице команд
   store_loop:
      mov   eax, 
      mov   , eax
      inc   esi
      inc   edi
      loop  store_loop

      mov   ecx, edi
      mov   esi, 0

      extern puts
   
   print_loop:
; Выделение локального пространства
      sub   esp, 4
; функция, повреждающая ecx
      mov   , ecx
      mov   eax, 
      push  eax
      call  puts
      add   esp, 4
      mov   ecx, 
      inc   esi
      loop  print_loop

      jmp   _exit
   
   _exit:
      mov   eax, 1
      mov   ebx, 0
      int   80h
.section .data

// Таблица команд для хранения до
//  10 аргументов командной строки
   cmd_tbl:
      .rept 10
         .long 0
      .endr

.section .text

   .globl _start

   _start:
// Создание кадра стека
      movl  %esp, %ebp
// На вершине стека содержится количество
//  аргументов командной строки.
// По умолчанию значение 1
      movl  (%ebp), %ecx

// Выход, если аргументов больше 10
      cmpl  $10, %ecx
      jg    _exit
   
      movl  $1, %esi
      movl  $0, %edi

// Сохранение аргументов командной
//  строки в таблице команд
   store_loop:
      movl  (%ebp, %esi, 4), %eax
      movl  %eax, cmd_tbl( , %edi, 4)
      incl  %esi
      incl  %edi
      loop  store_loop

      movl  %edi, %ecx
      movl  $0, %esi



   print_loop:
   
//  Выделение локального пространства
      subl  $4, %esp
// функция, повреждающая ecx
      movl  %ecx, -4(%ebp)
      movl  cmd_tbl( , %esi, 4), %eax
      pushl %eax
      call  puts
      addl  $4, %esp
      movl  -4(%ebp), %ecx
      incl  %esi
      loop  print_loop

      jmp   _exit

   _exit:
      movl  $1, %eax
      movl  $0, %ebx
      int   $0x80

В листинге 5 показана конструкция, повторяющая инструкции ассемблера. Достаточно естественным образом она называется конструкцией повторения. Конструкция повторения в GAS начинается с директивы (строка 6). Эта директива закрывается с помощью директивы (строка 8). После в GAS указывается число раз, которое должна быть повторена конструкция, заключенная в . Любые инструкции, размещенные в этой конструкции, эквивалентны написанию этих инструкций раз в отдельных строках.

Например, если количество (count) равно 3:

Это соответствует:

В NASM похожая инструкция используется на этапе предварительной обработки. Она начинается с директивы и заканчивается директивой . После директивы указывается выражение (в отличие от GAS, где за директивой следует количество):

В NASM также есть альтернативный вариант — директива . Так же, как и , она работает на уровне ассемблера и после нее также указывается выражение. Например, приведенная выше конструкция соответствует следующему:

А эта:

эквивалентна следующему:

и обе они эквивалентны:

Для создания в памяти области данных величиной 10 двойных слов в листинге 5 используется директива (или ). После этого из стека поочередно извлекаются аргументы командной строки и сохраняются в области памяти, пока таблица команд не заполнится.

Доступ к аргументам командной строки в обоих ассемблерах осуществляется одинаковым образом. В ESP или на вершине стека хранится количество аргументов командной строки, переданных в программу, по умолчанию указывается 1 (если аргументов командной строки нет). содержит первый аргумент командной строки, которым всегда является название программы, вызванной в командной строке. ,
и далее содержат остальные аргументы командной строки.

Кроме того, обратите внимание, как выполняется доступ к таблице команд в обеих сторонах листинга 5. Здесь для доступа к таблице команд используется режим косвенной адресации памяти (строка 33), вместе со смещением ESI (и EDI) и множителем

Таким образом, в NASM эквивалентно в GAS.

Компиляция и компоновка

NASM компилирует программы под различные операционные системы в пределах x86-совместимых процессоров. Находясь в одной операционной системе, можно беспрепятственно откомпилировать исполняемый файл для другой.

Компиляция программ в NASM состоит из двух этапов. Первый — ассемблирование, второй — компоновка. На этапе ассемблирования создаётся объектный код. В нём содержится машинный код программы и данные, в соответствии с исходным кодом, но идентификаторы (переменные, символы) пока не привязаны к адресам памяти. На этапе компоновки из одного или нескольких объектных модулей создаётся исполняемый файл (программа). Операция компоновки связывает идентификаторы, определённые в основной программе, с идентификаторами, определёнными в остальных модулях, после чего всем идентификаторам даются окончательные адреса памяти или обеспечивается их динамическое выделение.

Для компоновки объектных файлов в исполняемые в Windows можно использовать свободный бесплатно распространяемый компоновщик alink(для 64-х битных программ компоновщик GoLink), а в Linux — компоновщик ld, который есть в любой версии этой операционной системы.

Для ассемблирования файла нужно ввести следующую команду:

nasm -f format filename -o output

Инструкции перехода

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

В командах условного и безусловного () переходов используется по умолчанию ближний тип переходов — . Поэтому при возможности короткого перехода, чтобы не завысить размер программы на лишний байт, необходимо специально указать тип перехода . С версии 0.98.09b были добавлены опции оптимизации -Ox, которые позволяют автоматически оптимизировать размер инструкций перехода, в более ранних версиях или без таких опций минимальный размер программы можно получить только ручной модификацией исходного кода.

История

NASM был создан Саймоном Тэтхемом совместно с Юлианом Холлом и в настоящее время развивается небольшой командой разработчиков на SourceForge.net. Первоначально он был выпущен согласно его собственной лицензии, но позже эта лицензия была заменена на GNU LGPL после множества проблем, вызванных выбором лицензии. Начиная с версии 2.07 лицензия заменена на «упрощённую BSD» (BSD из 2 пунктов).

NASM может работать на платформах, отличных от x86, таких как SPARC и PowerPC, однако код он генерирует только для x86 и x86-64.

NASM успешно конкурирует со стандартным в Linux- и многих других UNIX-системах ассемблером gas. Считается, что качество документации у NASM выше, чем у gas. Кроме того, ассемблер gas по умолчанию использует AT&T-синтаксис, ориентированный на процессоры не от Intel, в то время как NASM использует вариант традиционного для x86-ассемблеров Intel-синтаксиса; Intel-синтаксис используется всеми ассемблерами для под DOS/Windows, например, MASM, TASM, fasm.

Синтаксис языка

В NASM используется Intel-синтаксис записи инструкций. Предложение языка ассемблера NASM (строка программы) может состоять из следующих элементов:Метка, Инструкция, Операнды, Комментарий.

Операнды отделяются между собой запятой. Перед строкой и после инструкции можно использовать любое количество пробельных символов. Комментарий начинается с точки с запятой, а концом комментария считается конец строки. В качестве инструкции может использоваться команда или псевдокоманда (директива компилятора). Если строка очень длинная, то её можно перенести на следующую, используя обратный слеш ( \ ), подобно тому, как это делается в языке Си.

Структура программы на MIPS-ассемблере

директивыметкикомментарииинструкциипсевдоинструкции

Основные директивы

  • объявляет символ sym глобальным и позволяет обращатся к нему из других файлов;
  • объявляет, что данные, которые хранятся в sym имеют размер size, и делает sym глобальной меткой (см. предыдущую директиву);
  • сохраняет строку str в памяти, не добавляя нулевой символ (\0) в конец;
  • сохраняет строку str и добавляет в конец нулевой символ (\0);
  • последовательно сохраняет в памяти байты b1, b2, …, bn;
  • последовательно сохраняет в памяти 16-битные значения h1, h2, …, hn;
  • последовательно сохраняет в памяти 32-битные значения w1, w2, …, wn;
  • последовательно сохраняет в памяти 64-битные значения dw1, dw2, …, dwn;
  • сохраняет в памяти числа с плавающей запятой f1, f2, …, fn;
  • сохраняет в памяти числа с плавающей запятой (двойная точность) d1, d2, …, dn;
  • выделить n байт в данном сегменте данных;
  • выровнять все следующие данные до 2^n байт.

Регистры

  • $zero ($0) – регистр, всегда содержащий значение 0 и доступный только для чтения;
  • $at ($1) – временный регистр процессора;
  • $v0-$v1 ($2-$3) – для результатов, возвращаемых функциями;
  • $a0-$a3 ($4-$7) – для аргументов функций;
  • $t0-$t9 ($8-$15, $24-$25) – для временных данных, можно использовать как угодно;
  • $s0-$s8 ($16-$23, $30) – для постоянных данных, можно использовать как угодно;
  • $k0-$k1 ($26-$27) – зарезервировано для ядра операционной системы;
  • $gp ($28) – поинтер для глобальных переменных, практически не используется;
  • $sp ($29) – поинтер стека, его значение всегда равно верхнему адресу стека;
  • $ra ($31) – бог солнца адрес инструкции, из которой была вызвана функция;
  • $f0 – для результатов, возвращаемых функцями, с плавающей запятой;
  • $f4, $f6, $f8, $f10, $f16, $f18 – для временных данных с плавающей запятой;
  • $f12, $f14 – для параметров функций с плавающей запятой

Формат выходных файлов

NASM поддерживает множество форматов выходных файлов, среди них:

  • bin — файл произвольного формата, определяемого только исходным кодом. Пригоден как для файлов данных, так и для модулей с исполняемыми кодами — например, системных загрузчиков, образов ПЗУ, модулей операционных систем, драйверов .SYS в MS-DOS или исполняемых файлов .COM.
  • obj — объектный модуль в формате OMF, совместимый с MASM и TASM.
  • win32 и win64 — объектный модуль для 32- и 64-битного кода, совместимый с Win32- и Win64-компиляторами Microsoft.
  • aout — объектный модуль в варианте формата a.out, использовавшемся в ранних Linux-системах.
  • aoutb — версия формата a.out для BSD-совместимых операционных систем.
  • coff — объектный модуль в формате COFF, совместимом с компоновщиком из DJGPP.
  • elf32 и elf64 — объектный модуль в форматах ELF32 и ELF64, используемых в Linux и Unix System V, включая Solaris x86, UnixWare и SCO Unix.

Формат выходного файла можно задать с помощью ключа командной строки -f. Форматы могут расширять синтаксис некоторых инструкций и добавлять собственные инструкции.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *