Информационные технологииStfw.Ru 🔍

Часто задаваемые вопросы по использованию ассемблера в юниксах.

Ошибки - это бич любой программы. Дмитрий Бондарев
🕛 05.09.2006, 12:16

Часто задаваемые вопросы по использованию ассемблера в юниксах.

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&TIntelОписание
(%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]адрес переменных окружения


Assembler   Теги:

Читать IT-новости в Telegram

Читайте также:

Синтаксис ассемблера

Информационные технологии
Мы в соцсетях ✉