Как обнулить регистры в ассемблере
Перейти к содержимому

Как обнулить регистры в ассемблере

  • автор:

Assembler: идеальный вывод содержимого регистров. [закрыт]

Хотите улучшить этот вопрос? Переформулируйте вопрос так, чтобы он был сосредоточен только на одной проблеме.

Закрыт 3 года назад .

Допустим, будем обсуждать вывод значения регистра AX в шестнадцатеричном беззнаковом формате (можно и в другом, просто в этом, как мне кажется, будет быстрее). Я представляю много способов написать такую процедуру, к примеру, для masm32:

ax_out proc uses dx cx .code push ax mov ah,02h mov cx,12d //Счетчик на три цикла — каждый цикл будем отнимать от него 4 mainloop: pop dx push dx shr dx,cl //Делаем сдвиг на 12/8/4, получая нужную нам цифру в 0-3 битах dl and dl,0Fh //Очищаем биты 4-7 в dl cmp dl,10d //Если это буква, то для получения кода прибавляем к ней 37 jge add_37h add dl,30h //Ну а если цифра, — то 30 jmp print_4 add_37h: add dl,37h print_4: int 21h sub cx,3 loop mainloop pop dx //А теперь поторяем это все для последней цифры. push dx and dl,0fh cmp dl,10d jge add_37h_last add dl,30h jmp print_last add_37h_last: add dl,37h print_last: int 21h pop ax ret ax_out endp 

Выглядит не лучшим образом. Поэтому я бы хотел увидеть код, которым бы вы (люди разбирающиеся в ассемблере лучше меня) стали выводить содержимое регистра AX. Я понимаю, что можно написать программу либо с упором на место занимаемое ею в памяти, либо на ее скорость, но всё же, если бы у вас была задача написать универсальный вывода AX на экран — как бы это выглядело?

Как обнулить регистры в ассемблере

Режимы адресации памяти x86-64 обеспечивают гибкий доступ к памяти, позволяя легко обращаться к переменным, массивам, записям, указателям и другим сложным типам данных. Архитектура x86-64 предоставляет несколько режимов адресации:

  • Адресация регистров
  • Адресация памяти относительно счетчика команд (PC-relative memory addressing)
  • Косвенная адресация регистров ( [reg64] )
  • Косвенная адресация регистров со смещением ( [reg64 + offset] )
  • Косвенная адресация регистров со смещением и масштабированием ( [reg64 + offset + reg64 * scale] )

Режим адресации регистров предоставляет доступ к регистрам общего назначения. Это стандартное обращение к регистрам процессора:

mov rax, rdx

Регистры представляют самый быстрый тип памяти компьютера. Инструкции, которые используют регистры, короче и быстрее, чем те, которые обращаются к памяти. Поскольку для большинства вычислений требуется по крайней мере один регистр, режим адресации регистров популярен в ассемблере x86-64. Единственное ограничение — оба операнда должны иметь одну и ту же разрядность.

Адресация памяти относительно счетчика команд

Данный режим заключается в использовании имени переменной или константы, которая определена в одном из разделов — .data, .data?, .const, .code и т. д.

.data i32 dword 33 .code main proc mov eax, i32 ret main endp end

Для вычисления адреса переменных/констант применяется регистр RIP (Instruction Pointer), он же счетчик команд (Program Counter), отсюда и соответствующее название.

Косвенная адресация регистров

Косвенная адресация регистров (register-indirect addressing mode) означает, что регистр содержит адрес некоторого объекта. Используя данный адрес, извлекается данный объект. Чтобы указать, что мы хотим взять не значение из регистра, а значения из адреса, который хранится в регистре, название регистра помещается в квадратные скобки:

[reg64]

Где reg64 — это один из 64-разрядных регистров общего назначения: RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8, R9, R10, R11, R12, R13, R14 и R15.

Инструкция lea

Для загрузки адреса некоторого объекта может применяться инструкция lea (load effective address). Данная инструкция имеет следующий синтаксис:

lea reg64, variable

Первый операнд инструкции представляет 64-разрядный регистр общего назначения, куда загружается адрес переменной variable — второго операнда. Стоит отметить, что переменная variable может представлять любой размер, поскольку в регистр загружается именно ее адрес, а адрес в 64-разрядной архитектуре имеет длину 64 бита.

Например, определим в секции данных переменную text и загрузим ее адрес в регистр rcx :

.data text byte "hello", 0 .code main proc lea rcx, text ; загрузка в rcx адреса переменной text ret main endp end

