汇编语言实现一个简单的十六进制转储使用工具

一个简单的十六进制转储使用工具,演示了汇编语言过程的使用。

; 可执行程序名    : hexdump2
; 版本    : 1.0
; 创建日期    : 7/9/2016
; 最后修改    : 7/9/2016
; 作者        : Moonlight Poet
; 描述        : 一个简单的十六进制转储使用工具,演示了汇编语言过程的使用。
; 
; 使用以下命令生成该程序 :
;     nasm -f elf64 -g -F stabs hexdump2.asm
;     ld -o hexdump2 hexdump2.o
; 

SECTION .bss            ; 包含未初始化数据的段
    BUFFLEN equ 10
    Buff resb BUFFLEN

SECTION .data            ; 包含已初始化数据的段

; 这里,我们使用一个由两个部分组成的简单数据结构,
; 实现一个十六进制转储使用用具的文本行。
; 第一个部分显示了16个字节的、中间用空格隔开的十六进制数。(也就是Dumplin)
; 紧跟在后面的是一个由16个字符组成的行,二者之间通过竖线(|)字符分隔。
; 因为这两个部分是相邻的,
; 所以可以被单独引用或者作为一个连续的单元来引用。
; 记住,如果要将DumpLin分开使用,在将其发送到Linux控制台之前,
; 必须追加一个EOF。

DumpLin: db " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
DUMPLEN equ $-DumpLin
ASCLin: db "|................|",10
ASCLINE equ $-ASCLin
FULLLEN equ $-DumpLin

; HexDigits表用来把数字值转换为它们的十六进制等值。
; 通过一个没有缩放的半字节来进行索引:[HexDigits + eax]
HexDigits: db "0123456789ABCDEF"

; 此表用于ASCII字符翻译,实现将其翻译成该十六进制转储行的ASCII部分
; 通过使用XLAT或者普通的内存查找。
; 所有的可打印的字符被翻译为它们本身。
; 高128个字符被转换为ASCII的句号(2Eh)
; 低128个字符中的不可打印字符也被转换为ASCII句号,例如:字符127。
DotXlat:
    db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
    db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
    db 20h,21h,22h,23h,24h,25h,26h,27h,28h,29h,2Ah,2Bh,2Ch,2Dh,2Eh,2Fh
    db 30h,31h,32h,33h,34h,35h,36h,37h,38h,39h,3Ah,3Bh,3Ch,3Dh,3Eh,3Fh
    db 40h,41h,42h,43h,44h,45h,46h,47h,48h,49h,4Ah,4Bh,4Ch,4Dh,4Eh,4Fh
    db 50h,51h,52h,53h,54h,55h,56h,57h,58h,59h,5Ah,5Bh,5Ch,5Dh,5Eh,5Fh
    db 60h,61h,62h,63h,64h,65h,66h,67h,68h,69h,6Ah,6Bh,6Ch,6Dh,6Eh,6Fh
    db 70h,71h,72h,73h,74h,75h,76h,77h,78h,79h,7Ah,7Bh,7Ch,7Dh,7Eh,2Eh
        db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
        db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
        db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
        db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
        db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
        db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
        db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
        db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
    
SECTION .text        ; 包含代码段

;----------------------------------------------------------------------------------------------------
; ClearLine:    将一个十六进制转储行字符串清零,即将其变为16个0。
; 更新日期:    7/9/2016
; 输入参数:    没有。
; 返回值:    没有。
; 修改:        没有。
; 调用:        DumpChar
; 描述:        通过16次调用DumpChar过程,每次传递给它参数0,
;          这个十六进制转储行字符串被清除为二进制0。

ClearLine:
    ;pushad        ; 保存主调程序的所有通用寄存器
    push rax
    push rdx
    mov edx,15    ; 我们将进行16次,从0开始计数
.poke:     mov eax,0    ; 告诉DumpChar插入一个'0'
    call DumpChar    ; 将'0'插入到十六进制转储字符串中
    sub edx,1    ; DEC并不影响CF!
    jae .poke    ; 如果EDX >= 0 ,回去继续循环。
    ;popad        ; 恢复所有主调程序的通用寄存器。
    pop rdx
    pop rax
    ret        ; 返回

