Основы Ассемблера
Когда вы пишете программу на ассемблере, вы просто пишете команды процессору. Команды процессору — это просто коды или коды операций или опкоды. Опкоды — фактически «читаемый текст»- версии шестнадцатеричных кодов. Из-за этого, ассемблер считается самым низкоуровневым языком программирования, все в ассемблере непосредственно преобразовывается в шестнадцатеричные коды. Другими словами, у вас нет компилятора, который преобразовывает язык высокого уровня в язык низкого уровня, ассемблер только преобразовывает коды ассемблера в данные.
В этом уроке мы обсудим несколько опкодов, которые имеют отношение к вычислению, поразрядным операциям, и т.д. Другие опкоды: команды перехода, сравнения и т.д, будут обсуждены позже.
Комментарии в ваших программах оставляются после точки с запятой. Точно также как в дельфи или си через //.
Числа в ассемблере могут представляться в двоичной, десятеричной или шестнадцатеричной системе. Для того, чтобы показать в какой системе использовано число надо поставить после числа букву. Для бинарной системы пишется буква b (пример: 0000010b, 001011010b), для десятеричной системы можно ничего не указывать после числа или указать букву d (примеры: 4589, 2356d), для шестнадцатеричной системы надо указывать букву h, шестнадцатеричное число надо обязательно писать с нулём в начале (примеры: 00889h, 0AC45h, 056Fh, неправильно F145Ch, С123h).
Самая первая команда будет хорошо всем известная MOV. Эта команда используется для копирования (не обращайте внимания на имя команды) значения из одного места в другое. Это ‘место’ может быть регистр, ячейка памяти или непосредственное значение (только как исходное значение). Синтаксис команды:
mov приемник, источник
Вы можете копировать значение из одного регистра в другой.
mov edx, ecx
Вышеприведенная команда копирует содержание ecx в edx. Размер источника и приемника должны быть одинаковыми,
например: эта команда — НЕ допустима:
mov al, ecx ; не правильно
Этот опкод пытается поместить DWORD (32-битное) значение в байт (8 битов). Это не может быть сделано mov командой (для этого есть другие команды).
А эти команды правильные, потому что у них источник и приемник не отличаются по размеру:
mov al, bl mov cl, dl mov cx, dx mov ecx, ebx
Вы также можете получить значение из памяти и поместить эго в регистр. Для примера возьмем следующую схему памяти:
смещение | 34 | 35 | 36 | 37 | 38 | 39 | 3A | 3B | 3C | 3D | 3E | 3F | 40 | 41 | 42 |
данные | 0D | 0A | 50 | 32 | 44 | 57 | 25 | 7A | 5E | 72 | EF | 7D | FF | AD | C7 |
(Каждый блок представляет байт)
Значение смещения обозначено здесь как байт, но на самом деле это это — 32-разрядное значение. Возьмем для примера 3A, это также — 32-разрядное значение: 0000003Ah. Только, чтобы с экономить пространство, некоторые используют маленькие смещения.
Посмотрите на смещение 3A в таблице выше. Данные на этом смещении — 25, 7A, 5E, 72, EF, и т.д. Чтобы поместить значение со смещения 3A, например, в регистр, вы также используете команду mov:
mov eax, dword ptr [0000003Ah]
Означает: поместить значение с размером DWORD (32-бита) из памяти со смещением 3Ah в регистр eax. После выполнения этой команды, eax будет содержать значение 725E7A25h. Возможно вы заметили, что это — инверсия того что находится в памяти: 25 7A 5E 72. Это потому, что значения сохраняются в памяти, используя формат little endian . Это означает, что самый младший байт сохраняется в наиболее значимом байте: порядок байтов задом на перед. Я думаю, что эти примеры покажут это:
- dword (32-бит) значение 10203040 шестнадцатиричное сохраняется в памяти как: 40, 30, 20, 10
- word (16-бит) значение 4050 шестнадцатиричное сохраняется в памяти как: 50, 40
Вернемся к примеру выше. Вы также можете это делать и с другими размерами:
mov cl, byte ptr [34h] ; cl получит значение 0Dh mov dx, word ptr [3Eh] ; dx получит значение 7DEFh
Вы, наверное, уже поняли, что префикс ptr обозначает, что надо брать из памяти некоторый размер. А префикс перед ptr обозначает размер данных:
Byte - 1 байт Word - 2 байта Dword - 4 байта
Иногда размер можно не указывать:
mov eax, [00403045h]
Так как eax — 32-разрядный регистр, ассемблер понимает, что ему также требуется 32-разрядное значение, в данном случае из памяти со смещением 403045h.
Можно также непосредственные значения:
mov edx, 5006h
Эта команда просто запишет в регистр edx, значение 5006. Скобки, [ и ], используются, для получения значения из памяти (в скобках находится смещение), без скобок, это просто непосредственное значение.
Можно также использовать регистр как ячейку памяти (он должен быть 32-разрядным в 32-разрядных программах):
mov eax, 403045h ; пишет в eax значение 403045 mov cx, [eax] ; помещает в регистр CX значение (размера word) из памяти ; указанной в EAX (403045)
В mov cx, [eax], процессор сначала смотрит, какое значение (= ячейке памяти) содержит eax, затем какое значение находится в той ячейке памяти, и помещает это значение (word, 16 бит, потому что приемник, cx, является 16-разрядным регистром) в CX.
Стековые операции — PUSH, POP. Перед тем, как рассказать вам о стековых операциях, я уже объяснял вам, что такое стек. Стек это область в памяти, на которую указывает регистр стека ESP. Стек это место для хранения адресов возврата и временных значений. Есть две команды, для размещения значения в стеке и извлечения его из стека: PUSH и POP. Команда PUSH размещает значение в стеке, т.е. помещает значение в ячейку памяти, на которую указывает регистр ESP, после этого значение регистра ESP увеличивается на 4. Команда Pop извлекает значение из стека, т.е. извлекает значение из ячейки памяти, на которую указывает регистр ESP, после этого уменьшает значение регистра ESP на 4. Значение, помещенное в стек последним, извлекается первым. При помещении значения в стек, указатель стека уменьшается, а при извлечении — увеличивается. Рассмотрим пример:
(1) mov ecx, 100 (2) mov eax, 200 (3) push ecx ; сохранение ecx (4) push eax (5) xor ecx, eax (6) add ecx, 400 (7) mov edx, ecx (8) pop ebx (9) pop ecx
- 1: поместить 100 в ecx
- 2: поместить 200 в eax
- 3: разместить значение из ecx (=100) в стеке (размещается первым)
- 4: разместить значение из eax (=200) в стеке (размещается последним)
- 5/6/7: выполнение операций над ecx, значение в ecx изменяется
- 8: извлечение значения из стека в ebx: ebx станет 200 (последнее размещение, первое извлечение)
- 9: извлечение значения из стека в ecx: ecx снова станет 100 (первое размещение, последнее извлечение)
Чтобы узнать, что происходит в памяти, при размещении и извлечении значений в стеке, см. на рисунок ниже:
(стек здесь заполнен нулями, но в действительности это не так, как здесь). ESP стоит в том месте, на которое он указывает)
mov ax, 4560h push ax
mov cx, FFFFh push cx
pop edx
edx теперь 4560FFFFh.
Вызов подпрограмм возврат из них — CALL, RET. Команда call передает управление ближней или дальней процедуре с запоминанием в стеке адреса точки возврата. Команда ret возвращает управление из процедуры вызывающей программе, адрес возврата получает из стека. Пример:
..code.. call 0455659 ..more code.. Код с адреса 455659: add eax, 500 mul eax, edx ret
Когда выполняется команда call, процессор передает управление на код с адреса 455659, и выполняет его до команды ret, а затем возвращает управление команде следующей за call. Код который вызывается командой call называется процедурой. Вы можете поместить код, который вы часто используете в процедуру и каждый раз когда он вам нужен вызывать его командой call.
Подробнее: команда call помещает регистр EIP (указатель на следующюю команду, которая должна быть выполнена) в стек, а команда ret извлекает его и передаёт управление этому адресу. Вы также можете определить аргументы, для вызываемой программы (процедуры). Это можно сделать через стек:
push значение_1 push значение_2 call procedure
Внутри процедуры, аргументы могут быть прочитаны из стека и использованы. Локальные переменные, т.е. данные, которые необходимы только внутри процедуры, также могут быть сохранены в стеке. Я не буду подробно рассказывать об этом, потому, что это может быть легко сделано в ассемблерах MASM и TASM. Просто запомните, что вы можете делать процедуры и что они могут использовать параметры.
Одно важное замечание: регистр eax почти всегда используется для хранения результата процедуры.
Это также применимо к функциям windows. Конечно, вы можете использовать любой другой регистр в ваших собственных процедурах, но это стандарт.
Вот и кончился очередной урок. На следующем уроке мы напишем первую программу на ассемблере.
Что означает cmp dword ptr ds:[eax+10h],0300h?
Что за адрес в MOV EAX,DWORD PTR DS:[10008234]
Занимаюсь дизассемблированием одной софтинки в ollydbg. Встретилась такая команда MOV EAX,DWORD.
Что означает ptr в строке byte ptr[si],al
Что означает ptr в строке byte ptr,al Вот весь код, может понадобится Преобразование двоичного.
Что означает строка: CMP AX, [ES:BX]
Подскажите пожалуйста, что означает строка: CMP AX,
8366 / 4254 / 1612
Регистрация: 01.02.2015
Сообщений: 13,226
Записей в блоге: 5
dword ptr и byte ptr используется для явного указания размера операнда в тех случаях, когда компилятор не в состоянии определить размер операнда из других соображений (по размеру операнда-приёмника или операнда-источника, как в mov eax, ‘#’ ). Или принудительное указание размера операнда, когда переменная в памяти, к которой выполняется обращение, определена другого размера
1 2 3
var dd 123456 mov word ptr [var], ax mov word ptr [var+2], dx
Добавлено через 2 минуты
lea di, es:[Str1] — увязана с assume, которой регистр es соотносится с конкретным сегментом. И относительно этого сегмента и отсчитывается смещение к переменной.
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь
Что означает mov byte ptr ds:[1], ‘b’?
Здравствуйте, обьясните пожалуйста фрагмент кода. mov byte ptr ds:, ‘a’ mov byte ptr.
Что означает строка: CString &ptr = Text; ?
Помогите please! Что означает строка: CString &ptr = Text; Text определена выше, как: .
mov eax,ss: [word ptr bp] [di+4]
Определите способ адресации операндов в команде и напишите результат её выполнения При вычислении.
Зачем DWORD PTR заносить в регистр?
Подскажите, пожалуйста, в 6й строке нежелательно напрямую добавлять ebx к DWORD PTR . DWORD PTR .
Dword ptr ассемблер что это
Все, что необходимо начинающему и опытному программисту
Изменим предыдущий пример так, чтобы в программе можно было использовать
встроенный ассемблер. Вариантов может быть несколько, поэтому
рассмотрим их по порядку.
Одним из решений может быть замена в обработчике нажатия кнопки
Buttonl оператора суммирования:
I R E S := II + 12
на ассемблерный блок команд:
asm
mov EAX, DWORD PTR II
add EAX, DWORD PTR 12
mov ‘ DWORD PTR I R E S , EAX
end;
В этом фрагменте кода целочисленные переменные и , 12 и I R E S определены
как двойные слова. В остальном текст ассемблерного блока несложен.
Сложение двух чисел выполняется в регистре ЕАХ С использованием команды
add, а результат помещается в переменную I R E S .
Обработчик нажатия кнопки с учетом этих изменений приведен в листинге 6.5.
Листинг 6.5. Суммирование двух целых чисел с использованием ассеблерного блока команд в обработчике нажатия кнопки
procedure TForml.ButtonlClick(Sender: TObject);
begin
asm
mov EAX, DWORD PTR II
Встроенный ассемблер языков высокого уровня: принципы использования 435
add ЕАХ, DWORD PTR 12
mov DWORD PTR IRES, EAX
end;
Edit3.Text := IntToStr(IRES);
end;
Обратите внимание, что оператор:
Edit3.Text := IntToStr(IRES);
корректно обрабатывает двойное слово IRES как переменную типа integer.
Операцию суммирования можно оформить и в виде процедуры. В «классическом»
варианте приложения исходный текст будет выглядеть так, как
представлено в листинге 6.6.
Листинг 6.6. Использование процедуры для вычисления суммы двух чисел
unit basmlpas;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TForml = class(TForm)
Buttonl: TButton;
Editl : TEdit;
Labell : TLabel;
Edit2 : TEdit;
Edit3 : TEdit;
Label2 : TLabel;
Label3 : TLabel;
procedure ButtonlClick(Sender: TObject);
private
< Private declarations >
public
< Public declarations >
end;
var
Forml: TForml ;
II, 12, IRES: Integer;
Implementation
procedure AddTwo;
begin
IRES := II + 12;
end;
procedure TForml.ButtonlClick(Sender: TObject);
begin
11 := StrToInt(Editl.Text);
12 := StrToInt(Edit2.Text);
AddTwo;
Edit3.Text := IntToStr(IRES);
end;
end.
В этом варианте программы операция суммирования выделена в отдельную
процедуру AddTwo. Процедура не имеет входных параметров и не возвращает
значение. Ее довольно легко модифицировать при помощи встроенного ассемблера
(листинг 6.7).
Листинг 6.7. Суммирование двухчисел в ассемблерном блоке процедуры
procedure AddTwo;
begin
asm
mov
mov
add
EAX, DWORD PTR II
EAX, DWORD PTR 12
DWORD PTR IRES, EAX
end;
end;
Встроенный ассемблер языков высокого уровня: принципы использования 437
Как видно из исходного текста, процедура, написанная на встроенном
ассемблере, использует переменные программы для вычислений и возврата
значения. В этом варианте не используется механизм передачи параметров
и не требуется отдельный оператор для возврата результата.
При всей простоте реализации таких процедур их применение лучше ограничить.
Если в программе присутствует несколько процедур, использующих
одни и те же переменные, то будет очень трудно отслеживать
изменения таких переменных. Обычно это приводит к ошибкам в работе
приложения.
Библиотека программиста. 2009.
Администратор: admin@programmer-lib.ru
Дневники чайника. Чтива 0, виток0
Пятый день.
О словах и двойных словах
(форматы данных)
Не сказать чтоб эта тема была самая сложная, но то, что она самая запутанная — это 100%.
То, что я здесь напишу, довольно туго для восприятия, однако со временем всё уляжется. Не расстраивайтесь, если вдруг в этой главе покажется, что Ассемблер очень сложный, вспомните о таком фокусе.
Не каждый водитель знает, что для того, чтоб машина ехала вперёд, колесо в точке соприкосновения с дорогой должно двигаться назад. Хотя такая информация была сообщена каждому водителю и каждому человеку. Ну, едет машина и едет, какая разница, куда крутятся колёса. Это нужно знать только при смене колёс, чтоб не перепутать направленность резины, или во время ремонта.
Примерно такая же информация будет изложена в этой главе. На практике всё быстро усвоится, если вам придется сталкиваться с машинным кодом и данными.
Матрос, ты должен знать своего друга (со стороны потенциального противника) лучше, чем себя.
Бинарники организуются в отряды по восемь, эти отряды называются байтами. Запомнил?
Байт — это минимальная расчетная единица бинарной армии. Запомнил?
2 байта организуются в слово (word).
4 байта организуются в двойное слово (dword, а пОлно — double word).
Dword — это самый распространённый набор битов в Win32-программах. Так как:
Dword =4 байта = 32 бита.
В предыдущей программе мы столкнулись вот с такой строкой:
00000003: 66C70701020304 mov d,[bx],004030201
«d,» здесь как раз и заменяет dword.
Я объяснил, что когда операнд находится в квадратных скобках, при команде mov это означает, что нужно производить действие по адресу в памяти, указанному операндом. То есть в BX раньше должен быть положен адрес. В ходе выполнения этой строки BX не изменяется, изменится только память по адресу, указанному BX. Размер изменяемой памяти dword (4 байта, двойное слово).
Остаётся маленький такой вопросик: почему в дизассемблере байты операндов команд процессора мы видим зеркально значениям операндов в командах Асма?
байты инструкции Команда Ассемблера 00000003: 66C707 01020304 mov d,[bx],004030201
Запомни, матрос, Бинарники очень хитрые. Для того, чтоб их враг путал байты по старшинству, в каждой целой боевой единице, будь то word (2 байта), dword (4 байта), qword (8 байт), байты строятся от младшего к старшему, черт их дери!
mov dword ptr [bx],04030201
Адрес, который был указан в BX, равен 0133h.
А действие выглядит так:
Адрес значение 0133 01 0134 02 0135 03 0136 04
В отладчике вы видели это вот так:
0130 16 CD 20 01 02 03 04 24 E2 04 B7 9A 66 B9 FF FF ^ ^ ^ ^
Мы пишем и читаем текст по-европейски — слева направо. Но для чисел большинство людей использует арабскую запись — справа налево (хотя читаем числа тупо от старшей цифры =).
К великому огорчению, программистами был принят смешанный формат отображения данных. Каждый байт отображается по арабской системе, а целая группа байтов — по европейской. Выходит, что на экране мы видим разную запись. Если программа-дизассемблер или отладчик воспринимают группы байтов как ЦЕЛОЕ число, то оно отображается арабской записью, как в колонке команд Ассемблера: 04 03 02 01, а если речь идёт просто о нескольких байтах, то мы видим европейскую запись, только за букву принят целый байт, что и показано выше: 01 02 03 04. Всё это лишь вопрос отображения на экране или в документах. Например, если использовать запись цифровых значений от нижнего правого угла экрана до верхнего левого (справа налево, снизу вверх), то вообще ничего переворачивать не нужно! То есть если бы была принята запись «справа налево всё» или «слева направо всё», то подобных проблем не было бы вообще.
Допустим, мы набрали вот такую строку:
mov word ptr [00000800h],0BBAAh
Здесь мы указали, что размер данных word (2 байта) и эти данные будут помещены в память по адресу 800h.
Объясню сейчас коротко.
Раз мы имеем заданный размер word (или как в Hiew’е «w,»), мы имеем некое ЦЕЛОЕ.
Младший байт (у нас AA) будет находиться по наименьшему адресу, а старший байт (BB) — по более старшему адресу.
Вот как эта строка будет выглядеть в Hiew’e:
Адрес Байты имя операнды 00000000: C7060008AABB mov w,[0800],0BBAA
И точно так же устроены dword. Допустим:
mov dword ptr [00000800h],0DDCCBBAAh
Вопрос, который наверняка возник у всех (и я предполагаю, что у многих в нецензурной форме): «На. в смысле зачем?»
Это нужно для того, чтобы процессор забирал байты из памяти, начиная с младшего (так быстрее вычислять). Ведь получается, что адрес числовой переменной любого размера, хоть word, хоть dword, будет указывать на младший байт в числе, далее пойдёт следующий байт в числе, и самый старший байт в числе всегда окажется в конце. Вычислять процессору так значительно быстрее.
Я думаю, что многие читатели совсем запутались, тут есть только одно утешение: перекладывать байты в голове совсем не нужно. Чтобы получить целое число из байтов или, наоборот, из числа сложить байты, достаточно переключить режим отображения в программе. Мало того, при простом программировании можно забыть об этих премудростях и вспоминать лишь изредка.
Всё, из теории остались только циклы и стек, о них мы будем говорить завтра.
Матрос! Я что-то не заметил, чтоб ты разрабатывал кнопку F10!
Если ты хочешь стать капитаном собственного корабля, пусть даже и без гипердвигателя, ты должен отлаживать и отлаживать.
Послезавтра я увижу своё отражение в F10-key у тебя на клавиатуре или я высажу тебя на ближайшей заброшенной планете.
А чтоб было веселей давить на кнопку F10, загони в отладчик следующую программу (prax03.com).
Набивайте всё сами, только так можно научиться.
В Hiew’e она должна выглядеть так:
00000000: B80300 mov ax,00003 00000003: CD10 int 010 00000005: B402 mov ah,002 00000007: 8B167501 mov dx,[0175] 0000000B: CD10 int 010 0000000D: FEC6 inc dh 0000000F: 80C203 add dl,003 00000012: 89167501 mov [0175],dx 00000016: B409 mov ah,009 00000018: BA5001 mov dx,00150 0000001B: CD21 int 021 0000001D: 803E760119 cmp b,[0176],019 00000022: 75E1 jne 000000005 00000024: B410 mov ah,010 00000026: CD16 int 016 00000028: CD20 int 020
Но это не всё, теперь переключитесь на Hex-режим (F4) и добейте программу следующими байтами после всего кода. Это будут «данные».
00000020: 20 20 20-20 20 20 20 00000030: 20 20 20 20-20 20 20 20-20 20 20 20-20 20 20 20 00000040: 91 E2 E0 AE-AA A0 20 E2-A5 AA E1 E2-A0 3A 20 20 Строка текста: 00000050: 2D 3D 80 E1-AC 3D 2D 24-20 20 20 20-20 20 20 20 -=Асм=-$ 00000060: 20 20 20 20-20 20 20 20-20 20 20 20-20 20 20 20 00000070: 77 6F 72 64-21 00 00 21-77 6F 72 64-20 20 20 20 word! !word 00000080: 20 20 20 20-20 20 20 20-20 20 20 20-20 20 20 20
На самом деле все эти пробелы (20h) программе НЕ нужны. Но когда вы будете смотреть программу в отладчике, они вам помогут.
Если у вас будет сдвиг хоть на байт, программа будет ошибочной. Поэтому проверьте, чтобы строка «-=Асм=-$» начиналась с 50h и, что ещё более важно, два нулевых байта (00 00) должны быть в файле по адресам 75 и 76h. Обязательно посмотрите в отладчике, что будет происходить с этими байтами (там они будут 175h и 176h). Всё остальное здесь мишура и для выполнения программы совершенно не имеет значения.
При отладке могут появляться сообщения насчёт экрана. ну и фиг с ними. Если в CV опция Screen Swap ещё не выключена, то это обязательно нужно сделать.
Результат действия этой программы — вывод строк по диагонали от верхнего левого угла до нижнего правого угла.
Здесь очень много нового, и я надеюсь, вам будет интересно узнать, как работают новые команды CMP и JNE.
Попробуйте сами разобраться, что происходит в программе на практике. Как я уже писал, прерываний (команда int) при написании программ Win32 мы использовать не будем. Поэтому можете не заострять на них внимание. Достаточно знать, что это полезные подпрограммы, часть которых заложена ещё в BIOS (basic input/output system — базовая система ввода/вывода).
Я вынужден их использовать только для того, чтобы программа была полноценная и вы могли наглядно изучать главные команды Ассемблера.
В данном примере будут задействованы int 10h для очистки экрана (AL=3) и для расположения курсора текста (AH=2). Ну и int 21h для вывода текста на экран. Всё, других прерываний в уроках больше не будет. О них за долгие годы написано достаточно.
Вот как программа должна трактоваться (сразу скажу — про метки я всё объясню позже).
mov ax,03 ; В AX значение 3 (параметр видеорежима 80x25) int 010 ; Подпрограмма установит текстовый видеорежим 80х25, ; при этом экран ДОС очистится TuLuLa: ; Всего лишь условная метка, в коде программы её нет ; Здесь могли быть любые буквы и ":" mov ah,02 ; В AH поместить 02 (для int10 - установка курсора) mov dx,[0175h] ; В DX загрузить значение из памяти по адресу 175h ; в первый раз там нули, ; а в следующий проход значения будут увеличены int 10 ; Установит текстовый курсор в положение, указанное в DX ; DH - номер линии, DL - номер колонки (верх.лев.- 0000) inc dh ; Прибавить 1 к значению DH add dl,03 ; Прибавить 3 к значению DL mov [0175h],dx ; Сохранить DH и DL в память по адресу 175h mov ah,09 ; Функция в int21h - вывод на экран с позиции курсора mov dx,00150h ; В DX адрес текстовой строки, заканчивающейся $ int 021 ; Подпрограмма выведет текстовую строку на экран cmp byte ptr [176h],19h ;Сверяется значение байта в памяти с числом 25d jne TuLuLa ;и если значения не равны, ;то прыг на выполнение от метки TuLuLa mov ah,010 ; а если были равны, то выполнится эта строка int 016 ; Подпрограмма дождётся нажатия клавиши ; и вернёт управление на следующую строку int 020 ; код завершения программы
Перед разбором этой программы я хочу рассказать о ключевом понятии в программировании — циклы.