Стоит отметить, что в C/C++ это было бы аналогично следующему коду:

char text[] = "hello"; char *RCX = &text[0];

Загрузка и сохранение значения по адресу

Загруженный адрес затем можно использовать для получения данных при косвенной адресации:

.data i32 dword 39 .code main proc lea rcx, i32 ; загружаем в RCX адрес переменной i32 mov eax, [rcx] ; загружаем в EAX значение по адресу из RCX ret main endp end

Здесь в регистр RCX загружается адрес переменной i32. Затем в регистр EAX помещается значение, которое хранится по адресу из регистра RCX — то есть значение переменной i32.

Также можно наоборот — сохранять значение по определенному адресу. Например:

.data i32 dword ? .code main proc lea rcx, i32 ; загружаем в регистр RCX адрес переменной i32 mov rdx, 12 ; в регистр RDX помещаем число 12 mov [rcx], rdx ; в память по адресу [RCX] помещаем значение из RDX mov eax, i32 ; в регистр EAX помещаем значение переменной i32 - 12 ret main endp end

Здесь в секции данных определена неинициализированная переменная i32. Сохраним в эту переменную число 12. Для этого загружаем адрес этой переменной в регистр RCX

lea rcx, i32

Сохраняемое число помещаем вначале в регистр RDX

mov rdx, 12

Далее помещаем данные из регистра RDX в участок памяти, адрес которой хранится в регистре RCX, то есть по сути адрес переменной i32:

mov [rcx], rdx

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

Установка смещения

Дополнительно для адреса можно установить смещение в байтах. В качестве смещения выступает 32-разрядное число со знаком

[reg64 ±смещение]
.data n1 dword 1 n2 dword 3 .code main proc lea rbx, n1 ; загружаем в регистр RBX адрес переменной n1 mov eax, [rbx + 4] ; в регистр EAX загружаем данные с адреса [rbx + 4], то есть переменной n2 ret main endp end

Здесь в регистр RBX помещается адрес переменной n1. Эта переменная занимает 4 байта (тип dword), соответственно адрес следующей переменной — n2 равен [RBX + 4]. И если мы возьмем данные по этому адресу, то фактически мы получим значение переменной n2.

Аналогично можно сохранять данные по адресу, используя смещение

.data n1 dword 1 n2 dword 3 .code main proc lea rbx, n1 ; загружаем в регистр RBX адрес переменной n1 mov rdx, 15 ; в регистр RDX помещаем число 15 mov [rbx + 4], rdx ; в память по адресу [RBX] помещаем значение из RDX mov eax, n2 ; в регистр EAX помещаем значение переменной n2 - 15 ret main endp end

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

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

Адресация с масштабированием

Адресация с масштабированием позволяет комбинировать два регистра плюс смещение и умножать значение индексного регистра на коэффициент масштабирования 1, 2, 4 или 8, чтобы вычислить адрес данных:

[base_reg64 + index_reg64*scale] [base_reg64 + index_reg64*scale + offset] [base_reg64 + index_reg64*scale - offset]

base_reg64 представляет любой 64-битный регистр общего назначения, index_reg64 представляет любой 64-битный регистр общего назначения, кроме RSP, а scale (коэффициент масштабирования) должен быть одной из констант 1, 2, 4 или 8. Дополнительно можно прибавлять или отнимать смещение.

Например, у нас есть набор некоторых данных, и нам надо в этом наборе получить какой-то определенный элемент:

.data numbers dword 11, 12, 13, 14, 15, 16, 17, 18 .code main proc lea rbx, numbers ; загружаем в регистр RBX адрес переменной numbers mov rsi, 5 ; индекс получаемого двойного слова - получаем 6 элемент mov eax, [rbx + rsi*4] ; в регистр EAX загружаем данные с адреса [rbx + rsi*4], то есть число 16 ret main endp end

Здесь данные представлены набором двойных слов numbers. Каждое число в этом наборе занимает 4 байта. Допустим, мы хотим получить 6-е число. Для этого в регистр RSI помещаем индекс нужного нам элемента — число 5. То есть, чтобы получить 6-е число, нам надо пройти 5 предыдущих чисел.

mov rsi, 5

Затем загружаем в регистр EAX значение 6-го числа из numbers, то есть число 16.

mov eax, [rbx + rsi*4]

Адрес складывается из следующих компонентов. Во-первых, в регистре RBX адрес начала набора numbers, то есть адрес первого элемента из этого набора. Чтобы дойти к адресу 6-го элемента от адреса 1-го элемента, нам надо пройти 5 элементов, поэтому в регистре RSI число 5. Каждый элемент занимает 4 байта, соответственно, чтобы пройти от 1-го элемента до 6-го, нам надо пройти rsi*4 = 5 *4 = 20 байтов.

