Печать шестнадцатеричных цифр со сборкой

Я пытаюсь изучить сборку NASM, но, похоже, я стараюсь бороться с тем, что кажется просто на языках высокого уровня.

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

Однако я пытаюсь понять, как увеличивать и печатать шестнадцатеричные цифры в сборке NASM и не знаю, как действовать. Например, если я хочу напечатать # 1 - n в Hex, как бы это сделать без использования библиотек C (которые все ссылки мне удалось найти)?

Моя основная идея заключалась бы в том, чтобы иметь переменную в разделе .data, которую я бы продолжал увеличивать. Но как мне извлечь шестнадцатеричное значение из этого места? Кажется, мне нужно преобразовать его в строку сначала...?

Приветствуется любой совет или образец кода.

Ответ 1

Сначала напишите простую процедуру, которая принимает значение 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
}

Ответ 2

Это домашнее задание?

Биты - это биты. Бит, байт, слово, двойное слово, это аппаратные термины, что-то наборы инструкций/ассемблер будут ссылаться. шестнадцатеричные, десятичные, восьмеричные, неподписанные, подписанные, строковые, символьные и т.д. являются проявлениями языков программирования. Аналогично .text,.bss,.data и т.д. Также являются проявлениями программных инструментов, набор инструкций не заботится о том, чтобы один адрес был .data и один из них был .text, это одна и та же инструкция в любом случае. Есть причины, почему все эти вещи языка программирования существуют, иногда очень хорошие причины, но не путайте при попытке решить эту проблему.

Чтобы преобразовать из бит в человекообразный ascii, вам сначала нужно знать свою таблицу ascii и побитовые операторы, а также, логический сдвиг, арифметический сдвиг и т.д. Плюс загружать и хранить и другие вещи.

Подумайте математически, что нужно, чтобы получить от некоторого числа в регистре/памяти в ascii hex. Скажите 0x1234, который равен 0b0001001000110100. Чтобы человек мог его прочитать, да, вам нужно получить его в строку из-за отсутствия лучшего термина, но вам необязательно хранить четыре символа плюс нуль в соседних ячейках памяти, чтобы что-то с ним делать. Это зависит от вашей выходной функции. Обычно объекты вывода, основанные на символах, сводятся к одному виду output_char(), который называется много раз.

Вы можете преобразовать в строку, но это больше работает, для каждого символа ascii вы вычисляете вызов какой-то одноразовой выходной функции. putchar() - пример функции символа выходного символа байта.

Итак, для двоичного кода вы хотите изучить один бит за раз и создать 0x30 или 0x31. Для восьмеричных, 3 бита за раз и создайте от 0x30 до 0x37. Hex основан на 4 бит за раз.

В шестнадцатеричном режиме проблема состоит в том, что 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 - 0 (0x30 в ascii).

void showbin ( unsigned char x )
{
    unsigned char ra;

    for(ra=0x80;ra;ra>>=1)
    {
        if(ra&x) output_char(0x31); else output_char(0x30);
    }
}

Может показаться логичным использовать unsigned char выше, но unsigned int, в зависимости от целевого процессора, может обеспечить гораздо лучший (более чистый/быстрый) код. но это еще одна тема

Вышеприведенное может выглядеть так, как показано на ассемблере (намеренно НЕ используя x86)

 ...
 mov r4,r0
 mov r5,#0x80
top:
 tst r4,r5
 moveq r0,#0x30
 movne r0,#0x31
 bl output_char
 mov r5,r5, lsr #1
 cmp r5,#0
 bne top
 ...

Unrolled легче писать и будет немного быстрее, компромисс больше используется в памяти

 ...
 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
and r0,r0,#0x7
add r0,r0,#0x30
bl output_char
mov r0,r4,lsr #3
and r0,r0,#0x7
add r0,r0,#0x30
bl output_char
and r0,r4,#0x7
add r0,r0,#0x30
bl output_char
...

Один (8 бит) байт в шестнадцатеричном виде может выглядеть так:

...
mov r4,r0
mov r0,r4,lsr #4
and r0,r0,#0xF
cmp r0,#9
addhi r0,r0,#0x37
addls r0,r0,#0x30
bl output_character
and 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
...
top:
ldr r4,my_variable
mov r0,r4,lsr #4
and r0,r0,#0xF
cmp r0,#9
addhi r0,r0,#0x37
addls r0,r0,#0x30
bl output_character
and r0,r4,#0xF
cmp r0,#9
addhi r0,r0,#0x37
addls r0,r0,#0x30
bl output_character
...
ldr r4,my_variable
add r4,r4,#1
str r4,my_variable
cmp r4,#7 ;say N is 7
bne top
...
my_variable .word 0

Сохранение в плунжер - это немного отходов, если у вас достаточно регистров. Хотя с x86 вы можете работать непосредственно в памяти и не должны проходить через регистры.

x86 не совпадает с указанным выше (ARM) ассемблером, поэтому он остается как упражнение читателя для разработки эквивалента. Дело в том, что это смещение, а также добавление этой материи, разбиение ее на элементарные шаги и инструкции оттуда естественно оттуда.

Ответ 3

Быстрый и грязный макрос GAS

.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 с знаком процента в параметре по умолчанию не выполняется с "% operator требуется абсолютное выражение"

Результат:

  • %al содержит 0x31, что '1' в ASCII
  • %bl содержит 0x41, что 'A' в ASCII

Теперь вы можете делать все, что хотите, с помощью %al и %bl, например:

  • объединить несколько байтов и скопировать их в память (не забудьте выделить в два раза больше памяти, чем байт)
  • распечатать их с помощью вызовов системы или BIOS.