Печать шестнадцатеричных цифр со сборкой
Я пытаюсь изучить сборку NASM, но, похоже, я стараюсь бороться с тем, что кажется просто на языках высокого уровня.
Все учебники, которые я использую, обсуждают с использованием строк – на самом деле, это, кажется, одна из их любимых вещей. Печать привет мир, переход от верхнего и нижнего регистра и т. Д.
Тем не менее, я пытаюсь понять, как увеличивать и печатать шестнадцатеричные цифры в сборке NASM и не знаю, как действовать. Например, если я хочу напечатать # 1 – n в Hex, как бы мне это сделать без использования библиотек C (какие все ссылки мне удалось найти)?
- Почему EDX должен быть 0, прежде чем использовать инструкцию DIV?
- Ассемблер ADC (добавить с переносом) в C ++
- Ссылка на содержимое ячейки памяти. (режимы адресации x86)
- Как слить скаляр в вектор без компилятора, теряющего инструкцию обнуления верхних элементов? Ограничение дизайна в встроенных средах Intel?
- Вызов 32-битного кода из 64-битного процесса
Моя основная идея заключалась бы в том, чтобы иметь переменную в разделе .data, которую я бы продолжал увеличивать. Но как мне извлечь шестнадцатеричное значение из этого места? Кажется, мне нужно преобразовать его в строку сначала …?
Любой совет или образец кода будут оценены.
- В чем разница между MOV и LEA?
- Цикл с вызовом функции быстрее, чем пустой цикл
- что делает инструкция movsbl?
- Код C ++ для проверки гипотезы Collatz быстрее, чем assembly вручную - почему?
- Как вы используете gcc для генерации кода сборки в синтаксисе Intel?
- Windows 64-разрядный реестр против 32-разрядного реестра
- Почему SSE скалярный sqrt (x) медленнее, чем rsqrt (x) * x?
- Что такое рамка стека в сборке?
Сначала напишите простую процедуру, которая принимает значение nybble (0..15) в качестве входных данных и выводит шестнадцатеричный символ (‘0’ .. ‘9’, ‘A’ .. ‘F’).
Затем напишите процедуру, которая принимает значение байта в качестве входного сигнала, а затем дважды вызывает указанную выше процедуру для вывода 2 шестнадцатеричных символов, то есть по одному для каждого nybble.
Наконец, для целочисленного N байта вам нужна процедура, которая вызывает эту вторую процедуру N раз, по одному для каждого байта.
Возможно, вам будет полезно выразить это в псевдокоде или HLL, таком как C сначала, а затем подумайте о том, как перевести это в asm, например
void print_nybble(uint8_t n) { if (n < 10) // handle '0' .. '9' putchar(n + '0'); else // handle 'A'..'F' putchar(n - 10 + 'A'); } void print_byte(uint8_t n) { print_nybble(n >> 4); // print hi nybble print_nybble(n & 15); // print lo nybble } print_int16(uint16_t n) { print_byte(n >> 8); // print hi byte print_byte(n & 255); // print lo byte }
Это домашнее задание?
Биты – бит. Бит, байт, слово, двойное слово, это аппаратные термины, что-то наборы инструкций / ассемблер будут ссылаться. шестнадцатеричные, десятичные, восьмеричные, неподписанные, подписанные, строковые, символьные и т. д. являются проявлениями языков программирования. Аналогично .text, .bss, .data и т. Д. Также являются проявлениями программных инструментов, набор инструкций не заботится о том, чтобы один адрес был .data и один из них был .text, это одна и та же инструкция в любом случае. Есть причины, по которым все эти вещи на языке программирования существуют, иногда очень хорошие причины, но не пытайтесь решить эту проблему.
Чтобы преобразовать из бит в человеческий читаемый ascii, вам сначала нужно знать свою таблицу ascii и побитовые операторы, а также, логический сдвиг, арифметический сдвиг и т. Д. Плюс загружать и хранить и другие вещи.
Подумайте математически, что нужно, чтобы получить от некоторого числа в регистре / памяти в ascii hex. Скажите 0x1234, который равен 0b0001001000110100. Чтобы человек мог его прочитать, да, вам нужно получить его в строку из-за отсутствия лучшего термина, но вам необязательно хранить четыре символа плюс нуль в соседних ячейках памяти, чтобы что-то с ним делать. Это зависит от вашей выходной функции. Обычно объекты вывода, основанные на символах, сводятся к одному виду output_char (), который называется много раз.
Вы можете преобразовать в строку, но это больше работает, для каждого символа ascii вы вычисляете вызов как бы одноразовой выходной функции. putchar () – пример функции символа выходного символа байта.
Таким образом, для двоичных файлов вы хотите изучить один бит за раз и создать 0x30 или 0x31. Для восьмеричных, 3 бита за раз и создайте от 0x30 до 0x37. Hex основан на 4 бит за раз.
У Hex есть проблема, что 16 символов, которые мы хотим использовать, не находятся рядом друг с другом в таблице ascii. Таким образом, вы используете от 0x30 до 0x39 от 0 до 9, но от 0x41 до 0x46 или от 0x61 до 0x66 для A-F в зависимости от ваших предпочтений или требований. Таким образом, для каждого nybble вы можете И с 0xF, сравнить с 9 и ADD 0x30 или 0x37 (10 + 0x37 = 0x41, 11 + 0x37 = 0x42 и т. Д.).
Преобразование из битов в регистр в ascii-представление двоичного. Если бит в памяти был 1, то 1 бит (0x31 ascii) бит был равен 0, 0 (0x30 в ascii).
void showbin (unsigned char x) { unsigned char ra; для (ра = 0x80; ра; ра >> = 1) { if (ra & x) output_char (0x31); else output_char (0x30); } }
Может показаться логичным использовать unsigned char выше, но unsigned int, в зависимости от целевого процессора, может создать гораздо лучший (более чистый / быстрый) код. но это еще одна тема
Вышеприведенное может выглядеть так, как показано на ассемблере (намеренно НЕ используя x86)
... mov r4, r0 mov r5, # 0x80 Вверх: tst r4, r5 moveq r0, # 0x30 movne r0, # 0x31 bl output_char mov r5, r5, lsr # 1 cmp r5, # 0 bne top ...
Развернуть легче писать и будет немного быстрее, компромисс больше используется памятью
... tst r4, # 0x80 moveq r0, # 0x30 movne r0, # 0x31 bl output_char tst r4, # 0x40 moveq r0, # 0x30 movne r0, # 0x31 bl output_char tst r4, # 0x20 moveq r0, # 0x30 movne r0, # 0x31 bl output_char ...
Скажем, у вас было 9 бит чисел и вы хотели конвертировать в восьмеричные. Принимайте по три бита за раз (помните, что люди читают слева направо, поэтому начинайте с верхних бит) и добавьте 0x30, чтобы получить 0x30 – 0x37.
... mov r4, r0 mov r0, r4, lsr # 6 и r0, r0, # 0x7 добавьте r0, r0, # 0x30 bl output_char mov r0, r4, lsr # 3 и r0, r0, # 0x7 добавьте r0, r0, # 0x30 bl output_char и r0, r4, # 0x7 добавьте r0, r0, # 0x30 bl output_char ...
Один (8 бит) байт в шестнадцатеричном виде может выглядеть так:
... mov r4, r0 mov r0, r4, lsr # 4 и r0, r0, # 0xF cmp r0, # 9 addhi r0, r0, # 0x37 addls r0, r0, # 0x30 bl output_character и r0, r4, # 0xF cmp r0, # 9 addhi r0, r0, # 0x37 addls r0, r0, # 0x30 bl output_character ...
Создание цикла от 1 до N, сохранение этого значения в памяти и чтение его из памяти (.data), вывод в шестнадцатеричном виде:
... mov r4, # 1 str r4, my_variable ... Вверх: ldr r4, my_variable mov r0, r4, lsr # 4 и r0, r0, # 0xF cmp r0, # 9 addhi r0, r0, # 0x37 addls r0, r0, # 0x30 bl output_character и r0, r4, # 0xF cmp r0, # 9 addhi r0, r0, # 0x37 addls r0, r0, # 0x30 bl output_character ... ldr r4, my_variable добавить r4, r4, # 1 str r4, my_variable cmp r4, # 7, N - 7 bne top ... my_variable .word 0
Сохранение в плунжер – это немного отходов, если у вас достаточно регистров. Хотя с x86 вы можете работать непосредственно в памяти и не должны проходить через регистры.
x86 не совпадает с указанным выше (ARM) ассемблером, поэтому он остается как упражнение читателя для разработки эквивалента. Дело в том, что это смещение, а также добавление этой материи, разбиение ее на элементарные шаги и инструкции оттуда естественно оттуда.
Быстрый и грязный макрос ГАЗ
.altmacro /* Convert a byte to hex ASCII value. c: r/m8 byte to be converted Output: two ASCII characters, is stored in `al:bl` */ .macro HEX c mov \c, %al mov \c, %bl shr $4, %al HEX_NIBBLE al and $0x0F, %bl HEX_NIBBLE bl .endm /* Convert the low nibble of a r8 reg to ASCII of 8-bit in-place. reg: r8 to be converted Output: stored in reg itself. */ .macro HEX_NIBBLE reg LOCAL letter, end cmp $10, %\reg jae letter /* 0x30 == '0' */ add $0x30, %\reg jmp end letter: /* 0x57 == 'A' - 10 */ add $0x57, %\reg end: .endm
Применение:
mov $1A, %al HEX <%al>
<>
используются из-за .altmacro
: макрос газа altmacro с знаком процента в параметре по умолчанию не выполняется: «% оператор нуждается в абсолютном выражении»
Результат:
-
%al
содержит 0x31, что является'1'
в ASCII -
%bl
содержит 0x41, что означает'A'
в ASCII
Теперь вы можете делать все, что хотите, с помощью %al
и %bl
, например:
- цикл на несколько байтов и скопировать их в память (не забудьте выделить в два раза больше памяти, чем есть байт)
- распечатывать их с помощью вызовов системы или BIOS
Синтаксис Intel. Это от моего загрузчика, но вы должны иметь возможность получить эту идею.
print_value_of_CX: print_value_of_C_high: print_value_of_C_high_high_part: MOV AH, CH SHR AH, 0x4 CALL byte_hex_printer print_value_of_C_high_low_part: MOV AH, CH SHL AH, 0x4 SHR AH, 0x4 CALL byte_hex_printer print_value_of_C_low: print_value_of_C_low_high_part: MOV AH, CL SHR AH, 0x4 CALL byte_hex_printer print_value_of_C_low_low_part: MOV AH, CL SHL AH, 0x4 SHR AH, 0x4 CALL byte_hex_printer byte_hex_printer: CMP AH, 0x00 JE move_char_for_zero_into_AL_to_print CMP AH, 0x01 JE move_char_for_one_into_AL_to_print CMP AH, 0x02 JE move_char_for_two_into_AL_to_print CMP AH, 0x03 JE move_char_for_three_into_AL_to_print CMP AH, 0x04 JE move_char_for_four_into_AL_to_print CMP AH, 0x05 JE move_char_for_five_into_AL_to_print CMP AH, 0x06 JE move_char_for_six_into_AL_to_print CMP AH, 0x07 JE move_char_for_seven_into_AL_to_print CMP AH, 0x08 JE move_char_for_eight_into_AL_to_print CMP AH, 0x09 JE move_char_for_nine_into_AL_to_print CMP AH, 0x0A JE move_char_for_A_into_AL_to_print CMP AH, 0x0B JE move_char_for_B_into_AL_to_print CMP AH, 0x0C JE move_char_for_C_into_AL_to_print CMP AH, 0x0D JE move_char_for_D_into_AL_to_print CMP AH, 0x0E JE move_char_for_E_into_AL_to_print CMP AH, 0x0F JE move_char_for_F_into_AL_to_print move_char_for_zero_into_AL_to_print: MOV AL, 0x30 CALL print_teletype_stringB RET move_char_for_one_into_AL_to_print: MOV AL, 0x31 CALL print_teletype_stringB RET move_char_for_two_into_AL_to_print: MOV AL, 0x32 CALL print_teletype_stringB RET move_char_for_three_into_AL_to_print: MOV AL, 0x33 CALL print_teletype_stringB RET move_char_for_four_into_AL_to_print: MOV AL, 0x34 CALL print_teletype_stringB RET move_char_for_five_into_AL_to_print: MOV AL, 0x35 CALL print_teletype_stringB RET move_char_for_six_into_AL_to_print: MOV AL, 0x36 CALL print_teletype_stringB RET move_char_for_seven_into_AL_to_print: MOV AL, 0x37 CALL print_teletype_stringB RET move_char_for_eight_into_AL_to_print: MOV AL, 0x38 CALL print_teletype_stringB RET move_char_for_nine_into_AL_to_print: MOV AL, 0x39 CALL print_teletype_stringB RET move_char_for_A_into_AL_to_print: MOV AL, 0x41 CALL print_teletype_stringB RET move_char_for_B_into_AL_to_print: MOV AL, 0x42 CALL print_teletype_stringB RET move_char_for_C_into_AL_to_print: MOV AL, 0x43 CALL print_teletype_stringB RET move_char_for_D_into_AL_to_print: MOV AL, 0x44 CALL print_teletype_stringB RET move_char_for_E_into_AL_to_print: MOV AL, 0x45 CALL print_teletype_stringB RET move_char_for_F_into_AL_to_print: MOV AL, 0x46 CALL print_teletype_stringB RET