5

I have written the following program in debug.exe:

1165:0100  mov ah, 09
1165:0102  mov dx, 107
1165:0105  int 21
1165:0107  db 'Hello World', 0A, 0D, '$'
1165:0115  jmp 1165:0100

However, when I execute it, it stops working after printing "Hello World" once. What I want it to do is to keep printing "Hello World" repeatedly, in a loop.

I also tried jmp 0100 but it also didn't work.

9
  • 1
    If you want to explore debug, I strongly recommend getting a hold of Rex Last's MS-DOS Revealed — it’s a pretty good introduction to assembly-language programming using debug. Alternatively, check out any of the free assemblers; NASM is pretty good and has good documentation. Jun 7, 2017 at 11:30
  • @stephen I've actually been looking for a good reference for debug.com, and I hadn't heard of the book you suggested, so I was going to purchase a copy. Unfortunately, used copies on Amazon.com seem to go for $268. Do you know of another source? Or is the text online somewhere, by chance? Jun 7, 2017 at 12:47
  • @Cody BookFinder shows a somewhat cheaper copy but it’s still rather expensive for what it is ($46 to the US)... I don’t think the text is online anywhere. Jun 7, 2017 at 15:01
  • As an aside, I don't recall if debug's r command expects decimal or hexadecimal input, but I somewhat suppose the latter. Is your file 23 or 35 bytes long? In either case, the tail would just be garbage, and not relevant to your problem.
    – user
    Jun 9, 2017 at 11:54
  • 2
    Also pay heed to Michael's comments on short jumps. You cannot presume which segment DOS loads your little program into. Next run it could be somewhere completely different. Far jumps (jumps to other segments) requires more forethought. As Michael says, it looks like debug turned your jump into a short jump, and I too am a bit surprised it did so using only two bytes (my guess is that the x86 instruction set has a convenient jmp instructions for jumps that are less than 128 bytes away, a little disassembly and google will reveal the answer)
    – 9Rune5
    Jun 18, 2017 at 10:38

2 Answers 2

30

Your code runs through the data ("Hello World" string) interpreting it as (nonsense) machine code - and very probably crashes before it even reaches the jmp instruction. You need to move the jmp instruction to before the string.

3
  • 6
    What actually happens is that the processor tries to execute the data, as if it were code. You surely already know this, but your answer could be improved by making that more explicit. Note that @user5161 can see what his code disassembles to in debug.com by typing -u 100. You'll see that the "string" data is turned into a bunch of nonsense instructions. Jun 7, 2017 at 12:57
  • 2
    More specifically, DOS programs don't exactly 'crash' in the sense that the OS has no provisions for terminating ill-behaving programs. What actually happens is that there is JMP 0119 instruction in the resulting code.
    – void_ptr
    Jun 7, 2017 at 18:31
  • 6
    @void_ptr Well, they typically crash in a much more spectacular way than on modern OSs. "Segmentation Fault. Core dumped" is really a boring show compared to a slight fizzling from the monitor that tells you a program gone astray has just readjusted your video frequencies ;)
    – tofro
    Jun 7, 2017 at 21:35
15

All right, I was curious what actually happened when you tried to execute that code. Because it crashes, obviously something is wrong, but what exactly is wrong.

After some digging, I finally found a machine on PCjs.org which both boots to a usable command line, and has sufficient hardware to be able to run DEBUG with good results. Start at the Windows 95 build 499 machine, and let it boot to a command prompt. By default, it will boot the disk image "PC-DOS 3.30 (Disk 1)". It will ask you for the current date and time; just press Enter at both prompts. You are now in IBM PC-DOS 3.30.

Then, load the "PC-DOS 3.30 (Disk 2)" disk image into drive A:. That one contains, among many other things, DEBUG.COM. You can now start DEBUG and provide your source code (in my case, DEBUG is using segment 114C; yours may be different). I'm also doing a CPU register dump at the beginning, so that we know what we have to work with:

A>DEBUG.COM
-r
AX=0000  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000
DS=114C  ES=114C  SS=114C  CS=114C  IP=0100   NV UP EI PL NZ NA PO NC
114C:0100 255300        AND     AX,0053
-a 100
114C:0100 mov ah,09
114C:0102 mov dx,107
114C:0105 int 21
114C:0107 db 'Hello World', 0a, 0d, '$'
114C:0115 jmp 114c:0100
114c:0117
-

OK, so let's see what that turns into:

-u 100 117
114C:0100 B409          MOV     AH,09
114C:0102 BA0701        MOV     DX,0107
114C:0105 CD21          INT     21
114C:0107 48            DEC     AX
114C:0108 65            DB      65
114C:0109 6C            DB      6C
114C:010A 6C            DB      6C
114C:010B 6F            DB      6F
114C:010C 20576F        AND     [BX+6F],DL
114C:010F 726C          JB      017D
114C:0111 64            DB      64
114C:0112 0A0D          OR      CL,[DI]
114C:0114 24EB          AND     AL,EB
114C:0116 E9376A        JMP     6B50
-

