Chapter 10: chastehex: Not just a program, but a philosophy
For the final program in this book, I have prepared something special. It is a command-line hex editor written entirely in assembly. It does not have a graphical user interface, but instead can be used to read or write bytes of a file at any location!
I will be showing you the full source code as one big file that you can copy or download directly from my GitHub repository. But first, I need to show you an example of how it works when it is assembled.
Starting from a DOSBox prompt, I create a small text file with this command:
echo brillient > company.txt
That is literally the name of a company I used to work for. It is a good example to use here because the official spelling is wrong. Brillient is a misspelling of brilliant, which means smart (unlike whoever chose the company name).
To use the chastehex (named chex.com in this example) program to view what is in the file, enter this command:
chex company.txt
You will see the following.
1 company.txt
2 00000000 62 72 69 6C 6C 69 65 6E 74 0D 0A brillient..
3 EOF
It is possible to change the ‘e’ into an ‘a’ and correct the spelling. We only need to change the hexcode for that byte.
The ‘e’ is the seventh letter in the word, which means it is address 6 because the first address of any file starts at 0. Knowing this, the following command does the trick:
chex company.txt 6 61
If you view the file again after this as explained earlier, it will show that it has been corrected.
1 company.txt
2 00000000 62 72 69 6C 6C 69 61 6E 74 0D 0A brilliant..
3 EOF
Obviously this is a silly example because there are plenty of other ways to correct a typo in a word. However, the power of this program comes from the fact that it is dynamic enough to handle not just text files but binary files of any size less than 2 gigabytes. You can, in fact, edit executable files, game save files, image files, or anything where you know the precise location of bytes you need to change for some reason.
If you enter chex without any arguments, a short help message will display:
1 chastehex:
2 hexdump a file:
3 chex file
4 read a byte:
5 chex file address
6 write a byte:
7 chex file address value
8 The file must exist
The flexibility of this program comes from the fact that it changes behavior based on how many arguments you give it. It can be used to dump any entire file or to read and write individual bytes. If you add more than 3 arguments it will accept the numbers as values of more bytes to write at the location you selected.
I frequently use this program for messing around with files just for the fun of it. But besides being a fun toy for messing with binary files, it also serves as an example of how much can be accomplished with assembly language. All it does is process command line arguments, open, read/write, and close files while also displaying basic information to the screen on what it is doing.
The idea of how it behaves is easy to understand, but it also can be a bit complex to write such a program. The good news is you don’t have to, because I wrote the entire program myself as an example of how much skill I have with Assembly language for DOS.
Below is the full source of chastehex for DOS. It will assemble with either the fasm or nasm assemblers.
chex.asm
1 org 100h ;DOS programs start at this address
2
3 mov word [radix],16 ; can choose radix for integer output!
4
5 mov ch,0 ;zero ch (upper half of cx)
6 mov cl,[80h] ;load length in bytes of the command string
7 cmp cx,0
8 jnz args_exist
9
10 mov ax,help ;if not arguments were given, show a help message
11 call putstring
12 jmp ending ;and end the program because there is nothing to do
13
14 args_exist:
15
16 mov dx,81h ;Point dx to the beginning of string
17 inc dx ;go to next char
18 dec cx ;but subtract 1 from count
19 mov [arg_index],dx ;save index to variable so dx is free to change as needed
20
21 ;find the end of the string based on length
22 mov ax,dx
23 add ax,cx
24 ;now we know where the string ends.
25 mov [arg_string_end],ax ;this is the end of the arg string. important for later
26 ;call putint ; print address where entire arg string ends
27
28 ;this routine replaces all non printable characters with zero in the arg string
29 mov bx,dx
30 filter:
31 cmp byte [bx],' '
32 ja notspace ; if char is above space, leave it alone
33 mov byte [bx],0 ;otherwise it counts as a space, change it to a zero
34 notspace:
35 inc bx
36 cmp bx,[arg_string_end] ;are we at the end of the arg string?
37 jnz filter ;if not at end, continue the filter
38
39 filter_end:
40 mov byte [bx],0 ;terminate the ending with a zero for safety
41
42 ;now that the argument string is prepared, we will try to use the first argument as a filename to open
43
44 mov ah,3Dh ;call number for DOS open existing file
45 mov al,2 ;file access: 0=read,1=write,2=read+write
46 mov dx,[arg_index] ;string address to interpret as filename
47 int 21h ;DOS call to finalize open function
48
49 mov [file_handle],ax ;save the file handle
50
51 jc file_error ;if carry flag is set, we have an error, otherwise, file is open
52
53 file_opened:
54
55 mov ax,dx
56 call putstring
57 call putline
58 jmp use_file ;skip past error message and start using the file
59
60 ;this section prints error message and then ends the program if file error found
61
62 file_error: ;prints error code2=file not found
63 mov ax,dx
64 call putstring
65 call putline
66 mov ax,file_error_message
67 call putstring
68 mov ax,[file_handle]
69 call putint
70 jmp arg_loop_end
71
72 ;how we use the file depends on the number of arguments given
73 ;if no arguments other than the filename exist, we do a regular hex dump
74
75 use_file:
76
77 mov bp,0 ;set bp to zero because it represents upper 16 bits of file address
78 call get_next_arg ;get address of next arg and return into ax register
79 cmp ax,[arg_string_end] ;this time, if ax equals end of string, we hex dump and then end the program later
80 jz hexdump ;jump to hexdump section
81
82 ;otherwise, if there are more args, ax contains next argument
83 ;we will next extra the address from this argument for future operations
84
85 ;first call the strint_32 function to get 32 bit integer from a hex string
86 ;the lower 16 bits are stored in ax just like regular strint
87 ;upper 16 bits are stored in the "extra_word" memory location
88 ;but then I copy them to the bp register to use for the rest of the program
89 call strint_32
90 mov bp,[extra_word] ;store the upper 16 bits in the bp register
91
92 ;this number will be out new offset to seek to
93 mov [file_offset],ax
94
95 mov ah,42h ;lseek call number
96 mov al,0 ;seek origin 00h start of file,01h current file position,02h end of file
97 mov bx,[file_handle]
98 mov cx,bp ;upper word of offset
99 mov dx,[file_offset] ;lower word of offset
100 int 21h
101
102 jc arg_loop_end ;end program if seek error (though I can't imagine how it would fail)
103
104 ;check if there are any more args
105 call get_next_arg
106 cmp ax,[arg_string_end]
107 jz dump_byte ;jump to dump_byte section and continue with read mode
108
109 jmp arg_loop ;otherwise we jump to the arg loop and write the values as bytes starting at offset
110
111 ;this next section is the reading mode that reads one byte. It only executes if we have not provided bytes to write to the new address
112 ;because we have an argument for an address we will read only this byte and display it
113 dump_byte:
114
115 mov ah,3Fh ;call number for read function
116 mov bx,[file_handle] ;store file handle to read from in bx
117 mov cx,1 ;we are reading only 1 byte
118 mov dx,byte_array ;store the bytes here
119 int 21h
120
121 mov cx,ax ;number of bytes read
122
123 ;set width to 4 and display extra:offset
124 mov word[int_width],4
125 mov ax,bp
126 call putint
127 mov ax,[file_offset]
128 call putint_and_space
129
130 cmp cx,1
131 jz not_eof ;skip past here as long as one byte was read otherwise show EOF
132 mov ax,end_of_file
133 call putstring
134 jmp arg_loop_end
135 not_eof:
136
137 mov ah,0 ;zero upper half of ax
138 mov al,[byte_array]
139
140 mov word[int_width],2
141 call putint
142 call putline
143
144 jmp arg_loop_end ;we are done so we end the program
145
146 hexdump:
147
148 ;we start the loop with a call to read exactly 16 bytes
149
150 mov ah,3Fh ;call number for read function
151 mov bx,[file_handle] ;store file handle to read from in bx
152 mov cx,16 ;we are reading sixteen bytes
153 mov dx,byte_array ;store the bytes here
154 int 21h
155
156 ;call putint ;check the number of bytes read
157
158 ;important note: the number of bytes read should be 16 or less and this is not an error
159 ;zero is expected if we are at the end of the file.
160 ;however, if it is zero, we print an EOF message and exit
161
162 cmp ax,0
163 jnz hexdump_print_row
164 mov ax,end_of_file
165 call putstring
166 jmp arg_loop_end
167
168 hexdump_print_row:
169
170 mov [bytes_read],ax
171
172 call print_bytes_row
173
174 jmp hexdump ;jump back to hexdump and attempt another read of a row
175
176 ;this loop processes the rest of the arguments
177 ;it interprets each one as a byte to write to the current offset
178 ;this loop should only execute if a file name and address have already been given
179 arg_loop:
180 mov ax,[arg_index] ;get address of current arg
181 ;call putstring
182
183 call strint_32 ;turn string at address ax into a number returned in ax
184
185 mov [byte_array],al
186
187 mov ah,40h ; select DOS function 40h write
188 mov bx,[file_handle] ;store file handle to write to in bx
189 mov cx,1 ;write 1 byte to this file
190 mov dx,byte_array ;write from this address
191 int 21h
192
193 ;set width to 4 and display extra:offset
194 mov word[int_width],4
195 mov ax,bp
196 call putint
197 mov ax,[file_offset]
198 call putint_and_space
199
200 add word[file_offset],1
201 adc bp,0
202
203
204 mov word[int_width],2
205 mov ah,0
206 mov al,[byte_array]
207 call putint
208 call putline
209
210 call get_next_arg ;get address of next arg and return into ax register
211
212 cmp ax,[arg_string_end] ;if the ax register contains address of the end of args string, end program to avoid failure
213 jz arg_loop_end
214 jmp arg_loop
215
216 arg_loop_end: ;this is the correct end of the program
217
218 ;close the file if it is open
219 mov ah,3Eh
220 mov bx,[file_handle]
221 int 21h
222
223 ending:
224 mov ax,4C00h ; Exit program
225 int 21h
226
227 arg_string_end dw 0
228 arg_index dw 0
229 file_error_message db 'Could not open the file! Error number: ',0
230 file_handle dw 0
231 read_error_message db 'Failure during reading of file. Error number: ',0
232 end_of_file db 'EOF',0
233
234 ;where we will store data from the file
235 byte_array db 16 dup '?',0
236 file_offset dw 0,0
237 bytes_read dw 0
238
239 ;function to move ahead to the next arg
240 ;only works after the filter has been applied to turn all spaces into zeroes
241
242 get_next_arg:
243 mov bx,[arg_index] ;dx has address of current arg
244 find_zero:
245 cmp byte [bx],0
246 jz found_zero
247 inc bx
248 jmp find_zero ; this char is not zero, go to the next char
249 found_zero:
250
251 find_non_zero:
252 cmp bx,[arg_string_end]
253 jz arg_finish ;if bx is already at end, nothing left to find
254 cmp byte [bx],0
255 jnz arg_finish ;if this char is not zero we have found the next string!
256 inc bx
257 jmp find_non_zero ;otherwise, keep looking
258
259 arg_finish:
260 mov [arg_index],bx ; save this index to variable
261 mov ax,bx ;but also save it to ax register for use
262 ret
263
264 ;this function prints a row of hex bytes
265 ;each row is 16 bytes
266 print_bytes_row:
267 mov cx,[bytes_read] ;number of bytes read
268
269 ;set width to 4 and display extra:offset
270 mov word[int_width],4
271 mov ax,bp
272 call putint
273 mov ax,[file_offset]
274 call putint_and_space
275
276 add [file_offset],cx
277 adc bp,0
278
279 mov ah,0 ;zero upper half of ax
280 mov bx,byte_array
281
282 mov word[int_width],2
283
284 print_byte:
285 mov al,[bx]
286 call putint_and_space
287 inc bx
288 dec cx
289 cmp cx,0
290 jnz print_byte
291
292 ;optionally, print chars after hex bytes
293 call print_bytes_row_text
294 call putline
295
296 ret
297
298 ;I define a string of 3 spaces as filler when less than 16 bytes are read
299 ;This makes the text section on the right properly lined up.
300 space_three db ' ',0
301
302 ;This function prints the text equivalent of the bytes on the last row printed.
303 ;It reads how many bytes were read in the last read operation.
304 ;If less than 16 bytes were read, it prints spaces as filler so that
305 ;text can still be printed lined up with all the other rows
306 ;even if less than 16 bytes exist in the current row.
307 ;This situation sometimes happens when we get near the end of the file.
308 ;It also replaces characters that can't be printed with periods -> .
309
310 print_bytes_row_text:
311
312 mov cx,[bytes_read]
313 pad_spaces:
314 cmp cx,0x10
315 jz pad_spaces_end
316 mov ax,space_three
317 call putstring
318 inc cx
319 jmp pad_spaces
320 pad_spaces_end:
321
322 mov bx,byte_array
323 mov cx,[bytes_read]
324 next_char:
325 mov ax,0
326 mov al,[bx]
327
328 ;if char is below '0' or above '9', it is outside the range of these and is not a digit
329 cmp al,0x20
330 jb not_printable
331 cmp al,0x7E
332 ja not_printable
333
334 printable:
335 ;if char is in printable range,copy as is and proceed to next index
336 jmp next_index
337
338 not_printable:
339 mov al,'.' ;otherwise replace with placeholder value
340
341 next_index:
342 mov [bx],al
343 inc bx
344 dec cx
345 cmp cx,0
346 jnz next_char
347 mov [bx],byte 0 ;make sure string is zero terminated
348
349 mov ax,byte_array
350 call putstring
351
352 ret
353
354 help db 'chastehex:',0Dh,0Ah
355 db 'hexdump a file:',0Dh,0Ah,9,'chex file',0Dh,0Ah
356 db 'read a byte:',0Dh,0Ah,9,'chex file address',0Dh,0Ah
357 db 'write bytes:',0Dh,0Ah,9,'chex file address byte1 byte2 etc.',0Dh,0Ah
358 db 4 dup 0
359
360 ; About the chastelib variant
361
362 ;instead of including chastelib16.asm as a header file
363 ;I copy pasted it except that I excluded functions that were not used.
364 ;Notably, the strint function is excluded because strint_32 is used instead
365 ;This was to squeeze chastehex for DOS down to 1024 bytes.
366
367 ;start of chastelib
368
369 ; This file is where I keep my function definitions.
370 ; These are usually my string and integer output routines.
371
372 ;this is my best putstring function for DOS because it uses call 40h of interrupt 21h
373 ;this means that it works in a similar way to my Linux Assembly code
374 ;the plan is to make both my DOS and Linux functions identical except for the size of registers involved
375
376 putstring:
377
378 push ax
379 push bx
380 push cx
381 push dx
382
383 mov bx,ax ;copy ax to bx for use as index register
384
385 putstring_strlen_start: ;this loop finds the length of the string as part of the putstring function
386
387 cmp [bx], byte 0 ;compare this byte with 0
388 jz putstring_strlen_end ;if comparison was zero, jump to loop end because we have found the length
389 inc bx ;increment bx (add 1)
390 jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
391
392 putstring_strlen_end:
393
394 sub bx,ax ; sub ax from bx to get the difference for number of bytes
395 mov cx,bx ; mov bx to cx
396 mov dx,ax ; dx will have address of string to write
397
398 mov ah,40h ; select DOS function 40h write
399 mov bx,1 ; file handle 1=stdout
400 int 21h ; call the DOS kernel
401
402 pop dx
403 pop cx
404 pop bx
405 pop ax
406
407 ret
408
409 ;this is the location in memory where digits are written to by the intstr function
410 int_string db 16 dup '?' ;enough bytes to hold maximum size 16-bit binary integer
411 int_string_end db 0 ;zero byte terminator for the integer string
412
413 radix dw 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
414 int_width dw 8
415
416 intstr:
417
418 mov bx,int_string_end-1 ;find address of lowest digit(just before the newline 0Ah)
419 mov cx,1
420
421 digits_start:
422
423 mov dx,0;
424 div word [radix]
425 cmp dx,10
426 jb decimal_digit
427 jnb hexadecimal_digit
428
429 decimal_digit: ;we go here if it is only a digit 0 to 9
430 add dx,'0'
431 jmp save_digit
432
433 hexadecimal_digit:
434 sub dx,10
435 add dx,'A'
436
437 save_digit:
438
439 mov [bx],dl
440 cmp ax,0
441 jz intstr_end
442 dec bx
443 inc cx
444 jmp digits_start
445
446 intstr_end:
447
448 prefix_zeros:
449 cmp cx,[int_width]
450 jnb end_zeros
451 dec bx
452 mov [bx],byte '0'
453 inc cx
454 jmp prefix_zeros
455 end_zeros:
456
457 mov ax,bx ; store string in ax for display later
458
459 ret
460
461 ;function to print string form of whatever integer is in ax
462 ;The radix determines which number base the string form takes.
463 ;Anything from 2 to 36 is a valid radix
464 ;in practice though, only bases 2,8,10,and 16 will make sense to other programmers
465 ;this function does not process anything by itself but calls the combination of my other
466 ;functions in the order I intended them to be used.
467
468 putint:
469
470 push ax
471 push bx
472 push cx
473 push dx
474
475 call intstr
476 call putstring
477
478 pop dx
479 pop cx
480 pop bx
481 pop ax
482
483 ret
484
485 ;the next utility functions simply print a space or a newline
486 ;these help me save code when printing lots of things for debugging
487
488 space db ' ',0
489 line db 0Dh,0Ah,0
490
491 putspace:
492 push ax
493 mov ax,space
494 call putstring
495 pop ax
496 ret
497
498 putline:
499 push ax
500 mov ax,line
501 call putstring
502 pop ax
503 ret
504
505 ;a small function just for the common operation
506 ;printing an integer followed by a space
507 ;this saves a few bytes in the assembled code
508
509 putint_and_space:
510 call putint
511 call putspace
512 ret
513
514 ;end of chastelib
515
516 ; About the strint_32 function
517
518 ;this function converts a string pointed to by ax into an integer returned in ax instead
519 ;it is a little complicated because it has to account for whether the character in
520 ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
521 ;it also checks for both uppercase and lowercase letters for bases 11 to 36
522 ;finally, it checks if that letter makes sense for the base.
523 ;For example, G to Z cannot be used in hexadecimal, only A to F can
524 ;The purpose of writing this function was to be able to accept user input as integers
525
526 ;this version of the strint function has been modified from its original version.
527 ;it has been formatted to extract up to 32 bits of data using memory despite using
528 ;only 16 bit registers.
529 ;It uses the same [radix] and [int_width] variables as the regular 16 bit strint
530
531 ;However, it uses an extra word variable in memory which is designed to store the upper 16 bits of
532 ;a 32 bit offset for file seeking. Apparently the DOS system calls support this based on Ralf Browns interrupt list.
533 ;I confirmed that it works by writing to higher addresses and reading them
534
535 ;You might wonder why I made a 32 bit variant of this function rather than replacing the original. These are my reasons
536
537 ;1. It only works with hexadecimal in the context of the chastehex program.
538 ;2. This function stores extra data every loop and is therefore slower.
539 ;3. Most of the time 32 bit data isn't needed as 16 bit DOS can't use 32 bit memory.
540
541 ;This function was a specific case only meant for adding 32 bit support for the DOS version of chastehex.
542 ;I also kept the original which only contains support for files less than 64 kilobytes.
543
544 extra_word dw 0 ;define an extra word(16 bits). The initial value doesn't matter.
545
546 strint_32:
547
548 ;initialize new variables added to this function
549 mov word[extra_word],0
550
551 mov bx,ax ;copy string address from ax to bx because ax will be replaced soon!
552 mov ax,0
553
554 read_strint_32:
555 mov cx,0 ; zero ecx so only lower 8 bits are used
556 mov cl,[bx]
557 inc bx
558 cmp cl,0 ; compare byte at address edx with 0
559 jz strint_end_32 ; if comparison was zero, this is the end of string
560
561 ;if char is below '0' or above '9', it is outside the range of these and is not a digit
562 cmp cl,'0'
563 jb not_digit_32
564 cmp cl,'9'
565 ja not_digit_32
566
567 ;but if it is a digit, then correct and process the character
568 is_digit_32:
569 sub cl,'0'
570 jmp process_char_32
571
572 not_digit_32:
573 ;it isn't a digit, but it could be perhaps and alphabet character
574 ;which is a digit in a higher base
575
576 ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
577 cmp cl,'A'
578 jb not_upper_32
579 cmp cl,'Z'
580 ja not_upper_32
581
582 is_upper_32:
583 sub cl,'A'
584 add cl,10
585 jmp process_char_32
586
587 not_upper_32:
588
589 ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
590 cmp cl,'a'
591 jb not_lower_32
592 cmp cl,'z'
593 ja not_lower_32
594
595 is_lower_32:
596 sub cl,'a'
597 add cl,10
598 jmp process_char_32
599
600 not_lower_32:
601
602 ;if we have reached this point, result invalid and end function
603 jmp strint_end_32
604
605 process_char_32:
606
607 cmp cx,[radix] ;compare char with radix
608 jnb strint_end_32 ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
609
610 ;before we process the character, to avoid data loss, we shift bits into the [extra_word]
611 push ax
612 shr ax,12 ;shift exactly 12 bits to keep the lowest hex digit of ax
613 shl word[extra_word],4 ;shift the [extra_word] 4 bits to make room for the hex digit
614 add [extra_word],ax
615 pop ax
616
617 mov dx,0 ;zero edx because it is used in mul sometimes
618 mul word [radix] ;mul ax with radix
619 add ax,cx
620
621 jmp read_strint_32 ;jump back and continue the loop if nothing has exited it
622
623 strint_end_32:
624
625 ret
The chastehex program is a massive beast of Assembly code. It is a 625 line masterpiece that has been tested under the traditional DOSBox emulator and the newer DOSBox-X.
I admit that it is a little hard to understand because Assembly source takes more space and it can be easier to get lost in it.
But the key reason I bothered is efficiency and frugality of the final machine code. Despite the large source code size of chastehex, the assembled binary is only 1024 bytes. Here it is:
chex.com
1 chex.com
2 00000000 C7 06 28 04 10 00 B5 00 8A 0E 80 00 83 F9 00 75 ..(............u
3 00000010 09 B8 75 03 E8 E0 02 E9 42 01 BA 81 00 42 49 89 ..u.....B....BI.
4 00000020 16 63 02 89 D0 01 C8 A3 61 02 89 D3 80 3F 20 77 .c......a....? w
5 00000030 03 C6 07 00 43 3B 1E 61 02 75 F1 C6 07 00 B4 3D ....C;.a.u.....=
6 00000040 B0 02 8B 16 63 02 CD 21 A3 8D 02 72 0A 89 D0 E8 ....c..!...r....
7 00000050 A5 02 E8 2E 03 EB 17 89 D0 E8 9B 02 E8 24 03 B8 .............$..
8 00000060 65 02 E8 92 02 A1 8D 02 E8 FB 02 E9 E6 00 BD 00 e...............
9 00000070 00 E8 65 01 3B 06 61 02 74 6B E8 18 03 8B 2E 93 ..e.;.a.tk......
10 00000080 04 A3 D3 02 B4 42 B0 00 8B 1E 8D 02 89 E9 8B 16 .....B..........
11 00000090 D3 02 CD 21 0F 82 BC 00 E8 3E 01 3B 06 61 02 74 ...!.....>.;.a.t
12 000000A0 02 EB 65 B4 3F 8B 1E 8D 02 B9 01 00 BA C2 02 CD ..e.?...........
13 000000B0 21 89 C1 C7 06 2A 04 04 00 89 E8 E8 A8 02 A1 D3 !....*..........
14 000000C0 02 E8 C8 02 83 F9 01 74 09 B8 BE 02 E8 28 02 E9 .......t.....(..
15 000000D0 82 00 B4 00 A0 C2 02 C7 06 2A 04 02 00 E8 86 02 .........*......
16 000000E0 E8 A0 02 EB 6F B4 3F 8B 1E 8D 02 B9 10 00 BA C2 ....o.?.........
17 000000F0 02 CD 21 83 F8 00 75 08 B8 BE 02 E8 F9 01 EB 54 ..!...u........T
18 00000100 A3 D7 02 E8 F4 00 EB DD A1 63 02 E8 87 02 A2 C2 .........c......
19 00000110 02 B4 40 8B 1E 8D 02 B9 01 00 BA C2 02 CD 21 C7 ..@...........!.
20 00000120 06 2A 04 04 00 89 E8 E8 3C 02 A1 D3 02 E8 5C 02 .*......<.....\.
21 00000130 83 06 D3 02 01 83 D5 00 C7 06 2A 04 02 00 B4 00 ..........*.....
22 00000140 A0 C2 02 E8 20 02 E8 3A 02 E8 8D 00 3B 06 61 02 .... ..:....;.a.
23 00000150 74 02 EB B4 B4 3E 8B 1E 8D 02 CD 21 B8 00 4C CD t....>.....!..L.
24 00000160 21 00 00 00 00 43 6F 75 6C 64 20 6E 6F 74 20 6F !....Could not o
25 00000170 70 65 6E 20 74 68 65 20 66 69 6C 65 21 20 45 72 pen the file! Er
26 00000180 72 6F 72 20 6E 75 6D 62 65 72 3A 20 00 00 00 46 ror number: ...F
27 00000190 61 69 6C 75 72 65 20 64 75 72 69 6E 67 20 72 65 ailure during re
28 000001A0 61 64 69 6E 67 20 6F 66 20 66 69 6C 65 2E 20 45 ading of file. E
29 000001B0 72 72 6F 72 20 6E 75 6D 62 65 72 3A 20 00 45 4F rror number: .EO
30 000001C0 46 00 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F 3F F.??????????????
31 000001D0 3F 3F 00 00 00 00 00 00 00 8B 1E 63 02 80 3F 00 ??.........c..?.
32 000001E0 74 03 43 EB F8 3B 1E 61 02 74 08 80 3F 00 75 03 t.C..;.a.t..?.u.
33 000001F0 43 EB F2 89 1E 63 02 89 D8 C3 8B 0E D7 02 C7 06 C....c..........
34 00000200 2A 04 04 00 89 E8 E8 5D 01 A1 D3 02 E8 7D 01 01 *......].....}..
35 00000210 0E D3 02 83 D5 00 B4 00 BB C2 02 C7 06 2A 04 02 .............*..
36 00000220 00 8A 07 E8 66 01 43 49 83 F9 00 75 F4 E8 08 00 ....f.CI...u....
37 00000230 E8 50 01 C3 20 20 20 00 8B 0E D7 02 83 F9 10 74 .P.. ........t
38 00000240 09 B8 34 03 E8 B0 00 41 EB F2 BB C2 02 8B 0E D7 ..4....A........
39 00000250 02 B8 00 00 8A 07 3C 20 72 06 3C 7E 77 02 EB 02 ......< r.<~w...
40 00000260 B0 2E 88 07 43 49 83 F9 00 75 E6 C6 07 00 B8 C2 ....CI...u......
41 00000270 02 E8 83 00 C3 63 68 61 73 74 65 68 65 78 3A 0D .....chastehex:.
42 00000280 0A 68 65 78 64 75 6D 70 20 61 20 66 69 6C 65 3A .hexdump a file:
43 00000290 0D 0A 09 63 68 65 78 20 66 69 6C 65 0D 0A 72 65 ...chex file..re
44 000002A0 61 64 20 61 20 62 79 74 65 3A 0D 0A 09 63 68 65 ad a byte:...che
45 000002B0 78 20 66 69 6C 65 20 61 64 64 72 65 73 73 0D 0A x file address..
46 000002C0 77 72 69 74 65 20 62 79 74 65 73 3A 0D 0A 09 63 write bytes:...c
47 000002D0 68 65 78 20 66 69 6C 65 20 61 64 64 72 65 73 73 hex file address
48 000002E0 20 62 79 74 65 31 20 62 79 74 65 32 20 65 74 63 byte1 byte2 etc
49 000002F0 2E 0D 0A 00 00 00 00 50 53 51 52 89 C3 80 3F 00 .......PSQR...?.
50 00000300 74 03 43 EB F8 29 C3 89 D9 89 C2 B4 40 BB 01 00 t.C..)......@...
51 00000310 CD 21 5A 59 5B 58 C3 3F 3F 3F 3F 3F 3F 3F 3F 3F .!ZY[X.?????????
52 00000320 3F 3F 3F 3F 3F 3F 3F 00 02 00 08 00 BB 26 04 B9 ???????......&..
53 00000330 01 00 BA 00 00 F7 36 28 04 83 FA 0A 72 02 73 05 ......6(....r.s.
54 00000340 83 C2 30 EB 06 83 EA 0A 83 C2 41 88 17 83 F8 00 ..0.......A.....
55 00000350 74 04 4B 41 EB DC 3B 0E 2A 04 73 07 4B C6 07 30 t.KA..;.*.s.K..0
56 00000360 41 EB F3 89 D8 C3 50 53 51 52 E8 BF FF E8 87 FF A.....PSQR......
57 00000370 5A 59 5B 58 C3 20 00 0D 0A 00 50 B8 75 04 E8 76 ZY[X. ....P.u..v
58 00000380 FF 58 C3 50 B8 77 04 E8 6D FF 58 C3 E8 D7 FF E8 .X.P.w..m.X.....
59 00000390 E8 FF C3 00 00 C7 06 93 04 00 00 89 C3 B8 00 00 ................
60 000003A0 B9 00 00 8A 0F 43 80 F9 00 74 54 80 F9 30 72 0A .....C...tT..0r.
61 000003B0 80 F9 39 77 05 80 E9 30 EB 26 80 F9 41 72 0D 80 ..9w...0.&..Ar..
62 000003C0 F9 5A 77 08 80 E9 41 80 C1 0A EB 14 80 F9 61 72 .Zw...A.......ar
63 000003D0 0D 80 F9 7A 77 08 80 E9 61 80 C1 0A EB 02 EB 1F ...zw...a.......
64 000003E0 3B 0E 28 04 73 19 50 C1 E8 0C C1 26 93 04 04 01 ;.(.s.P....&....
65 000003F0 06 93 04 58 BA 00 00 F7 26 28 04 01 C8 EB A1 C3 ...X....&(......
66 EOF
In case you were wondering, that hex dump was produced by chastehex itself! This program has become one of my primary debugging tools when I am writing assembly language because it helps me see the exact bytes in the executable files or in the text files my programs write.
As it turns out, the core chastelib series of functions ( putstring, intstr, putint, and strint ) were written so that I could port the original C version I wrote on Linux. The program is unique in that it uses the closest DOS equivalent of the 6 POSIX functions read, write, open, close, lseek, and exit.
This meant that I first had to write the Linux Assembly version and construct my 4 functions. When the 32-bit Linux version was complete, the next step was to gradually port all the functions to DOS. I had to learn the system call numbers and translate the Linux calls into DOS calls.
I ran into trouble because DOS handles command line arguments differently than Linux does. However, I finally got the same behavior from the DOS version as the Linux version had.
It is not an exaggeration to say that I spend hundreds of hours on this program. In fact it took longer to write the comments for explaining it than it did to write the program and test it.
The reason I say chastehex is more than a program is because it follows my philosophy of how code should be written. It is smaller and faster than any assembly code that a C compiler can produce. It is also original enough that it could not be written by AI and still be this dense and efficient. Although I have written this same program for Linux in both Assembly and C forms, the DOS version remains the one that I am most proud of because it is my highest achievement on the first operating system I ever used.
However, a program only good when people can understand what it does, and how to use it. A full understanding of a program comes from its source code. That’s why this entire book was written to help people learn Assembly language and appreciate programs like chastehex. With the skills you learned, you may even write more impressive tools for DOS and other operating systems. If you become better than me, I have succeeded as a teacher!
I do hope that you have enjoyed this book as I attempted to teach some of the secrets of how DOS programs work at the assembly language level. I truly love and understand math at a different level than most people but I do hope to receive feedback for future editions of this book, including the Linux edition that I want to write in the future.
If you understood this book, congratulations, you are brilliant! If not, perhaps a more general introduction to programming in C is more at your skill level. See my free website version of my other programming book: Chastity’s Code Cookbook.