【问题标题】:Assembly: how to convert number into ascii and write to display buffer汇编:如何将数字转换为ASCII并写入显示缓冲区
【发布时间】:2016-09-14 23:55:36
【问题描述】:

我是汇编新手,正在使用 AT&T 语法在 linux 64 位中编程。如果我将数字 1 存储在寄存器中,如何将其转换为 ascii 字符“A”?例如:

movl $1, %ebx
addl $64, %ebx

我可以将 64 加到 1 以得到 65(A 的十进制值),然后以某种方式将其转换为“A”并使用 write 系统调用将其发送到缓冲区吗?

编辑 1:在这里发布我的程序代码。

.section .data

message:
        .long 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

length:
        .long 10

.section .text

.globl _start

_start:

xorq %rdi, %rdi  
xorq %rax, %rax  
xorq %rbx, %rbx  
xorq %rcx, %rcx                  
xorq %rdx, %rdx  
movl length, %edx

loop:

        cmpl %ecx, %edx                 
        je loop_end                     
        movl message(,%rdi,4), %eax     
        addl $64, %eax                  
        pushq %rax                      
        incq %rdi                       
        incq %rcx                       
        jmp loop                        



loop_end:

        cmpq $0, %rcx                   
        je exit                         
        popq %rbx                       
        pushq %rcx
        movq $1, %rax
        movq $1, %rdi
        movq %rbx, %rsi                 
        movl length, %edx
        syscall                         
        popq %rcx
        decq %rcx
        jmp loop_end

exit:

        movq $60, %rax
        movq $0, %rdi
        syscall

【问题讨论】:

  • 是的,您甚至可以使用add $'A', %ebx 使您的代码更易于阅读,并记录常量的用途。字符 整数,所以是的,您只需将字节存储在内存中并将指向该内存的指针传递给 write(0, buf, 1)
  • 好的,但是如果我不想使用常量,我如何将 %ebx 寄存器中的 $65 转换为“A”?他们的任何操作都会转化为 ascii 吗?
  • 创建一个临时缓冲区。在其中放入一个字符(65 是一个已经代表一个字符的值)。将缓冲区的地址传递给sys_write系统调用。
  • @Mic: 65 'A',英文字母A的ASCII编码。无需进一步转换或修改;它已经是一个字符值。 (抱歉,我应该说 add $'A'-1, %ebx,因为您希望 EBX 索引从 A=1 开始的字母表。64'@'。使用符号常量的一个好处是不必经常查找 ASCII 表.)
  • 谢谢你们的回答。也许您可以查看我的代码并为我指明正确的方向。我将其发布在主要问题中。当我运行它时,它不会产生任何东西。我想我希望看到如下输出:a,b,c,d,e,f,g,h,i,j

标签: linux assembly x86-64 gnu-assembler att


【解决方案1】:

我对 AT&T 语法并不完全熟悉,但是按照您习惯的方式对 NASM 进行反汇编就足够了。

您应该尽量避免所谓的硬编码常量,因为它会使您的程序更难维护,尤其是当它的长度有数百行甚至数千行时。因此;

            section .data       
Values:     db      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 26, 18, 12, 20, 19, 11
V_Size      equ     $ - Values

比这个更可取

    message:
    .long 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

length:
    .long 10

您所做的并没有错,但是方法是基于您的计数,而不是汇编程序。正如已经指出的那样,使用完成工作所需的最小数据大小。在这种情况下 char 优于 long

NASM 中的这段代码

        section .text
    global  _start

_start: xor     ecx, ecx
        push    rcx                 ; Applications default return value
        mov      cl, V_Size
        push    rcx
        mov     ebx, Values
        push    rbx

    Next:
        or      byte [ebx], 64
        inc     ebx
        loop    Next

        pop     rsi
        pop     rdx
        pop     rax
        inc      al
        mov     edi, eax
        syscall

        mov     edi, eax
        dec     edi
        mov     eax, edi
        mov      al, 60
        syscall

        section .data       
Values:     db      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 26, 18, 12, 20, 19, 11
V_Size      equ     $ - Values

将产生