;----------------------------------------------------------------------------------------------------
; DumpChar:    插入一个值到十六进制转储字符串中。
; 更新日期: 7/9/2016
; 输入参数: 传递一个即将被插入的值到EAX寄存器中。
;     传递这个值在改行中的位置(0-15)到EDX寄存器中。
; 返回值:     没有。
; 修改:     EAX, ASCLin, DumpLin
; 调用:     没有。
; 描述:     传递到EAX寄存器中的值将其放在十六进制传出部分,
;     又放在ASCII部分,
;     其位置为传递给EDX的值
;     当它不是一个可打印字符时,用一个空格来表示。

DumpChar:
    push rbx    ; 保存主调程序的EBX寄存器
    push rdi    ; 保存主调程序的EDI寄存器
; 首先,我们将输入的字符串插入到转储行的ASCII部分
    mov bl,byte [DotXlat+eax]    ; 将不可打印字符翻译成'.'(句号2Eh)(如果可打印就打印了,具体见DotXlat)
    mov byte [ASCLin+edx+1],bl    ; 写入ASCII码部分
; 接下来,我们把与插入字符等值的十六进制值
; 插入到转储行的十六进制部分:
    mov ebx,eax        ; 保存输入字符的第二个副本
    lea edi,[edx*2+edx]    ; 计算字符串行中的偏移量(EDX*3)
; 查找低半字节的字符,并将其插入到字符串:
    and eax,0000000Fh        ; 屏蔽掉除低半字节以外的所有位
    mov al,byte [HexDigits+eax]    ; 查找该半字节的等值字符。
    mov byte [DumpLin+edi+2],al    ; 将这个等值字符写入字符串行。
; 查找高半字节的字符,并将其插入到字符串:
    and ebx,000000F0h        ; 屏蔽掉除第二低的半字节之外的所有位
    shr ebx,4            ; 将字节的高四位移到低四位
    mov bl,byte [HexDigits+ebx]    ; 查找与该半字节的等值字符。
    mov byte [DumpLin+edi+1],bl    ; 将这个等值字符写入字符串行。
; 任务完成!让我们“回家”:
    pop rdi                ; 恢复主调程序的EDI寄存器的值
    pop rbx                ; 恢复主调程序的EBX寄存器的值
    ret                ; 返回主调程序

;----------------------------------------------------------------------------------------------------
; PrintLine: 将DumpLin现实到标准输出
; 更新日期: 7/9/2016
; 输入参数: 没有。
; 返回值: 没有。
; 修改: 没有。
; 调用: 内核sys_write
; 描述: 将十六进制转储行DumpLin显示到标准输出。
; 使用 INT 80h sys_write. 所有寄存器都被保存起来。

PrintLine:
    ;pushad            ; 保存主调程序的所有通用寄存器
    push rax
    push rbx
    push rcx
    push rdx
    mov eax,4        ; 指定sys_write调用
    mov ebx,1        ; 指定文件描述符1;标准输出
    mov ecx,DumpLin        ; 传递字符串行的偏移量
    mov edx,FULLLEN        ; 传递字符串行的大小
    int 80h            ; 进行内核调用显示字符串行
    ;popad            ; 恢复主调程序的所有通用寄存器
    pop rdx
    pop rcx
    pop rbx
    pop rax
    ret            ; 返回主调程序

;----------------------------------------------------------------------------------------------------
; LoadBuff: 通过 INT 80h sys_read 将一个缓冲区从标准输入装满数据。
; 更新日期: 7/9/2016
; 输入参数: 没有。
; 返回值: 从EBP中读入的字节数
; 修改: ECX, EBP, Buff
; 调用: 内核 sys_write
; 描述: 使用 INT 80h sys_read 从标准输入中加载慢慢一缓冲区数据
; 并将其放入Buff。
; 因为我们开始了一个新的装满数据的缓冲区,所以缓冲区偏移量计数器ECX被设置为零。
; 主调程序必须测试EBP中的值:
; 如果在返回时EBP中的值为零,表明在标准输入中遇到了EOF(文件结尾)
; 如果在返回时EBP中的值比零小,表明发生了某种类型的错误。

