5

我正在尝试学习 NASM 汇编,但我似乎在为高级语言中看似简单的东西而苦苦挣扎。

我使用的所有教科书都讨论了使用字符串——事实上,这似乎是他们最喜欢的东西之一。打印hello world,从大写变为小写等。

但是,我试图了解如何在 NASM 程序集中增加和打印十六进制数字,但不知道如何继续。例如,如果我想以十六进制打印 #1 - n,我将如何在不使用 C 库的情况下执行此操作(我已经能够找到使用的所有引用)?

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

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

4

4 回答 4

9

首先编写一个简单的例程,它将一个 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
}
于 2010-10-04T08:53:36.513 回答
1

这是家庭作业吗?

比特就是比特。位,字节,字,双字,这些是硬件术语,指令集/汇编器将参考。十六进制、十进制、八进制、无符号、有符号、字符串、字符等都是编程语言的表现形式。同样.text、.bss、.data等也是软件工具的体现,指令集不关心一个地址是.data,一个是.text,无论哪种方式都是相同的指令。所有这些编程语言事物的存在是有原因的,有时是非常好的原因,但在尝试解决这个问题时不要感到困惑。

要将位转换为人类可读的 ascii,首先需要了解您的 ascii 表、位运算符、和、或、逻辑移位、算术移位等。再加上加载和存储等。

从数学上考虑从寄存器/内存中的某个数字到 ascii hex 需要什么。说 0x1234,即 0b0001001000110100。对于人类阅读它,是的,您需要将它放入一个字符串中,因为缺少更好的术语,但您不一定需要在相邻的内存位置存储四个字符加上一个空值才能对其进行处理。这取决于您的输出功能。通常基于字符的输出实体归结为多次调用的某种类型的单个 output_char()。

您可以转换为字符串,但这需要更多的工作,因为您计算的每个 ascii 字符都会立即调用某种基于单字符的输出函数。putchar() 是字节输出字符类型函数的示例。

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

Hex有个问题就是我们要使用的16个字符在ascii表中找不到相邻的。因此,根据您的偏好或要求,您使用 0x30 到 0x39 表示 0 到 9,但使用 0x41 到 0x46 或 0x61 到 0x66 表示 A 到 F。因此,对于每个 nybble,您可能会使用 0xF 与 9 进行比较并添加 0x30 或 0x37(10+0x37 = 0x41、11+0x37 = 0x42 等)。

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

void showbin ( 无符号字符 x )
{
    无符号字符 ra;

    对于(ra=0x80;ra;ra>>=1)
    {
        if(ra&x) output_char(0x31); 否则输出字符(0x30);
    }
}

使用上面的 unsigned char 似乎是合乎逻辑的,但 unsigned int 取决于目标处理器,可以生成更好(更干净/更快)的代码。但那是另一个话题

上面的内容在汇编程序中可能看起来像这样(故意不使用 x86)

...
 移动 r4,r0
 mov r5,#0x80
最佳:
 tst r4,r5
 moveq r0,#0x30
 移动 r0,#0x31
 bl output_char
 mov r5,r5, lsr #1
 cmp r5,#0
 顶部
 ...

展开更容易编写并且会更快一些,权衡是使用更多的内存

...
 tst r4,#0x80
 moveq r0, #0x30
 移动 r0,#0x31
 bl output_char
 tst r4,#0x40
 moveq r0, #0x30
 移动 r0,#0x31
 bl output_char
 tst r4,#0x20
 moveq r0, #0x30
 移动 r0,#0x31
 bl output_char
 ...

假设您有 9 位数字并想转换为八进制。一次取三位(记住人类从左到右阅读,所以从高位开始)并添加 0x30 以获得 0x30 到 0x37。

...
移动 r4,r0
移动 r0,r4,lsr #6
和 r0,r0,#0x7
添加 r0,r0,#0x30
bl output_char
移动 r0,r4,lsr #3
和 r0,r0,#0x7
添加 r0,r0,#0x30
bl output_char
和 r0,r4,#0x7
添加 r0,r0,#0x30
bl output_char
...

十六进制的单个(8 位)字节可能如下所示:

...
移动 r4,r0
移动 r0,r4,lsr #4
和 r0,r0,#0xF
cmp r0,#9
添加 r0,r0,#0x37
添加 r0,r0,#0x30
bl output_character
和 r0,r4,#0xF
cmp r0,#9
添加 r0,r0,#0x37
添加 r0,r0,#0x30
bl output_character
...

进行从 1 到 N 的循环,将该值存储在内存中并从内存 (.data) 中读取它,以十六进制输出:

...
mov r4,#1
str r4,my_variable
...
最佳:
ldr r4,my_variable
移动 r0,r4,lsr #4
和 r0,r0,#0xF
cmp r0,#9
添加 r0,r0,#0x37
添加 r0,r0,#0x30
bl output_character
和 r0,r4,#0xF
cmp r0,#9
添加 r0,r0,#0x37
添加 r0,r0,#0x30
bl output_character
...
ldr r4,my_variable
添加 r4,r4,#1
str r4,my_variable
cmp r4,#7 ;说N是7
顶部
...
我的变量.word 0

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

x86 与上述 (ARM) 汇编器不同,因此留给读者练习以计算出等价物。关键是,它是移位、加、加,把它分解成基本的步骤,指令自然地从那里掉出来。

于 2010-10-05T06:54:38.043 回答
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>

<>使用的原因是.altmacro默认参数中带有百分号的 Gas altmacro 宏因“% 运算符需要绝对表达式”而失败

结果:

  • %al包含 0x31 ,它是'1'ASCII
  • %bl包含 0x41 ,它是'A'ASCII

%al现在你可以用and做任何你想做的事情%bl,例如:

  • 循环多个字节并将它们复制到内存(确保分配的内存是字节的两倍)
  • 使用系统或 BIOS 调用打印它们
于 2015-09-24T08:02:41.120 回答
-1

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

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
于 2018-04-17T20:01:29.500 回答