x86汇编字符串缓冲区编号为ASCII

时间:2016-11-18 07:03:44

标签: linux assembly x86 nasm qemu

我正在编写一个x86汇编程序,以十六进制输出一个数字。该程序使用nasm进行汇编,图像文件由qemu运行。程序的行为使我很困惑。正如下面的工作程序所示,我不需要在数字上添加0x30以使其打印该数字的字符。

func TestMain(m *testing.M) {
    mysqld, err := mysqltest.NewMysqld(nil)
    if err != nil {
        log.Fatal("runTests: failed", err)
    }

    defer mysqld.Stop()
    dbm, err = gorm.Open("mysqld", mysqld.Datasource("test", "", "", 0 ))
    if err != nil {
        log.Fatal("db connection error:", err)
    }

    defer dbm.Close()
    code := m.Run()
    os.Exit(code)
}

boot_sect.asm

    ; Boot sector code offset: 0x7c00
[org 0x7c00]

    mov dx, 0x1fb6 ; The hexadecimal to be printed
    call print_hex ; call the function
    jmp $ ; jump infinitely

%include "print_string.asm" ; Include the print_string function

print_hex:
    pusha ; push all registers to stack
    mov ax, 0x4 ; rotate through the number four times
print_hex_loop:
    cmp ax, 0x0 ; compare the counter with 0
    jle print_hex_end ; if it is zero then jump to the end  
    mov cx, dx ; move dx to cx
    and cx, 0x000F ; take the lower four binary digits of cx
    cmp cx, 0xa ;compare the digits with 0xa
    jge print_hex_letter ; if it is larger than a, jump to printing character
    add cx, 0x0 ; otherwise print the ascii of a number
    jmp print_hex_modify_string ; jump to routine for modifing the template
print_hex_letter:
    add cx, 0x7 ; print the ascii of a letter
print_hex_modify_string:
    mov bx, HEX_OUT ; bring the address of HEX_OUT into dx
    add bx, 0x1 ; skip the 0x
    add bx, ax ; add the bias
    add byte [bx], cl ; move the character into its position
    shr dx, 4 ; shift right 4 bits
    sub ax, 0x1 ; subtract 1 from the counter
    jmp print_hex_loop ; jump back to the start of the function
print_hex_end:
    mov bx, HEX_OUT ; move the address of HEX_OUT to bx
    call print_string ; call the function print_string
    popa ; pop all registers from stack
    ret ; return to calling function

HEX_OUT:
    db '0x0000',0 ; The template string for printing

    times 510-($-$$) db 0 ; fill zeros
    dw 0xaa55 ; MAGIC_FLAG for boot

print_string.asm

这个程序的输出是我所期望的,但是当我试图在数字上添加0x30以获得数字的ASCII码时,输出是乱码。是否有一些技巧或我错过了一些关键点?

谢谢!

1 个答案:

答案 0 :(得分:1)

原始问题的答案:

因为你add byte [bx], cl将数字写入缓冲区,并且缓冲区已经包含'0',所以它第一次正常工作。第二次调用print_hex会再次产生乱码,因为HEX_OUT内容已被修改(琐事:首先打印的十六进制数字也允许正确打印某些第二个值?)。

现在只是为了好玩,我添加可能会为自己做print_hex。也许它会为你的x86 ASM编程提供额外的想法,我试着对它做出很多评论来解释为什么我会按照我这样做的方式做事:

首先,我将分离格式化函数,因此我最终可以在其他地方重用它,因此输入是数字和目标缓冲区指针。我使用LUT(查找表)进行ASCII转换,因为代码更简单。如果您关心大小,可以在代码中以较少的字节进行分支,并使用较慢的pusha / popa来保存寄存器。

format_hex:
    ; dx = number, di = 4B output buffer for "%04X" format of number.
    push    bx              ; used as temporary to calculate digits ASCII
    push    si              ; used as pointer to buffer for writing chars
    push    dx
    lea     si,[di+4]       ; buffer.end() pointer
format_hex_loop:    
    mov     bx,dx           ; bx = temporary to extract single digit
    dec     si              ; si = where to write next digit
    and     bx,0x000F       ; separate last digit (needs whole bx for LUT indexing)
    shr     dx,4            ; shift original number one hex-digit (4 bits) to right
    mov     bl,[format_hex_ascii_lut+bx]    ; convert digit 0-15 value to ASCII
    mov     [si],bl         ; write it into buffer
    cmp     di,si           ; compare buffer.begin() with pointer-to-write
    jb      format_hex_loop ; loop till first digit was written
    pop     dx              ; restore original values of all modified regs
    pop     si
    pop     bx
    ret
format_hex_ascii_lut:       ; LUT for 0-15 to ASCII conversion
    db      '0123456789ABCDEF'

然后为方便起见,也可以添加print_hex函数,提供自己的缓冲区,用于格式化" 0x"和nul终结者:

print_hex:
    ; dx = number to print
    push    di
    push    bx
    ; format the number
    mov     di,HEX_OUT+2
    call    format_hex
    ; print the result to screen
    lea     bx,[di-2]       ; bx = HEX_OUT
    ; HEX_OUT was already set with "0x" and nul-terminator, otherwise I would do:
    ; mov       word [bx],'0x'
    ; mov       byte [bx+6],0
    call    print_string
    pop     bx
    pop     di
    ret
HEX_OUT:
    db      '0x1234',0      ; The template string for printing

最后从启动代码中使用示例:

    mov     dx,0x1fb6       ; The hexadecimal to be printed
    call    print_hex
    mov     dx,ax           ; works also when called second time
    call    print_hex       ; (but would be nicer to print some space between them)

    jmp     $               ; loop infinitely

(我确实在某种程度上验证了这个代码(它将编译和运行),虽然只是通过它的单独部分和32b环境(修补几行使其成为32b),所以一些bug可能已经滑入。我没有16b环境来验证它是完整的启动代码。)