Search (using Google):  Web Karig

 

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.