LoadBuff:
    push rax        ; 保存主调程序的EAX寄存器
    push rbx        ; 保存主调程序的EBX寄存器
    push rdx        ; 保存主调程序的EDX寄存器
    mov eax,3        ; 指定 sys_read call
    mov ebx,0        ; 指定文件描述符 0:标准输入。
    mov ecx,Buff        ; 指定要被读入数据的缓冲区的偏移地址
    mov edx,BUFFLEN        ; 传递一次要读入的字节数
    int 80h            ; 调用 sys_read 来填满缓冲区
    mov ebp,eax        ; 保存从文件中读入的字节数,以备后用。
    xor ecx,ecx        ; 清除缓冲区指针ECX,将其设为0
    pop rdx            ; 恢复主调用程序的EDX寄存器
    pop rbx            ; 恢复主调用程序的EBX寄存器
    pop rax            ; 恢复主调用程序的EAX寄存器
    ret            ; 返回主调用程序

GLOBAL _start

;----------------------------------------------------------------------------------------------------
; 主程序从这里开始
;----------------------------------------------------------------------------------------------------
_start:
    nop        ; 为GDB输入无操作指令
    nop

; 在循环开始前需要做的所有初始化工作都从这里开始:
    xor esi,esi    ; 将整个字节计数器清零
    call LoadBuff    ; 从缓冲区读入第一个缓冲区的数据
    cmp ebp,0     ; 如果ebp=0,sys_read到达了标准输入的EOF(文件结尾)
    jbe Exit

; 审查该缓冲区,并将这些二进制字节转换为十六进制数字值:
Scan:
    xor eax,eax        ; 将EAX寄存器清零
    mov al,byte [Buff+ecx]    ; 将一个字节从缓冲区读出到AL中
    mov edx,esi        ; 复制整个计数器的值到EDX寄存器中
    and edx,000000Fh    ; 屏蔽掉除字符计数器的最低四位以外的其他位
    call DumpChar        ; 调用字符插入过程

; 将缓冲区的指针指向下一个字符,并查看以下缓冲区的工作是否已经完成:
    inc esi            ; 递增整个已处理的字符的计数值
    inc ecx            ; 递增缓冲区的指针值
    cmp ecx,ebp        ; 与缓冲区中的字符数相比较
    jb .modTest        ; 如果我们已经处理完了缓冲区中的所有字符……
    call LoadBuff        ; ……再次充满缓冲区
    cmp ebp,0        ; 如果ebp=0,sys_read到达了标准输入的结尾
    jbe Done        ; 如果到达了EOF,我们的任务就完成了

; 判断一下我们是否到达了一个包含16个值的块的结尾,并且需要显示一行:
.modTest:
    test esi,0000000Fh    ; 测试计数器中最低的四位是否为零。
    jnz Scan        ; 如果计数器不是16的倍数,继续循环(计数器用来一次扫描16个字节的数,没到的话就继续循环)
    call PrintLine        ; ……否则打印那一行
    call ClearLine        ; 清除十六进制转储行,将其全部设置为0
    jmp Scan        ; 继续扫描缓冲区

; 全部完成,让我们结束这个“晚会”
Done:
    call PrintLine        ; 打印“剩余”行
Exit:                
    mov eax,1        ; 用于退出Syscall的代码
    mov ebx,0        ; 将零作为返回码
    int 80h            ; 进行内核调用

样例输入:

hello,world!
I am moonlightpoet.
How old are you?
Hoe are you?
I'm fine,thank you!And you?

样例输出:

 68 65 6C 6C 6F 2C 77 6F 72 6C 64 21 0A 49 20 61 |hello,world!.I a|
 6D 20 6D 6F 6F 6E 6C 69 67 68 74 70 6F 65 74 2E |m moonlightpoet.|
 0A 48 6F 77 20 6F 6C 64 20 61 72 65 20 79 6F 75 |.How old are you|
 3F 0A 48 6F 65 20 61 72 65 20 79 6F 75 3F 0A 49 |?.Hoe are you?.I|
 27 6D 20 66 69 6E 65 2C 74 68 61 6E 6B 20 79 6F |'m fine,thank yo|
 75 21 41 6E 64 20 79 6F 75 3F 0A 0A 00 00 00 00 |u!And you?......|

 

posted @ 2016-07-10 15:29  月光诗人  阅读(2035)  评论(0编辑  收藏  举报