Преобразование данных

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

.data n1 byte 11 n2 byte 12 n3 byte 13 n4 byte 14 .code main proc lea rbx, n1 ; загружаем в регистр RBX адрес переменной n1 mov eax, [rbx] ; в регистр EAX загружаем данные с адреса [rbx], то есть переменной n1 ret main endp end

В регистр RBX помещается адрес переменной n1, затем данные из памяти по адресу из RBX помещаются в регистр EAX. То есть мы ожидаем, что в регистре EAX в итоге будет число 11 — значение переменной n1. Однако если мы скомпилируем программу с помощью MASM, запустим и проверим содержимое регистра EAX, то мы увидим, что там не число 11:

c:\asm>ml64 hello.asm /link /entry:main Microsoft (R) Macro Assembler (x64) Version 14.36.32532.0 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: hello.asm Microsoft (R) Incremental Linker Version 14.36.32532.0 Copyright (C) Microsoft Corporation. All rights reserved. /OUT:hello.exe hello.obj /entry:main c:\asm>hello c:\asm>echo %ERRORLEVEL% 235736075 c:\asm>

Число 235736075 вряд ли равно значению переменной n1. Поскольку значение помещается в 32-разрядный регистр EAX, то по адресу из RBX выбирается 4 байта, то есть все последующие переменные — n2, n3 и n4. Но нам надо получить только значение переменной n1. Для этого мы можем получить значение в 8-разрядный регистр:

.data n1 byte 11 n2 byte 12 n3 byte 13 n4 byte 14 .code main proc lea rbx, n1 ; загружаем в регистр RBX адрес переменной n1 mov rax, 0 ; обнуляем регистр rax mov al, [rbx] ; AL = 11 (и EAX = 11) ret main endp end

Либо мы можем применить преобразование до байта:

.data n1 byte 11 n2 byte 12 n3 byte 13 n4 byte 14 .code main proc lea rbx, n1 movzx eax, byte ptr[rbx] ret main endp end

Таким образом, данные по адресу из RBX преобразуются до 1 байта, а инструкция movzx заполняется старшие разряды нулями, благодаря чему нет несоответствия по размеру между операндами.

Использование и сохранение регистров во встроенном коде на языке ассемблера

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

Если используется соглашение о вызовах __fastcall , компилятор передает аргументы функций в регистрах, а не в стеке. Это может создавать проблемы в функциях с блоками __asm , поскольку для функции не существует способа определить, как параметры распределены по регистрам. Если функция получила параметр в регистре EAX и сразу же записала в регистр EAX какое-то другое значение, исходный параметр будет потерян. Кроме того, необходимо сохранять значение регистра ECX в любой функции, объявленной с атрибутом __fastcall .

Чтобы избежать подобных конфликтов регистров, не используйте соглашение __fastcall для функций, которые содержат блок __asm . Если соглашение __fastcall задано глобально с помощью параметра компилятора /Gr, объявляйте каждую функцию, содержащую блок __asm , с атрибутом __cdecl или __stdcall . (Атрибут __cdecl сообщает компилятору использовать соглашение о вызовах C для этой функции.) Если вы не компилируетсяе с помощью /Gr, не объявляйте функцию с атрибутом __fastcall .

При использовании блока __asm для написания кода на языке ассемблера в функциях C/C++ нет необходимости сохранять значения регистров EAX, EBX, ECX, EDX, ESI и EDI. Например, в POWER2. Пример в написании функций с помощью встроенной сборки power2 функция не сохраняет значение в регистре EAX. Однако использование этих регистров влияет на качество кода, поскольку распределитель регистров не может использовать их для хранения значений между блоками __asm . Кроме того, если во встроенном коде на языке ассемблера используется регистр EBX, ESI или EDI, компилятор вынужден сохранять и восстанавливать значения этих регистров в прологе и эпилоге функции.

Необходимо сохранять значения остальных используемых регистров (например, DS, SS, SP, BP и флаговые регистры) в пределах области блока __asm . Необходимо сохранять значения регистров ESP и EBP, если только нет определенной причины для их изменения (переключение стека, например). См. также статью «Оптимизация встроенной сборки».

