Часто задаваемые вопросы по использованию ассемблера в юниксах.
Часто задаваемые вопросы по использованию ассемблера в юниксах.
Q: Какие ассемблеры бывают в юникс и где их взять?
Q: У юникса есть API ?
Q: Как передаются параметры системных вызовов?
Q: Где узнать номера системных вызовов?
Q: Где найти описания системный вызовов или функций libc?
Q: Покажите маленькую программку типа "Hello,world!"
Q: Как ее запустить (слинковать, ассемблировать)?
Q: А покажите "Hello,world!" с GUI.
Q: Как мне получить аргументы командной строки и переменные окружения?
Q: Какие порекомендуете ссылки?
Q: Какие ассемблеры бывают в юникс и где их взять?
A: Ассемблер as (GNU AS) считается стандартным, также очень популярен ассемблер NASM. Есть также
хороший ассемблер FASM.
AS: Во FreeBSD as и все утилиты из пакета binutils присутвуют в системе по
умолчанию. В Linux потребуется дополнительно установить пакет binutils
(формат пакета зависит от дистрибутива).
Важно! AS использует синтаксис AT&T, а NASM и FASM синтаксис Intel.
Q: У юникса есть API ?
A: Да. Называется это системные вызовы (syscalls). Осуществляются
они путем вызова прерывания номер 0x80. Кроме того, практически везде действует "lcall $7,$0" ("call 7:0"
в синтаксисе интел) как его полный аналог. Так же в системе обязательно присутствует библиотека libc.
Q: Как передаются параметры системных вызовов?
A: Во FreeBSD параметры передаются через стек, сначала последний, затем предпоследний и первый
параметр помещается в стек последним. Так же при испотьзовании прерывания необходимо поместить в стек
любое 32-битное значение. В Linux через регистры по порядку первый в EBX, и тд - ECX, EDX, ESI,
EDI, EBP. Если количество параметров больше 6, то в памяти формируется структура с параметрами и
в EBP помещается адрес этой стурктуры. В EAX в обоих случаях номер системного вызова. Если хотите
использовать функции из libc, то передача параметров при этом осуществляется так как принято в
Си (в мире Windows такой способ известен как CDECL). Результат всегда возвращается в EAX.
Q: Где узнать номера системных вызовов?
A: Самый простой способ для FreeBSD(должны быть установлены исходники системы!) -
смотреть файл /usr/srs/sys/kern/syscalls.master. Там и номера и параметры.
Способ для Linux - /usr/src/linux/arch/i386/kernel/syscall_table.S или файл entry.S из того же
каталога для версий 2.4 и 2.6.
Предполгается что /usr/src/linux/ это каталог с исходными текстами ядра Linux
Q: Где найти описания системный вызовов или функций libc?
A: Как обычно в юникс - справочник man (например man 2 write) или info.
Q: Покажите маленькую программку типа "Hello,world!"(скачать примеры)
A:
Вариант 1 (as,FreeBSD) hello.s:
.data
msg:
.asciz "Hello,world!\n"
msg_len= .-msg
.text
.global _start
_start:
pushl $msg_len # константа, длинна строки
pushl $msg # адрес строки "Hello,world!\n"
pushl $1 # дескриптор стандартного вывода
movl $4,%eax # 4 - номер системного вызова write
push %eax # здесь может быть любое 32х битное
int $0x80 # выполнить систменый вызов
addl $16,%esp # восстанавливаем стек
pushl $0 # код выхода
movl $1,%eax # 1 - номер системного вызова sys_exit
push %eax
int $0x80
Вариант 2 (fasm,FreeBSD) hello.asm:
format ELF
section '.text' executable
public _start
_start:
push msg_len ; size of message
push msg ; offset of message
push 1 ; stdout
mov eax,4 ; 4 = sys_write
push eax
int 0x80
add esp,4*4 ; очищаем за собой стэк
xor eax,eax
push eax ; код выхода
inc eax ; 1 = sys_exit
int 0x80
section '.data' writeable
msg db "Hello world",0
msg_len = $-msg
Вариант 3 (as, все платформы, используем libc) hello-world.s:
.data
msg: .string "Hello world."
.text
.globl main
main:
pushl $msg
call puts
xorl %eax, %eax
pushl %eax
call exit
Вариант 4 (nasm, Linux) hello-nasm.asm:
SECTION .data ; секция данных
msg: db "Hello World",10 ; строка для вывода, 10=cr
len: equ $-msg ; "$" означает "здесь"
; len это значение, не адрес
SECTION .text ; секция код
global _start ; делаем метку доступной для линкера
_start: ; стандартный вход
mov edx,len ; arg3, длинна строки для вывода
mov ecx,msg ; arg2, адрес строки
mov ebx,1 ; arg1, стандартный вывод
mov eax,4 ; передаем команду sysout в int 80 hex
int 0x80 ; прерываение 80 hex, вызов kernel
mov ebx,0 ; код выхода, 0=normal
mov eax,1 ; команда выход для kernel
int 0x80 ; прерывание 80 hex, вызов kernel
Q: Как ее запустить (слинковать, ассемблировать)?(скачать примеры)
A:
Вариант 1:
as hello.s -o hello.o
ld -s hello.o -o hello
./hello
Вариант 2:
fasm hello.asm hello.o
ld -s hello.o -o hello
./hello
Вариант 3:
as hello-world.s -o hello-world.o
gcc -Wl,-s hello-world.o -o hello-world
./hello-world
Вариант 4:
nasm -f elf hello-nasm.asm
ld -s hello-nasm.asm -o hello-nasm
./hello-nasm
Q: А покажите "Hello,world!" с GUI.(скачать примеры)
A: Простейшие примеры:
Вариант 1 (as, все платформы) hello-gui.s:
# as hello-gui.s -o hello-gui.o
# gcc -Wl,-s hello-gui.o -lX11 -L/usr/X11R6/lib -o hello-gui
# ./hello-gui
.data
prDisplay: .long 0 # указатель на структуру Display
nScreenNum: .long 0 # номер экрана
nWnd: .long 0 # ID окна
rEvent: .space 96,0 # буфер для получения события (сообщения)
# вообщето все намного сложней
# но длинное слово (32бит)со смещением 0
# обычно содержит тип события
prGC: .long 0
msgHello: .asciz "Hello, world!" # какое-то непереводимое ругательство
msgHelloLen=.-msgHello-1
.text
.global main # без libc мы обойтись не сможем!
main:
# устанавлиываем связь с сервером
pushl $0
call XOpenDisplay
addl $4,%esp
movl %eax,prDisplay # теперь prDisplay содержит адресс структуры
# или равен 0 в случае ошибки
# получаем номер основного экрана
pushl %eax
call XDefaultScreen
addl $4,%esp
movl %eax,nScreenNum
# создаем окно
pushl nScreenNum
pushl prDisplay
call XWhitePixel
addl $8,%esp
pushl %eax # белый пиксел )
pushl nScreenNum
pushl prDisplay
call XBlackPixel
addl $8,%esp
pushl %eax # черный пиксел )
pushl $5 # толщина рамки
pushl $100 # высота окна
pushl $100 # ширина окна
pushl $0 # x
pushl $0 # y
pushl nScreenNum
pushl prDisplay
call XRootWindow
addl $8,%esp
pushl %eax # окно родитель
pushl prDisplay # дисплей
call XCreateSimpleWindow
addl $36,%esp
movl %eax,nWnd # номер окна
# устанавливаем события обрабатываемые прграммой
pushl $(1 | (1 << 15)) # это у нас ExposureMask OR KeyPressMask
pushl nWnd
pushl prDisplay
call XSelectInput
addl $12,%esp
# показываем окно (проще некуда ;-) )
pushl nWnd
pushl prDisplay
call XMapWindow
addl $8,%esp
# самое главное
# цикл получения и обработки сообщений
wloop:
pushl $rEvent # адрес буфера
pushl prDisplay # дисплей
call XNextEvent
addl $8,%esp
movl rEvent,%eax # !!! так делать не стоит
# но здесь сойдет
cmpl $12,%eax
je _Expose
cmpl $2,%eax
je _KeyPress
jmp wloop
_Expose:
# запрос на перерисовку
# получаем графический контекст
pushl $0
pushl $0
pushl nWnd
pushl prDisplay
call XCreateGC
addl $16,%esp
movl %eax,prGC
# а кто у на сегодня "черный"?
pushl $0
pushl prDisplay
call XBlackPixel
addl $8,%esp
# устанвливаем цвет которым рисуем
pushl %eax
pushl prGC
pushl prDisplay
call XSetForeground
addl $12,%esp
# рисуем текст
pushl $msgHelloLen
pushl $msgHello
pushl $50
pushl $10
pushl prGC
pushl nWnd
pushl prDisplay
call XDrawString
addl $28,%esp
# освобождаем графический контекст
pushl prGC
pushl prDisplay
call XFreeGC
addl $8,%esp
jmp wloop
_KeyPress:
# кто-то нажал на кнопку
pushl prDisplay
call XCloseDisplay
call exit
Вариант 2 (fasm, все платформы) gui_fasm.asm:
; fasm gui_fasm.asm
; gcc -Wl,-s gui_fasm.o -lX11 -L/usr/X11R6/lib -o gui_fasm
; ./gui_fasm
format ELF
public main
extrn XOpenDisplay
extrn XDefaultScreen
extrn XWhitePixel
extrn XBlackPixel
extrn XRootWindow
extrn XCreateSimpleWindow
extrn XSelectInput
extrn XMapWindow
extrn XNextEvent
extrn XCreateGC
extrn XSetForeground
extrn XDrawString
extrn XFreeGC
extrn XCloseDisplay
extrn exit
section '.data' writeable
prDisplay dd 0 ; указатель на структуру Display
nScreenNum dd 0 ; номер экрана
nWnd dd 0 ; ID окна
prGC dd 0
rEvent rb 96 ;буфер для получения события (сообщения)
msg db 'Hello,world!',0x0
msg_size = $-msg
section '.text' executable
main:
;устанавлиываем связь с сервером
push 0
call XOpenDisplay
add esp,4
mov [prDisplay],eax ;prDisplay содержит адресс структуры
; получаем номер основного экрана
push eax
call XDefaultScreen
add esp,4
mov [nScreenNum],eax
; создаем окно
push [nScreenNum]
push [prDisplay]
call XWhitePixel
add esp,8
push eax ; белый пиксел
push [nScreenNum]
push [prDisplay]
call XBlackPixel
add esp,8
push eax ; черный пиксел
push 5 ; толщина рамки
push 100 ; высота окна
push 100 ; ширина окна
push 0 ; x
push 0 ; y
push [nScreenNum]
push [prDisplay]
call XRootWindow
add esp,8
push eax ; окно родитель
push [prDisplay]
call XCreateSimpleWindow
add esp,36
mov [nWnd],eax
; устанавливаем события обрабатываемые прграммой
mov eax,1
shl eax,15
or eax,1
push eax
push [nWnd]
push [prDisplay]
call XSelectInput
add esp,12
; показываем окно
push [nWnd]
push [prDisplay]
call XMapWindow
add esp,8
; цикл получения и обработки сообщений
wloop:
push rEvent ; адрес буфера
push [prDisplay] ; дисплей
call XNextEvent
add esp,8
mov eax,rEvent
mov eax,[eax]
cmp eax,12
je _Expose
cmp eax,2
je _KeyPress
jmp wloop
_Expose:
; запрос на перерисовку
; получаем графический контекст
push 0
push 0
push [nWnd]
push [prDisplay]
call XCreateGC
add esp,16
mov [prGC],eax
; "черный"
push 0
push [prDisplay]
call XBlackPixel
add esp,8
; устанвливаем цвет которым рисуем
push eax
push [prGC]
push [prDisplay]
call XSetForeground
add esp,12
; рисуем текст
push msg_size
push msg
push 50
push 10
push [prGC]
push [nWnd]
push [prDisplay]
call XDrawString
add esp,28
; освобождаем графический контекст
push [prGC]
push [prDisplay]
call XFreeGC
add esp,8
jmp wloop
_KeyPress:
push [prDisplay]
call XCloseDisplay
call exit
Q: Как мне получить аргументы командной строки и переменные окружения?
A: В точке входа в программу (_start:) в стеке содержатся следующие данные
AT&T | Intel | Описание |
(%esp) | dword [esp] | n, количество аргументов командной строки |
4(%esp) | dword [esp+4] | адрес первого аргумента, имя программы |
4*2(%esp) | dword [esp+4*2] | адрес второго аргумента |
... | ||
4*n(%esp) | dword [esp+4*n] | адрес последнено аргумента |
4*n+4(%esp) | dword [esp+4*n+4] | нулевой адрес (NULL) |
4*n+8(%esp) | dword [esp+4*n+8] | адрес переменных окружения |