11 December 2003 Hex dump, part 2 In Part 1, I introduced some routines for printing characters to the screen, moving the cursor, and scrolling the screen when the screen overflows. Here I introduce the rest of the code I need to implement a real-mode hex-dump routine. In entries to come, I'll introduce new code, and I'll use this hex-dump routine to verify that the new code works. Code The code here is part of a new boot sector. byte_to_hex My hex-dump routine grabs a byte from memory, passes it to byte_to_hex, gets back the two hexadecimal digits needed to display the byte's value, and prints those digits to the screen. To use byte_to_hex, pass the byte in AL. The function returns with the first (left) hexadecimal digit in AL and the second (right) one in AH. This function works by converting four bytes of AL — first the low four bytes, then the high four bytes — into an offset into the .digits table and loading a hexadecimal digit into AL or AH. byte_to_hex: xor bh, bh mov bl, al and bl, 0x0F mov ah, [.digits + bx] mov bl, al shr bl, 4 mov al, [.digits + bx] ret .digits: db "0123456789ABCDEF" New print_char I decided to split up print_char (from my previous entry) into smaller routines, which my new routines can use. Note that I have to call get_pos (to get the cursor position in DL and DH) before I can call next_column, next_row, or set_pos. print_char: xor bh, bh xor cx, cx inc cx mov ah, 0x0A int 0x10 get_pos: xor bh, bh mov ah, 3 int 0x10 next_column: inc dl cmp dl, 80 jb set_pos next_row: xor dl, dl inc dh cmp dh, 25 jb set_pos dec dh push dx call scroll_up pop dx set_pos: xor bh, bh mov ah, 2 int 0x10 ret Other print routines My hex-dump routine relies on these. print_colon: mov al, ':' jmp print_char print_space: mov al, ' ' jmp print_char print_vbar: mov al, '|' jmp print_char print_word: ; pass word in AX push ax mov al, ah call print_byte pop ax print_byte: ; pass byte in AL call byte_to_hex jmp print_char dump_16 This is the hex-dump routine itself. To use this, point FS at the segment that contains the sixteen bytes you want to dump, and pass the offset to the bytes in BX. You'll get back FS unchanged and the original offset plus sixteen in BX, so you can just call the routine repeatedly to dump more bytes. The routine will print a line in this format: 1234:5678: 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 | ABCDEFGHIJKLMNOP The segment number comes first, followed by the offset, followed by the hexadecimal byte value of each of sixteen bytes (starting at that segment and offset), followed by the ASCII characters stored in those same sixteen bytes. So the routine works as follows: It preserves the offset in BX, because it calls routines that overwrite BX. It then prints the segment number and a colon. dump_16: push bx mov ax, fs call print_word call print_colon It then retrieves a copy of the offset and prints that, followed by a colon and a space. pop ax push ax call print_word call print_colon call print_space This is the loop to dump, sixteen times, the hexadecimal byte value of a byte in memory, followed by a space. pop bx xor si, si .1: mov al, [fs:bx+si] push si push bx call print_byte call print_space pop bx pop si inc si cmp si, 16 jb .1 Once the byte values are printed, I print a vertical bar ('|') and a space. push bx call print_vbar call print_space pop bx Then I print the contents of those same sixteen bytes as ASCII. xor si, si .2: mov al, [fs:bx+si] push si push bx call print_char pop bx pop si inc si cmp si, 16 jb .2 Finally, I move the cursor to the start of a new row... push bx call get_pos call next_row ...and return in BX the original offset plus sixteen. pop bx add bx, 16 ret Test code The simplest test is to test this code by dumping the boot sector itself to the screen, because I can verify the output of dump_16 against the output of my disassembler (ndisasmw.exe). The code is simple. It sets up the segment registers and stack as usual. Then it points FS:BX at the beginning of the boot sector and dumps the first sixty-four bytes found there. main: ; set up segment registers and stack xor ax, ax mov fs, ax mov bx, 0x7C00 call dump_16 call dump_16 call dump_16 call dump_16 jmp $ Test results My disassembler reports that the first 64 bytes of the boot sector look like this: 00000000 EA057C0000 jmp 0x0:0x7c05 00000005 8CC8 mov ax,cs 00000007 8ED8 mov ds,ax 00000009 8EC0 mov es,ax 0000000B 8EE0 mov fs,ax 0000000D 8EE8 mov gs,ax 0000000F FA cli 00000010 B80010 mov ax,0x1000 00000013 8ED0 mov ss,ax 00000015 BCFEFF mov sp,0xfffe 00000018 FB sti 00000019 31C0 xor ax,ax 0000001B 8EE0 mov fs,ax 0000001D BB007C mov bx,0x7c00 00000020 E89800 call 0xbb 00000023 E89500 call 0xbb 00000026 E89200 call 0xbb 00000029 E88F00 call 0xbb 0000002C EBFE jmp short 0x2c 0000002E B80300 mov ax,0x3 00000031 CD10 int 0x10 00000033 30FF xor bh,bh 00000035 31D2 xor dx,dx 00000037 B402 mov ah,0x2 00000039 CD10 int 0x10 0000003B C3 ret 0000003C B707 mov bh,0x7 0000003E 31C9 xor cx,cx My dump_16 routine reports that the first 64 bytes of the boot sector look like this: 0000:7C00: EA 05 7C 00 00 8C C8 8E D8 8E C0 8E E0 8E E8 FA 0000:7C10: B8 00 10 8E D0 BC FE FF FB 31 C0 8E E0 BB 00 7C 0000:7C20: E8 98 00 E8 95 00 E8 92 00 E8 8F 00 EB FE B8 03 0000:7C30: 00 CD 10 30 FF 31 D2 B4 02 CD 10 C3 B7 07 31 C9 Eureka! They match — dump_16 works. Check the index for other entries. |