Для некоторых типов SSE требуется 8-байтовое выравнивание стека, в результате чего компилятор вынужден создавать код динамического выравнивания стека. Чтобы иметь возможность доступа к локальным переменным и параметрам функций после выравнивания, компилятор поддерживает два указателя фреймов. Если компилятор выполняет пропуск указателя кадра (FPO), он будет использовать EBP и ESP. Если компилятор не выполняет FPO, он будет использовать EBX и EBP. Чтобы обеспечить правильное выполнение кода, не изменяйте значение регистра EBX в коде на языке ассемблера, если функция требует динамического выравнивания стека, так как при этом может измениться указатель фрейма. Переместите типы с 8-байтовым выравниванием за пределы функции или не используйте регистр EBX.

Если встроенный код на языке ассемблера изменяет флаг направления с помощью инструкций STD или CLD, необходимо восстановить исходное значение этого флага.

Завершение блока, относящегося только к системам Майкрософт

Как обнулить регистр?

Author24 — интернет-сервис помощи студентам

Регистр сведений «Списанные материалы из эксплуатации». Почему регистр сведений, а не регистр накопления?
Существует Регистр сведений «Списанные материалы из эксплуатации». Регистр сведений предназначен.

Даны два числа. Записать в регистр SI меньшее, а в регистр DI — большее из них
Даны два числа. Записать в регистр SI меньшее, а в регистр DI — большее из них.

Занести в регистр AX значение регистра CS, а в регистр BL записать заданное число
Здравствуйте, у меня такая проблема: Нужно составить программу, которая заносит в регистр AX.

Поместить в регистр BX константу 100 (16). Старший байт BX переместить в регистр BL
здраствуйте помогите пож-та разобраться в задаче!написал прогу но не работает! Поместить в регистр.

Эксперт CЭксперт С++

5113 / 4552 / 854
Регистрация: 07.10.2015
Сообщений: 9,462

1 2 3 4 5
xor eax,eax xor ax,ax mov al,0 mov ah,0 xor ah,ah

Добавлено через 1 минуту
Кстати, уточните, о каком Ассемблере и процессоре идет речь?
805 / 532 / 158
Регистрация: 27.01.2015
Сообщений: 3,017
Записей в блоге: 1
можно еще так, но ксором как-то красивше)

and eax, 0

Эксперт Hardware

Эксперт Hardware

6106 / 2350 / 390
Регистрация: 29.07.2014
Сообщений: 3,110
Записей в блоге: 4

Лучший ответ

Сообщение было отмечено Constantin Cat как решение

Решение

21 способ обнулить регистр
(c)журнал Top Device

1 2 3 4 5 6 7 8 9 10 11 12 13 14
mov ax,0 ;обнуление MOV'ом sub ax,ax ;вычитаем регистр сам из себя xor ax,ax ;арвиХакер, ксорящий ворды в уме and ax,0 ;логика - тоже неплохой вариант imul ax,0 ;более хитро: умножим на 0. shr ax,16 ;cдвиг (не путать со спрыгом) loop $ ;обнулим (E)CX cwd ;обнулим DX aam 1 ;обнулим AL. (AH=AL,AL=0) aad 0 ;обнулим AH in ax,81h ;более хитро: прочитаем 0 из порта mov eax,fs:[10h] ;cчитаем ноль из сегмента FS (PE файл) call GetCurrentObject ;вызовем функцию с кривыми параметрами (вернется NULL в EAX) push 0 / pop ax ;используя стек

Предлагаются также следующие варианты обнуления регистра:
— INC/DEC/JNZ
— используя сопроцессор
— сканирование цепочки обработчкиков SEH до победного нуля
— сканирование цепочки хендлов файлов до нуля
— считывание нуля из случайного файла
— вычисление синуса от Pi * n (умножать командой FMUL)
— сортировка памяти и поиск нуля как минимального элемента
— определение нуля как константы (в исходнике)
— создание специального макроса для генерации нуля
— подсчет количества оставшихся файлов
.

87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь

(MCS-51) Внести в регистр А двоично-десятичное число XX, в регистр R5 — XX
Внести в регистр А двоично-десятичное число XX, в регистр R5 — XX, попеременно отображать эти числа.

Регистр флагов в обычный регистр
Можно ли положить регистр флагов в обычный регистр (допустим, eax)? (для дальнейшего вывода на.

Регистр сведений. Регистр накоплений
Здравствуйте! Нужна помощь в создании (и настройки работы) регистра сведений и регистра накопления.

Как обнулить цикл
Здравствуйте. Есть задача сделать меню, которое при скролле пропадает и если скролл останавливает.

Или воспользуйтесь поиском по форуму:

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

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