用汇编打印十六进制数字

时间:2010-10-04 08:41:52

标签: assembly x86 nasm masm

我正在尝试学习NASM程序集,但我似乎正在努力解决高级语言中的问题。

我正在使用的所有教科书都使用字符串进行讨论 - 事实上,这似乎是他们最喜欢的东西之一。打印你好世界,从大写变为小写等

但是,我试图了解如何增加和打印NASM程序集中的十六进制数字,并且不知道如何继续。例如,如果我想在Hex中打印#1 - n,那么如果不使用C库(我能够找到所有引用,我将如何使用),我该怎么办呢?

我的主要想法是在.data部分有一个变量,我将继续增加。但是如何从此位置提取十六进制值?我似乎需要先将它转换为字符串......?

任何建议或示例代码都将不胜感激。

4 个答案:

答案 0 :(得分:8)

首先编写一个简单的例程,它将nybble值(0..15)作为输入并输出一个十六进制字符('0'..'9','A'..'F')。

接下来编写一个例程,它接受一个字节值作为输入,然后调用上面的例程两次输出2个十六进制字符,即每个nybble一个。

最后,对于一个N字节整数,你需要一个例程,它调用第二个例程N次,每个字节一次。

您可能会发现在伪代码或HL等首先表达它有帮助,然后考虑如何将其转换为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
}

答案 1 :(得分:1)

这是家庭作业吗?

比特是比特。位,字节,字,双字,这些是硬件术语,指令集/汇编器将参考。 hex,decimal,octal,unsigned,signed,string,character等是编程语言的表现形式。同样,.text,.bss,.data等也是软件工具的表现形式,指令集并不关心一个地址是.data,一个是.text,它是同一条指令。所有这些编程语言都存在的原因有很多,有时是非常好的理由,但在尝试解决这个问题时不要混淆。

要从位转换为人类可读的ascii,首先需要知道你的ascii表,按位运算符,以及逻辑移位,算术移位等。加载和存储等等。

从数学上思考从寄存器/内存中的某个数字到ascii hex的内容。比如0x1234,即0b0001001000110100。对于一个人来阅读它,是的,你需要把它变成一个字符串,因为缺少一个更好的术语,但是你不一定需要在相邻的内存位置存储四个字符和一个null,以便用它做一些事情。这取决于您的输出功能。通常,基于字符的输出实体可以归结为多次调用的某种output_char()。

你可以转换为一个字符串,但这更有效,因为你计算的每个ascii字符都会调用某种基于单字符的输出函数。 putchar()是字节输出字符类型函数的示例。

因此对于二进制文件,您希望一次检查一个位并创建0x30或0x31。对于八进制,一次3位,并创建0x30到0x37。十六进制一次基于4位。

Hex有一个问题,就是在ascii表中找不到我们想要使用的16个字符。因此,对于A到F,您使用0x30至0x39表示0至9但0x41至0x46或0x61至0x66,具体取决于您的偏好或要求。因此,对于每个nybble,您可以使用0xF,与9和ADD 0x30或0x37(10 + 0x37 = 0x41,11 + 0x37 = 0x42等)进行比较。

从寄存器中的位转换为二进制的ascii表示。如果内存中的位为1则显示该位的1(0x31 ascii)为0,则显示0(ascii中的0x30)。

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
 ...

展开更容易编写并且速度更快,权衡更多的内存

 ...
 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

如果你有足够的寄存器,保存到ram有点浪费。虽然使用x86,您可以直接在内存上操作,而不必通过寄存器。

x86与上面的(ARM)汇编程序不同,所以它留给读者练习等效。关键是,正在转移,分析和添加这个问题,将其分解为基本步骤,指令自然会从那里掉出来。

答案 2 :(得分:1)

快速而肮脏的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>
由于<>Gas altmacro macro with a percent sign in a default parameter fails with "% operator needs absolute expression"

,因此使用

.altmacro

结果:

  • %al包含0x31,在ASCII
  • 中为'1'
  • %bl包含0x41,在ASCII
  • 中为'A'

现在,您可以使用%al%bl执行任何操作,例如:

  • 循环多个字节并将它们复制到内存(确保分配的内存是字节数的两倍)
  • 使用系统或BIOS调用打印它们

答案 3 :(得分:0)

英特尔语法。这是来自我的引导程序,但你应该能够理解。

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