Not quite what we intended, is it? The first few instructions are fine, but by CS:0107H DEBUG goes off on a tangent, and never recovers. Notice that it doesn't think there is any instruction beginning on offset 0115, which is where our jump to the beginning of the program was supposed to be. But sure enough, if we look specifically for it...

-u 115 117
114C:0115 EBE9          JMP     0100
114C:0117 37            AAA
-

...there's our jump, in plain sight.

I'm assuming here that DEBUG's understanding of 8086 machine code is a decent approximation of the 8086/8088 CPU's Execution Unit, which is likely a reasonable assumption to make. (Its purpose, after all, was as a simple debugger and machine code monitor.)

This code will execute just fine up through offset 0107, moving past the supposed DEC AX. Says Ralph Brown's Interrupt List, upon return from INT 21H AH=09H, AL=24H (probably mostly an accident of the implementation, and not a deliberate choice), so at that point, decrementing AX is a perfectly valid operation even not accounting for wraparound. However...

There is no x86 instruction that starts out with a 65H byte. (See for yourself.) That's also why DEBUG would display it as a plain data byte (DB) until it again finds something that it recognizes as a valid instruction. In this case those valid instructions are nonsensical, but that's just because DEBUG is trying to interpret as code what in reality is data.

When the CPU encounters this illegal code byte (remember, the CPU is still in the middle of executing code; it has no knowledge of the fact that what's coming up was data intended for human consumption, and happily does the best it can), it triggers interrupt vector 06H, #UD (undefined opcode or sometimes invalid opcode -- this one seems appropriately schizofrenic). Exactly what happens at that point depends on your environment, but you can have a look at its address to see where the CPU will jump. Interrupt vectors are stored as 32-bit segment:offset pairs starting at address 0000H:0000H for vector 00H. Vector 06H is thus at bytes 0000H:0018H through 0000H:001BH. Let's look at what's there:

-u 0000:0018 001B
0000:0018 A09500        MOV     AL,[0095]
0000:001B F0            LOCK
0000:001C D01B          RCR     BYTE PTR [BP+DI],1
-

Ignore the disassembly; that's meaningless here. The important part is the data, which in this case turns out to be A09500F0. Let's look at memory address F000H:95A0H (remember that x86 CPUs store integers in reverse-byte order, and that the segment offset is stored at a lower offset than the segment, so we need to effectively read the address bytewise backwards):

-u F000:A095
F000:A095 2E            CS:
F000:A096 8B5404        MOV     DX,[SI+04]
F000:A099 89560A        MOV     [BP+0A],DX
F000:A09C 8166160100    AND     WORD PTR [BP+16],0001
F000:A0A1 33C0          XOR     AX,AX
F000:A0A3 EB14          JMP     A0B9
[remainder snipped for brevity]
-

At F000H:A0B9H, there is more meaningful-looking code. You can inspect it yourself if you want to (it's just a -u f000:a0b9 away) but that doesn't really matter, because what PCjs does with an invalid opcode may or may not be representative of what actual hardware did with it. I'm guessing that, on real hardware, if you were to perform the same investigation, you'd find that the interrupt vector points toward a HLT instruction, or possibly a jump-to-self, with the intent of freezing the computer from the user's point of view. Simply, by the time you get that far, something has gone horribly wrong, and in the simple days of DOS systems with no memory protection or anything similar, there wasn't a lot that could be done at that point to meaningfully recover. Stopping the system to make sure that no further harm is done, then, is a reasonable course of action.

And that is why your program stops working.

Easy as pie, no?

4
  • 6
    "There is no x86 instruction that starts out with a 65H byte". 65h is the prefix to indicate that any address operand in the instruction is relative to the GS segment register (386 and above only). DEBUG doesn't decode it because it doesn't understand any extensions to the instruction set beyond the original 8087 additions (there are extended versions of DEBUG out there that do understand this, including the version that comes with FreeDOS, however). 65 6C decodes to GS INSB which is a rather meaningless instruction, but would be understood by the processor to be just the same as INSB.
    – Jules
    Sep 6, 2017 at 1:16
  • 1
    Disassembling with ndisasm rather than debug gives the following: mov ah,0x9 / mov dx,0x107 / int 0x21 / dec ax / gs insb / insb / outsw / and [bx+0x6f],dl / jc 0x7d / or cl,[fs:di] / and al,0xeb / jmp word 0x6a50
    – Jules
    Sep 6, 2017 at 1:24
  • 1
    @Jules Interesting, thank you. Without analyzing the code in detail, it still does look rather nonsensical (as would be expected for data being treated as code). I have plans to update this answer later, and will see what I can do about incorporating your information then.
    – user
    Sep 6, 2017 at 5:39
  • Since this is an 8088-tagged question, all the talk about gs registers and illegal opcode interrupts would seem to be moot. The 8088 had no such beasts and would have either treated them as nop or done something weird. Even treating them as nop wouldn't help since there's a perfectly legal jb 017d and jmp 6b50 in the "pipeline". So it almost certainly jumped to a code location that sent it "off with the fairies", as we used to say.
    – paxdiablo
    Feb 26, 2022 at 9:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .