Online video hd

Смотреть красавицы видео

Официальный сайт mydebut 24/7/365

Смотреть видео бесплатно

dementiy 10.07.2010 02:13

CodingАссемблер в Linux. Часть первая

“Самый лучший способ изучить новый язык – это сразу начать писать на нем программы” - Брайан Керниган, Деннис Ритчи

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

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#max.s - Find the maximum value
.section .data
array:
.int 7, 9, 24, 15, 11, 13
size:
.int 6
.section .text
.globl _start
_start:
movl array, %ebx
movl $1, %eax
for:
cmpl size, %eax
jb forcode
jmp exit
forcode:
cmpl %ebx, array(, %eax, 4)
cmova array(, %eax, 4), %ebx
incl %eax
jmp for
exit:
movl $1, %eax
int $0x80



 1
2
3
4
5
6
7
8
9
10
11
12
//max.c - Find the maximum value
#include <stdlib.h>
int main(void) {
int array
= {7, 9, 24, 15, 11, 13};
int i, max, size = 6;
max = array<0>;
for (i = 1; i &lt; size; i++)
if (max &lt; array<i>)
max = array</i><i>;
exit(max);
}</i>



Итак попробуем построчно разобрать программу.
Первая строка начинается с символа «#», который, как несложно догадаться, означает, что последующий текст в строке комменатрий. Также есть и многострочные комментарии, которые задаются также, как и в С — /* текст комментария */.
Далее в программе идет секция данных «.section .data». Как написано в документации к GAS, секция это диапазон последовательных адресов, данные в которой имеют специальное предназначение. Общий формат для объявления секции такой:
.section name

где name это имя секции, которое и указывает предназначение секции. Основными секциями являются:
.data — секция инициализированных данных (например переменных или констант);
.bss — секция неинициализированных данных;
.text — секция кода.Директиву «.section» в программе можно опускать и писать сразу название секции, например вместо «.section .data» писать просто «.data». Также вы можете оставлять секцию пустой, тогда она просто не включается в исполняемый файл и соответственно места в памяти не занимает (рис.1).


Рис.1. Секция данных не включена в исполняемый файл

Для нашей программы в секции данных мы объявляем массив целочисленных значений array и размер этого массива size:
1
2
3
4
array:
.int 7, 9, 24, 15, 11, 13
size:
.int 6


array и size это всего лишь метки, которые предназначены для того, чтобы к данным можно было обратиться по имени, другими словами метка это адрес первого элемента (если элементов данных несколько, то они перечисляются через запятую), который за ней следует. Сами метки не занимают места в памяти. Вы можете и не задавать имя метки, но тогда для обращения к данным вам придется вычислять их адрес, который может изменяться.
Теперь поговорим о типах данных. Основные типы данных представлены ниже:
.ascii — текстовая строка;
.asciz — текстовая строка с добавлением нулевого символа в конец строки;
.byte — значение длиной в один байт;
.short (.word) — значение длиной в два байта (слово);
.int (.long) — значение длиной в четыре байта (двойное слово)
.quad — значение длиной в восемь байт;
.octa — значение длиной в шестнадцать байт;
.float (.single) — вещественное число одинарной точности;
.double — вещественное число двойной точности.Для текстовых строк, заданных с помощью директивы «.ascii», следующие обозначения эквивалентны:
1
2
3
4
str1:                                    
.ascii "a", "b", "c"
str2:
.ascii "abc"


Но при использовании директивы «.asciz» они уже не будут эквивалентными, так как добавляется признак конца строки, т.е. в первом случае он будет добавлен после каждого символа, а во втором только после последнего символа:


Рис. 2. Использование директивы «.asciz»

Общий формат для объявления «переменной» такой:
1
2
value:
.data_type num <, num_2, num_3, .......>


Перед шестнадцатиричными числами ставится префикс «0x», перед восмиричными «0», перед двоичными «0b», десятичные числа пишутся как есть, но не должны начинаться с нуля.
Далее идет секция кода «.section .text». Основное отличие секции кода от секции данных заключается в том, что она имеет право на исполнение, то есть данные в ней интерпретируются, как инструкции. Вот небольшой пример:
1
2
3
4
5
6
7
8
.section .text
.globl _start
_start:
.byte 0xB8
.int 1
.byte 0xBB
.int 0
.byte 0xCD, 0x80


На первый взгляд нет ни одной инструкции, но на самом деле это не так, в чем можно убедиться, взглянув на код в HT Editor:


Рис. 3. Интерпретация данных, как инструкций

В этом примере инструкции задаются в виде опкодов (ОпКод — Код Операции).
Про использование опкодов можно почитать в цикле статей Aquila - Заклинание кода

Возвращаясь к нашей программе, далее по коду «.globl _start». Директива «.globl» объявляет метку, которая будет доступна из внешних приложений. Для того, чтобы линковщик знал откуда начинается программа необходимо указать точку входа (entry point). По умолчанию этой точкой служит метка «_start» (при использовании GCC - «main»). Если линковщик не может найти точку входа, то он выведет следующее предупреждение: «ld: warning: cannot find entry symbol _start; defaulting to 0000000008048074».
Вот мы и добрались непосредственно до самого кода. Для начала следует упомянуть о способах адресации. Выделяют следующие способы (говорят еще режимы) адресации:
непосредственная адресация — значение (константа) напрямую указывается в инструкции и не может быть изменено во время выполнения программы, например:
1
2
movl $1, %eax  #поместить 1 в регистр %eax
addl $8, %esp #добавить 8 к регистру %esp


регистровая адресация — при регистровой адресации источником служит регистр, например:
1
2
movl %eax, %ebx   #поместить содержимое регистра %eax в регистр %ebx
xchg %eax, %ebx #обменять содержимое регистров %eax и %ebx


прямая (абсолютная) адресация — при прямой адресации адрес операнда задается в виде именованного адреса (метки), например:
1
2
3
4
value:
.int 1
...
movl value, %eax #поместить 1 в регистр eax


Если нужно поместить адрес элемента, то перед «value» нужно поставить знак долара «$»
индексная адресация — при индексной адресации используется смещение, индексный регистр и множитель, который может принимать значения 1, 2 или 4 (байт, слово или двойное слово). Общая формула для расчета итогового адреса такова:

базовый_адрес(смещение, индекс, множитель)
итоговый_адрес = базовый_адрес + смещение + индекс * множитель

Любое из значений может быть опущено, если опущены все значения, то остается только базовый_адрес и, таким образом, получается прямая адресация.
косвенная адресация — при косвенной адресации берется значение по адресу, указанному в регистре, например:
1
2
3
4
5
value:
.int 1
...
movl $value, %eax #поместить адрес value в регистр %eax
movl (%eax), %ebx #поместить значение по адресу в %eax в регистр %ebx


базовая адресация — базовая адресация аналогична косвенной, за исключением лишь того, что перед (%register) указывается константа, которая прибавляется к этому адресу, т.е. const(%register) = (%register + const).Итак с адресацией вроде бы разобрались. Теперь снова вернемся к программе. Мы устанавливаем максимальный размер элемента «max» равным первому элементу массива «array<0>». На ассемблере это делается с помощью инструкции «movl array, %ebx» (прямая адресация). Тут мы встречаемся с нашей первой инструкцией — MOV. Инструкция MOV (от английского Move) это инструкция пересылки данных из первого операнда (источник) во второй (приемник). Следует отметить, что мы используем синтаксис AT&T;, при использовании синтаксиса Intel первый операнд служит приемником, а второй источником, это касается не только инструкции MOV, но и многих других. Формат инструкции следующий:
movx источник, приемник или, чтобы было проще запомнить movx что, куда

«x» служит модификатором и указывает на размер пересылаемого значения. Он может принимать следующие значения:
b — значение размером в 1 байт;
w — значение размером в 2 байта;
l — значение размером в 4 байта;
q — значение размером в 8 байт.В качестве источника может выступать непосредственное значение (константа), ячейка в памяти или регистр, в качестве приемника ячейка в памяти или регистр. Действительно, инструкция вроде «movl $1, $2» (значение — значение) не имеет смысла. Также не нужно забывать о том, что размеры источника и приемника должны быть эквивалентными (вы же не покупаете обувь на несколько размеров меньше, нога ведь не влезет).
Итак, с тем, что первый элемент массива помещается в регистр... стоп, а что такое регистр? Wikipedia говорит - «Регистр процессора — сверхбыстрая память внутри процессора, предназначенная прежде всего для хранения промежуточных результатов вычисления (регистр общего назначения) или содержащая данные, необходимые для работы процессора — смещения базовых таблиц, уровни доступа и т.д. (специальные регистры)». Итак, регистры принято считать быстрой памятью. Регистры делятся на:
регистры общего назначения (РОН) — EAX (Accumulator), EBX (Base Register), ECX (Counter Register), EDX (Data Register);
регистры указателей — EIP (Instruction Pointer), ESP (Stack Pointer), EBP (Base Pointer);
регистры индексов — ESI (Source Index), EDI (Destination Index).
регистр флагов — EFLAGS;
сегментные регистры — CS, DS, SS, FS, ES, GS;Рассмотрим регистры общего назначения на примере регистра EAX. Структура регистра EAX (EBX, ECX, EDX) следующая:



Такая структура РОН и отличает их от остальных регистров, т.е. можно обращаться не только ко всему регистру, но и к отдельным его частям, например:
1
2
3
4
movb $0xff, %al             #поместить FFh в %al
movb $0xff, %ah #поместить FFh в %ah
movw $0xffff, %ax #поместить FFFFh в %ax
movl $0xffffffff, %eax #поместить FFFFFFFFh в %eax


Регистр EIP содержит адрес следующей инструкции, которая подлежит выполнению.
Регистр ESP указывает на вершину стека. Стек — это область памяти, работа с которой организована по принципу FILO (First Input Last Output — первым вошел, последним вышел). В основном стек используется для передачи аргументов функциям.
Регистр EFLAGS это 32-битный регистр, в котором 17 бит являются флагами. Если флаг установлен, то бит принимает значение 1 и наоборот. В большинстве случаев проверяются следующие флаги:
ZF (Zero flag) — устанавливается, если результатом арифметической или логической операции был нуль;
OF (Overflow flag) — предназначен для работы со знаковыми числами и устанавливается, если результат операции выходит за пределы допустимого значения;
PF (Parity flag) — устанавливается, если в резульатет оперции проверяемое значение содержит четное число бит со значением 1;
SF (Sign flag) — используется при работе со знаковыми числами и указывает на изменение знака числа;
CF (Carry flag) — предназначен для работы с беззнаковыми числами и устанавливается, если в результате математической операции произошло переполнение;Некоторые инструкции связаны с определенными регистрами, например инструкция LOOP связана с регистром «%ecx». Формат инструкции LOOP:
loop адрес

где адрес это место в программе куда следует перейти. Регистр «%ecx» выступает в роли счетчика, в него помещается значение равное количеству итераций, которое необходимо совершить, например:
1
2
3
4
5
6
movl $0, %eax
movl $10, %ecx
label:
addl $5, %eax
loop label
...


После выполнения всех инструкций регистр «%eax» будет содержать «50». Каждое выполнение инструкции «loop» уменьшает значение регистра «%ecx» на единицу и сравнивает его с нулем (именно в таком порядке: уменьшить — сравнить), если значение «%ecx» равно нулю, то продолжить выполнение следующей за «loop» инструкции, иначе перейти на метку.
Остальные регистры будут описаны по мере их использования.
Снова вернемся к программе. Регистр «%eax» выступает в роли счетчика цикла и предварительно устанавливается равным единице. Далее идет сам цикл. Для того чтобы лучше понять работу цикла «for» представим его в виде цикла «while» на С:
1
2
3
4
5
i = 1;
while (i &lt; size) {
//тело цикла
i++;
}


Что же здесь происходит? Сначала мы устанавливаем счетчик цикла, затем начинается цикл с проверки условия выхода из него, если оно истино, то выполняются инструкции в теле цикла и увеличивается счетчик, если оно ложно, то происходит выход из цикла. Итак, счетчик цикла мы установили «movl $1, %eax». Далее устанавливается метка «for» (можно дать ей любое название), которая является началом цикла. Теперь нам надо сравнить счетчик с размером массива, чтобы знать следует ли продолжать выполнение цикла. За сравнение значений отвечает инструкция CMP (Compare). Формат инструкции:
cmp операнд_2, операнд_1

Реально происходит вычитание второго операнда (операнд_2) из первого (операнд_1) и, в зависимости от результата, устанавливаются флаги в регистре EFLAGS (CF, OF, SF, ZF, AF, и PF). Как же воспользоваться этим результатом? А для этого специально предназначены инструкции условных переходов. Формат этих инструкций такой:
jxx адрес

где «xx» — от 1 до 3 кодовых символа перехода (таблица 1), адрес — место в программе куда следует перейти и обычно задается меткой.


Таблица 1. Инструкция JXX

Есть два типа условных переходов:
short jumps — короткие переходы используют 8-битное смещение;
near jumps — ближние переходы используют либо 16-битное смещение, либо 32-битное смещение.Инструкция «jxx» не поддерживает дальние переходы (far jumps), поэтому в документации Intel можно найти такое решение данной проблемы:
1
2
3
4
5
...
jxx beyond
jmp farlabel
beyond:
...


Мы используем инструкцию «jb» (jump if below) для перехода к телу цикла (метке «forcode»), если значение в регистре %eax все еще меньше size, в противном случае выполняется следующая за «jl» инструкция — JMP (Jump). JMP это инструкция безусловного перехода, то есть переход происходит всегда. Инструкция JMP является аналогом GOTO в языках высокого уровня. Формат инструкции такой:
jmp адрес

где адрес также, как и в инструкции «jxx» место в программе (адрес в памяти) куда следует перейти.
И в который раз возращаясь к программе. Итак мы перешли в тело цикла, где должно происходить сравнение текущего максимального элемента (который хранится в регистре «%ebx») с элементом массива (к элементам массива мы обращаемся используя индексную адресацию). Это делает инструкция «cmpl %ebx, array(, %eax, 4)». Теперь, если значение в регистре «%ebx» меньше, чем значение элемента массива, то «%ebx» присваивается новый максимальный элемент. Можно было бы воспользоваться уже описанными инструкциями, но есть специально предназначенная инструкция для условного перемещения — CMOV (Conditional Move). Формат инструкции такой:
cmovxx источник, приемник

где «xx» - от 1 до 3 кодовых символа, которые определяют условие перемещения значения из источника в приемник (модификаторы принимают те же значения, что и для инструкции «jxx», см. таблицу 1).
После того, как мы переместили или не переместили значение в регистр «%ebx» мы наращиваем счетчик и пригаем на начало цикла, где все повторяется заново. Счетчик наращивается с помощью инструкции инкремента — INC (Increment). Формат инструкции:
inc регистр или incx значение_в_памяти

Для регистра модификатор (модификатор принимает те же значения, что и для инструкции «mov») указывать необязательно, но для значения в памяти нужно. Есть и обратная инструкция, которая уменьшает значение на единицу — DEC (Decrement). Формат инструкции такой же, как и у INC:
dec регистр или decx значение_в_памяти

Вот мы и подошли к развязке. В конце концов цикл завершает свое выполнение и осуществляется переход на метку «exit». Нам остается только завершить выполнение программы, для этого используется системный вызов «exit». В регистр «%eax» помещается номер системного вызова, в «%ebx» возвращаемое значение, а затем выполняется прерывание с номером «80h». Номера системных вызовов можно найти в исходных текстах ядра, а именно в файле «arch/x86/kernel/syscall_table_32.S». Вот замечательный ресурс, где можно посмотреть какие значения должны находиться в регистрах для каждого системного вызова. На этом пока все.

P.S. Получилось не так, как хотелось, довольно скомкано и возможно запутано. В дальнейшем надеюсь будет более ясная мысль и лучшее изложение текста (если конечно эта тема еще интересует). Если Вы найдете в тексте какие-то недочеты или грубые ошибки, то обязательно напишите в комментариях, будем исправлять вместе =). Ну и как всегда pdf'ка с текстом статьи.


Тэги: assembler AT&T gas
+ 37 -
Похожие Поделиться

s2h 10.07.2010 03:12 #
+ 1 -
без комментариев
antigluk 10.07.2010 09:36 #
+ 2 -
Огого. Вилинуксую =)
Спасибо за статью, может начну асм изучать в свободное время.
mrded 10.07.2010 11:52 #
+ 1 -
статья как-раз когда я уже сдал весь асм в универе))
вот буквально на пол года раньше, ваще круто былобы)

спасибо, полезная статья
inst 10.07.2010 12:53 #
+ 0 -
плюсую :)
razum2um 10.07.2010 11:53 #
+ 1 -
Вилинукс еще торт ;)
K900 10.07.2010 12:50 #
+ 0 -
Нифигасебе оО
Плюсую, но пока лучше с питоном помучаюсь =)
leonike 10.07.2010 15:29 #
+ 2 -
Питон один из тех языков, которые доставляют удовольствие, а не мучения :)
Во всяком случае для меня.
K900 10.07.2010 15:30 #
+ 0 -
Просто я его нифига не знаю =)
razum2um 10.07.2010 12:59 #
+ 0 -
Товарищ автор, а можно Вас еще помучить вопросами? Разбирали пример с х32 регистром, можете показать что-нибудь х64-специфичное?
Или коротенькая заметка о взаимодействии с пользователем - std{in,out}, м? ;)
dementiy 11.07.2010 00:45 #
+ 1 -
Извините пожалуйста, что заставил ждать (лето, хорошая погода, баскетбол и чемпионат мира дали о себе знать). К сожалению с 64 разрядными регистрами я не работал, но если хотите могу покапать эту тему. На счет stdin и stdout. Я обязательно включу работу с ними (и не только) в следующую статью, а также о работе с числами (мы, как Вы миогли заметить, рассматривали только целые беззнаковые числа), функциями, может быть еще макросы, в общем как пойдет =). А вообще, если интересно и Вы умете писать хотя бы самые простые программы на С, то у GCC есть опция "-S" (полностью команда будет "gcc -S file.c"), которая выдает исходный код на ассемблере (правда используются не системные вызовы, а библиотека С).
razum2um 11.07.2010 12:09 #
+ 0 -
hello world
.file "1.c"
.section .rodata
.LC0:
.string "Hello, world!"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
movq %rsp, %rbp
.cfi_offset 6, -16
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
leave
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Gentoo 4.4.2 p1.0) 4.4.2"
.section .note.GNU-stack,"",@progbits

Я в шоке ;) Потому и спрашиваю о std{in,out}, ибо догадываюсь, что руками код короче и проще....
dementiy 11.07.2010 13:30 #
+ 0 -
Например с использованием библиотеки С:

.section .data
output:
.asciz "Hello world\n"
.section .text
.globl _start
_start:
pushl $output #помещаем адрес строки в стек
call printf #вызываем printf
addl $8, %esp #выравниваем стек
pushl $0
call exit

И без использование библиотеки, через системный вызов write:

.section .data
output:
.asciz "Hello world\n"
.equ length, . - output - 1
.section .text
.globl _start
_start:
movl $4, %eax #номер системного вызова write (4) в %eax
movl $1, %ebx #номер файлового дескриптора (stdout - 1) в %ebx
movl $length, %ecx #длина строки в %ecx
movl $output, %edx #адрес строки в %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80
razum2um 11.07.2010 13:45 #
+ 0 -
чьерт, чувствую себя дураком, даже собрать не могу
razum2um@localhost /tmp $ cat h.as
.section .data
output:
.asciz "Hello world\n"
.section .text
.globl _start
_start:
pushl $output #помещаем адрес строки в стек
call printf #вызываем printf
addl $8, %esp #выравниваем стек
pushl $0
call exit

razum2um@localhost /tmp $ as h.as
h.as: Assembler messages:
h.as:7: Error: suffix or operands invalid for `push'
h.as:10: Error: suffix or operands invalid for `push'

Честно говоря я занимался ассемблером последний раз года 3 назад, когда сидел на винде и трассировал в SoftIce (sic!) shareware для небезызвестных целей.. :)
Посему никакого продуктивного опыта...
razum2um 11.07.2010 13:49 #
+ 0 -
второй пример вываливается также. он не понимает pushl
razum2um 11.07.2010 13:52 #
+ 0 -
пардон.. второй компилится, но
razum2um@localhost /tmp $ cat h.as
.section .data
output:
.asciz "Hello world\n"
.equ length, . - output - 1
.section .text
.globl _start
_start:
movl $4, %eax
movl $1, %ebx
movl $length, %ecx
movl $output, %edx
int $0x80
movl $1, %eax
movl $0, %ebx
int $0x80
razum2um@localhost /tmp $ as h.as
razum2um@localhost /tmp $ chmod a+x a.out
razum2um@localhost /tmp $ ./a.out
bash: ./a.out: не могу запустить бинарный файл
dementiy 11.07.2010 13:59 #
+ 0 -
Хм... Все должно работать. Два вопроса: Работает ли у Вас пример из статьи? и У Вас 64 разрядная система (просто у Вас используются 64 разрядные регистры)? Попробуйте указать опцию у as "--32" (as --32 h.as).
razum2um 11.07.2010 14:11 #
+ 0 -
гента х64, пример сначала не проверял, только читал все, вспоминал

поддержка х32 вкомпилена, модуль ia32_aout загружен
CONFIG_IA32_EMULATION=y
CONFIG_IA32_AOUT=m

bash: ./a.out: не могу запустить бинарный файл - теперь везде (и с --32 и без, на всех трех)
razum2um 11.07.2010 14:11 #
+ 0 -
гента х64, пример сначала не проверял, только читал все, вспоминал

поддержка х32 вкомпилена, модуль ia32_aout загружен
CONFIG_IA32_EMULATION=y
CONFIG_IA32_AOUT=m

bash: ./a.out: не могу запустить бинарный файл - теперь везде (и с --32 и без, на всех трех)
gen1s 10.07.2010 16:04 #
+ 0 -
Такую статью (ещё лучше организовать цикл статей) публиковать на Хабрахабре. Там ей самое место. А так всё очень интересно, подробно и не скучно написно.
razum2um 10.07.2010 16:57 #
+ 1 -
хабр не торт ;)
strk 11.07.2010 11:22 #
+ 0 -
Есть возможность сделать торт :)
lockie 11.07.2010 14:16 #
+ 1 -
Торт - ложь.
blackraven 12.07.2010 19:04 #
+ 0 -
Отлично! Люто, бешено Плюсую!
Так держать и пожалуйста - не забрасывайте цикл, продолжайте!
blackraven 12.07.2010 19:05 #
+ 1 -
FILO (First Input Last Output — первым вошел, последним вышел)

Общепринятым обозначением таки является LIFO - Last In First Out... Смысл один и тот же, но лучше использовать устоявшиеся термины.
А вообще - так держать, и не останавливайтесь - пишите есчо, отлично пишете!
blackraven 12.07.2010 19:06 #
+ 0 -
http://ru.wikipedia.org/wiki/LIFO
dementiy 12.07.2010 23:19 #
+ 0 -
Вы правы, LIFO общепринятое обозначение, но у меня в голове вертелось русское употребление "Первым вошел, последним вышел" и пример тарелок (можно взять только верхнюю) =) Продолжать обязательно буду, вот только не знаю, как по времени получится (совсем скоро останусь на две недели без своего ноутбука, а это на данный момент единственная железка, которая у меня есть). И спасибо всем кто оставил комментарии =)

Смотреть онлайн бесплатно

Онлайн видео бесплатно


Смотреть русское с разговорами видео

Online video HD

Видео скачать на телефон

Русские фильмы бесплатно

Full HD video online

Смотреть видео онлайн

Смотреть HD видео бесплатно

School смотреть онлайн