ABCDEFGHIJZRLTSK

在“K”之后立即使用命令提示符。

            section .data:
 6000d8 01020304 05060708 090a1a12 0c14130b

        section .text:

<_start>:   These two instructions are idiosyncratic to my style of programming and not
            essential to functionality of program. 

  4000b0:   31 c9                   xor    %ecx,%ecx
  4000b2:   51                      push   %rcx

            Setup RCX & RBX for LOOP instruction

  4000b3:   b1 10                   mov    $0x10,%cl
  4000b5:   51                      push   %rcx                 ARG2 to syscall
  4000b6:   bb d8 00 60 00          mov    $0x6000d8,%ebx
  4000bb:   53                      push   %rbx                 ARG1 to syscall

<Next>:     This conforms to the scope of your objective.

  4000bc:   67 80 0b 40             orb    $0x40,(%ebx)         [ebx] += 'A'
  4000c0:   ff c3                   inc    %ebx
  4000c2:   e2 f8                   loop   4000bc <Next>

            ssize_t write (int fd, const void *buf, size_t count);

  4000c4:   5e                      pop    %rsi                 ARG1 = ASCII Pntr
  4000c5:   5a                      pop    %rdx                 ARG2 = # of chars
  4000c6:   58                      pop    %rax
  4000c7:   fe c0                   inc    %al                  SYS_WRITE
  4000c9:   89 c7                   mov    %eax,%edi            ARG0 = STD_OUT
  4000cb:   0f 05                   syscall

            Epilogue: Again, just a method I use.

  4000cd:   89 c7                   mov    %eax,%edi  
  4000cf:   ff cf                   dec    %edi
  4000d1:   89 f8                   mov    %edi,%eax
  4000d3:   b0 3c                   mov    $0x3c,%al
  4000d5:   0f 05                   syscall 

【讨论】:

  • 你为什么要使用这种奇怪的未注释的 NASM 代码来设置系统调用的寄存器?您的 SYS_exit (eax=60) 似乎依赖于 write 返回一个正值(不是错误代码)。因此,您可以通过运行 ./a.out &gt; /dev/full./a.out &gt;&amp;-(关闭标准输出)或任何其他方式使 sys_write 失败,使您的程序运行 syscalleax=0xFFFFFF3c。内核查看整个 EAX,而不仅仅是 AL,以获取系统调用号(x86-64 系统调用号目前上升到__NR_execveat 322)。哦,我看到你在 AT&T 的 disasm 输出中有 cmets,但他们在那里没有解释任何内容。
  • 即使您确实希望退出状态依赖于 write(),编写相同代码的明智方法是指令的一半:lea edi, [rax-1]/mov eax, SYS_write。您令人困惑的 mov/dec/mov/mov imm 的优势为零。
  • 同样,从你之前疯狂的 PUSH/POP 代码中,也有零优势和很多混乱。 xor ecx, ecx / mov cl, V_Size 与 MOV r32、imm32 相比节省了一个字节,并且仅在 V_Size 适合 8 位时才有效。 (就像你说的,避免在你的程序中进行硬编码假设!)它还浪费了一条指令,这在任何支持 AMD64 的 CPU 上都比一个代码字节更重要。 LOOP is a really slow instruction that you shouldn't use either.
  • 尽管您的 cmets 具有不同程度的合法性,但对于 如何将数字转换为 ASCII 并写入显示缓冲区的问题,它们是多余的。为此,4000BC 和 4000CD 之间的代码隐含地解决了这个问题。关键是,这个例子读取数组,适当地转换它并显示在控制台上。
  • 工作代码不足以做出好的回答。重要的是它对人类观众来说是可读/可理解的。您对让汇编程序计算长度的文字说明非常好,并使您的代码的那部分清晰。您的 AT&T 反汇编中的 [ebx] += 'A' 注释可以理解这一点,但如果初学者(唯一不知道答案的人)很难遵循您的代码,我不会感到惊讶。
猜你喜欢
  • 1970-01-01
  • 2017-04-01
  • 1970-01-01
  • 2022-08-05
  • 2012-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多