[HN Gopher] Programming with DOS Debugger (2003)
       ___________________________________________________________________
        
       Programming with DOS Debugger (2003)
        
       Author : susam
       Score  : 101 points
       Date   : 2024-04-06 08:46 UTC (2 days ago)
        
 (HTM) web link (susam.net)
 (TXT) w3m dump (susam.net)
        
       | M95D wrote:
       | I liked the Hello World program = 17 bytes. 13B is just the text
       | to print.
        
       | userbinator wrote:
       | _The fact that this debugger program is always available with MS-
       | DOS or Windows 98 system means that these systems are ready for
       | some rudimentary assembly language programming without requiring
       | any additional tools._
       | 
       | PC magazines from the 80s through the 90s took advantage of this,
       | publishing listings of "source code" that could be typed into
       | DEBUG to create tiny but useful utilities, mostly under 1K in
       | size. It was an era when "power users" or even moderately
       | advanced users would often know some actual programming too and
       | many grew into full-time developers; a stark contrast from these
       | days when many "real" developers barely understand anything about
       | their environment (and are incentivised not to.)
        
         | frogulis wrote:
         | > and are incentivised not to
         | 
         | Could you elaborate on that?
        
           | bitwize wrote:
           | Not much time for in-depth learning when you have sprint
           | commitments to make. Just have Copilot generate the
           | boilerplate and tests and fix them as you go.
        
             | snvzz wrote:
             | Or copy-paste from stackunderflow.
        
             | exe34 wrote:
             | Gotta keep that velocity up!
        
           | 13of40 wrote:
           | Example: Everyone knows how to use a Dictionary. It's not
           | worth anyone's time to know how it works under the hood,
           | because only a fool would actually implement one from scratch
           | in production code. Niche embedded cases possibly excepted.
        
             | kqr wrote:
             | I would argue this is because we've raised the bar for what
             | counts as a computational primitive, and not a qualitative
             | difference.
             | 
             | A modern developer can ignore the details of a dictionary
             | in the exact same way a programmer of the past could ignore
             | the details of the mov instruction. (And be more productive
             | for it.)
        
               | ale42 wrote:
               | The problem is when developers having virtually no
               | background in actual computer science (e.g. algorithm
               | complexity) start implementing their own algorithms
               | because what thy want is not directly available as a
               | primitive. I have the impression that this tendency to
               | use more and more complex software primitives, while at
               | the same time introducing further levels of indirection
               | in the actually run machine code, is one of the reasons
               | that many applications are now as slow (or slower) than
               | they were 10 years ago despite the hardware being much
               | faster. Just compare the performance of an Electron-based
               | application (which seems the go-to solution of most
               | developers nowadays for desktop app implementation) with
               | a native one...
        
               | kqr wrote:
               | I think you're embedding a value judgment in this (slow
               | execution = bad) that is probably not shared by the
               | people using the primitives you think are inappropriate.
               | 
               | In other words, when people don't care for speed they use
               | Electron. What part of that indicates that people have no
               | background in algorithm complexity? We have traded off
               | run-time speed against other things for ages -- even in
               | computing science.
        
               | ale42 wrote:
               | > I think you're embedding a value judgment in this (slow
               | execution = bad) that is probably not shared by the
               | people using the primitives
               | 
               | Can be... because I'm also a user, and it's frustrating
               | to have the PC bloated by dozens of Electron-based apps
               | using huge amounts of RAM, draining the battery more than
               | necessary, and for part of them, actually slow to use.
               | Perhaps some users don't care because they are either not
               | sensitive to small delays or resource usage, or because
               | they have never seen anything else.
               | 
               | I see also an environmental question arising here. More
               | CPU cycles = more energy used. Makes no difference for
               | applications used by small groups of people, but for
               | software used in millions of copies, I'm wondering how
               | many MWh we are wasting (I wouldn't care much --except
               | for battery life-- if energy was only from renewable
               | sources... but it's not the case)
        
               | ehaliewicz2 wrote:
               | I'm guessing, just guessing, that most of the people who
               | don't think slow execution is bad are probably not that
               | interested in what the machine actually has to do to
               | execute their code, and hence, are not actually well
               | educated in making those tradeoffs.
        
             | skissane wrote:
             | > It's not worth anyone's time to know how it works under
             | the hood
             | 
             | Knowing (at a high level) how your programming language
             | implements dictionaries has relevance to questions like
             | time complexity of various operations, potential security
             | vulnerabilities (a hash table might succumb to hash
             | collision denial of service, especially if the
             | implementation isn't hardened against that possibility; a
             | tree-based implementation probably won't have that
             | vulnerability), likely impact of different bugs (e.g. a
             | buggy hash method can cause much more problems on a hash
             | table than on a tree, while for a buggy comparison method
             | it is the other way around), concurrency, etc
             | 
             | > because only a fool would actually implement one from
             | scratch in production code
             | 
             | I've implemented a dictionary before in C. Not for work
             | (only wrote C code for work one single time ever, and it
             | was only a page worth of code that was called from Java, no
             | complex data structures needed), just for my own
             | learning/amusement. That said, C is probably the one
             | context in which people still commonly "roll their own"
             | basic data structures, even in production code, just
             | because C's standard library is so weak in that regard (and
             | C's lack of generics/templates doesn't help either)
        
             | commandlinefan wrote:
             | > only a fool would actually implement one
             | 
             | No, only a fool would let his boss know that he did.
        
           | boffinAudio wrote:
           | You need permission to write software and distribute for
           | modern platforms. This is very disincentivizing .. not so
           | when you've got a compiler/assembler onboard everywhere and
           | can just ship without permission from MegaCorp...
        
           | GuB-42 wrote:
           | You are incentivized to user higher abstractions, sometimes
           | for good reasons (portability, productivity, etc...).
           | 
           | But the higher you go, the more magic there is. On a DOS
           | machine when x86 processors actually ended with "86", or on
           | home computers like C64s and Amigas, everything was simple.
           | No concurrency, no security, no networking, you had a
           | processor and it ran instructions. You go from a character
           | displayed on screen and with a little research, have a good
           | idea about everything that happened down to the transistor.
           | Now, it is impossible, most of the hardware components of a
           | modern computer are made of little computers, each one orders
           | of magnitude more complex than these early systems, many of
           | them run encrypted software.
           | 
           | It means that now, software development is more about trying
           | to get an idea of what the people who made the layer under
           | yours were thinking about. It is becoming harder to go with
           | first principles, too complex, it is therefore discouraged,
           | as it is more likely to hurt productivity.
        
         | commandlinefan wrote:
         | > "source code" that could be typed into DEBUG
         | 
         | Oh, wow - I remember doing something similar on my Commodore
         | 64. The C64 came with a BASIC interpreter, but no built-in
         | assembler. There was a magazine called Compute's Gazette that
         | included, in the back of every issue, a simple assembler of
         | sorts written in BASIC. So if you wanted to run any of the
         | programs included in the magazine, you'd first type in the code
         | for the assembler and use that to "bootstrap" your assembler
         | programs. They were long (pages and pages) of listings of
         | hexadecimal, with a checksum at the end of each line to make
         | sure you didn't make a tiny mistake toward the beginning that
         | rendered the whole thing useless.
         | 
         | It was actually pretty amazing how sophisticated some of these
         | programs would end up being, considering the entire hex dump
         | could realistically be printed on four or five A4 standard
         | pages.
         | 
         | Man, I spent a lot of time typing those things in - I still
         | remember off the top of my head, almost 40 years later, that
         | the opcode for LDA was A9 and STA was 8D, because I typed those
         | in a _lot_. Back then, I thought that this was more or less
         | what a career in computer programming would be like - honestly,
         | with the JIRA tickets and Sprint retrospectives and standups
         | and planning sessions that it's turned out to be, I'm a little
         | disappointed.
        
       | kazinator wrote:
       | I once made a tiny pre-emptive kernel using the DOS debugger. It
       | was like the day after graduating from CS undergrad, and I needed
       | a cleanse from the many years-long C on Unix mission (which
       | happens to be continuing, decades later).
        
       | dleslie wrote:
       | I still have a copy of "Assembly language step-by-step" which
       | started with DEBUG then moved to nasm in later chapters.
        
         | Zardoz84 wrote:
         | I have yet a book of Peter Norton of 80286 assembly that uses
         | DEBUG a lot to do the first steps programming assembly.
        
         | hyperdimension wrote:
         | Funny that this would come up today; I'm working through that
         | very book now. I'm still in the debug part and using 'a'
         | though; I haven't moved onto the assembler yet.
        
       | EvanAnderson wrote:
       | Debug is a passable quick-and-dirty assembler. It's no fun to
       | refactor code after you've resolved all the addresses and hard-
       | coded them into the "source" but when you're in a pinch and don't
       | have access to real tools it works well.
        
         | netmare wrote:
         | I "solved" that by using two assembling passes. The first had
         | dummy jump/call addresses in order to determine the code
         | offsets of labels. Then I would resolve the relative offsets,
         | replace the target labels with offset deltas and reassemble.
         | 
         | I used that trick to speed up my QBasic programs with graphics,
         | string and list handling, etc. It was one of the first
         | rewarding times in my programming "career" and it actually made
         | me feel proud of myself.
        
           | EvanAnderson wrote:
           | Yeah-- I did the same thing. It just sucks when you've
           | forgotten s prefix or a push/pop early in the code and you
           | have to retarget all the subsequent addresses. It really
           | makes you appreciate a real assembler.
        
         | 48864w6ui wrote:
         | If you write basic blocks only with a certain alignment there's
         | much less hand patching imposed for small changes
        
           | EvanAnderson wrote:
           | I remember reading about people doing that on the Apple II
           | with the Woz mini assembler. I always had an assembler on the
           | 6502 so I never did it myself. Makes sense though. Never
           | thought to do it on x86 but it's a good idea.
        
         | userbinator wrote:
         | After a while, you get really good at hex arithmetic and
         | memorising x86 instruction lengths.
        
       | peter_d_sherman wrote:
       | A few things to point out:
       | 
       | 1) The Debugger and the program being debugged -- are effectively
       | running in the _same execution environment_ ; the _same memory
       | space_ (both programs are different ranges of the same memory and
       | both can access each other 's memory without restrictions, but
       | only one can be running at one time (single stepping swaps
       | control (for an instant) and then swaps it back again, as does
       | running under the debugger until a breakpoint is hit -- as there
       | are absolutely NO threads (concurrent code execution paths) in
       | this environment!)
       | 
       | If the debugged program crashes for any reason, for example,
       | enters an infinite loop and/or doesn't yield control back to the
       | debugger via executing a RET instruction -- then the entire PC
       | (if DOS is running directly on hardware (as opposed to under
       | Windows and/or a VM) will crash, AKA "lock-up"! Powering the
       | entire machine on and off is now necessary to reboot it! (Dave
       | Letterman, many years ago, in response to the then-feared
       | upcoming Y2K disaster: "Just Reboot!")
       | 
       | 2) While this mode of programming is hard, laborious, counter-
       | intuitive, slow, and error-prone(!) -- it should also be _lauded,
       | praised_ -- because equal-and-oppositely, whoever is programming
       | at this level has effectively _gotten rid of 99.99% of the "tech
       | stack" dependencies_ -- the code written by other programmers in
       | compilers, operating systems, programing languages, programming
       | environments, libraries, frameworks, tech stacks, other software
       | components, etc., etc.
       | 
       | Oh sure, there's still DOS in the background... but the DOS
       | code/API fits into like what, like 32KB? (That's Kilobytes with a
       | 'K', not Megabytes or Gigabytes... several orders of magnitude
       | smaller...)
       | 
       | And a pure assembly low-level programmer -- does not even need to
       | depend/rely on DOS or BIOS calls... they can effectively get rid
       | of those too by writing their own hardware drivers... OS
       | developers that write in Assembly typically do this or something
       | like this...
       | 
       | Anyway, an excellent article!
        
         | rahen wrote:
         | DOS makes a nice runtime for low level and embedded
         | applications. Some implementations are 64 bits, such as this
         | one: https://github.com/dosemu2/fdpp
         | 
         | I wish there were an ARM-compatible version of DOS, if possible
         | stateless. It would often be more suitable for an ARM board
         | than a full-fledged Linux, given its almost non-existent attack
         | surface, low resource consumption, and simplicity. Heck, I'd
         | even like to see DOS microservices on stateless nano-VMs.
        
         | 082349872349872 wrote:
         | > _whoever is programming at this level has effectively gotten
         | rid of 99.99% of the "tech stack" dependencies_
         | 
         | Not necessarily: I once worked with a team whose reference
         | manual was the S/360 "Principles of Operation" -- but they
         | weren't exactly working on the metal; everyone's model of the
         | 360 architecture was virtual, all running on top of a (1960's!)
         | supervisor multiplexing the actual box.
         | 
         | https://en.wikipedia.org/wiki/Conversational_Monitor_System
         | 
         | http://bitsavers.trailing-edge.com/pdf/ibm/360/princOps/A22-...
        
       | tommieb wrote:
       | Lovely article, that brought back memories! A wee pedantic bit
       | here, to exit the program, it was the school of thought, to use
       | the following instruction - set the exit code to zero and issue
       | terminate.
       | 
       | mov ah, 4ch mov al, 00h ; Or combine both mov into mov ax, 4c00h
       | int 21h
        
       | keepamovin wrote:
       | This is where I first learned to program. the manual for MS-DOS
       | 3.1 I think it was came with instructions for the debugger and
       | assembler, and how you could create programs.
        
       | boffinAudio wrote:
       | I once made a lot of money by guiding a colleague over the phone
       | how to write a program with DEBUG.com to do a software 'upgrade'
       | consisting of 128 bytes to change in the older version .. this
       | was pre-Internet-everywhere, and the time cost of waiting for a
       | tape (yes we delivered DOS software on tape back in the day) to
       | arrive across country was too high. An hour of careful hex
       | narration later, we got it done and days of downtime were saved.
       | Oh, what a bonus that was (went straight into a new 486, lol).
       | 
       | Kind of miss DEBUG.com now that I think about it.
        
       | _0ffh wrote:
       | I'm sure I'm not the only one who's learned to write 6502
       | assembly on a machine code monitor [1].
       | 
       | [1] https://www.c64-wiki.com/wiki/Machine_Code_Monitor
        
       | jim_lawless wrote:
       | DEBUG was also able to read/write disk blocks and it could load /
       | save files from memory based on register settings. I had written
       | a batch file and a file of DEBUG commands ( redirected into DEBUG
       | ) that would load the blocks from 1.44M diskettes at about 64K at
       | a time, save each just-under-64k segment with a separate
       | filename, and then zip up the whole collection. I also had
       | written the counterpart scripts to decompress and block-write the
       | data back out to a 1.44M floppy. This allowed us to archive the
       | .zip collections as disk images for some critical boot disks and
       | such.
       | 
       | I had also written a utility in C published in Windows/DOS
       | Developer's Journal (April 1995) before some email clients could
       | UUDECODE or Base64-decode attachments. This utility wrote a short
       | loader as a text file followed by the G command to execute it.
       | Following the G command were lines of UUENCODED data. The
       | assembly language loader code would begin reading lines from the
       | standard input device and would UUDECODE them as it added them
       | into DEBUG's current working buffer starting at 100h. At the tail
       | end of the script, the N command was used to name the output
       | file, the CX register was updated via RCX to set the output
       | length, and the W command was used to save the file. This allowed
       | me to send some encoded binary files embedded in the body of the
       | email to folks that didn't have sophisticated email clients. They
       | could just pipe this portion of the email into DEBUG and it would
       | ultimately write the decoded binary file.
        
         | anthk wrote:
         | Your solution it's similar to sharutils.
        
       | billpg wrote:
       | My 80s era BBC Micro came with a 6502 assembler as part of BASIC
       | built into ROM.
       | 
       | It was a little quirky in that it was a two-pass assembler only
       | if you put it inside a FOR loop and turned off unknown-identifier
       | errors on the first pass. I was told you could write rudimentary
       | assembler macros with judicious use of PROC but I could never get
       | it working.
        
       | fuzztester wrote:
       | You could easily create a tiny .COM program to reboot the PC,
       | using DEBUG.
       | 
       | You had to start DEBUG, then, at its prompt, use, in sequence,
       | the A (Assemble) command to assemble a JMP instruction, the N
       | (Name) command to name the .COM file to write the assembled
       | machine code to, the R (Register) command to set the CX register,
       | indicating the number of assembled machine code bytes to write to
       | the file (with the BX register already initialised to 0 by DEBUG
       | on startup, because both BX and CX were used to indicate the
       | number of bytes to write) and then the W (Write) command to write
       | that many bytes of machine code to that file.
       | 
       | Then you would exit DEBUG.
       | 
       | Now, at any time after that, you could run that .COM file to
       | reboot your PC.
       | 
       | So if it was called R.COM, you could just type R and hit Enter to
       | reboot. (In DOS, you did not have to type the executable file
       | name's extension, whether it was .COM or .EXE or .BAT.)
       | 
       | So if you were playing a game in the office during working hours,
       | it could be faster to type R and hit Enter, than even to press
       | Ctrl-Alt-Del or to press the reboot button of your PC. Useful if
       | your boss suddenly came into the room ...
       | 
       | IIRC the JMP instruction was: JMP F000:FFF0. (1)
       | 
       | That would make the PC jump to that address in memory (in the
       | BIOS ROM) and start executing the code there.
       | 
       | And that was the start of the BIOS machine code to reboot the PC.
       | 
       | (1) When I googled just now to confirm the address, since I was
       | not sure of the exact one, interestingly, the first hit was an
       | article about the same topic on ... Susam's site :)
       | 
       | But his BIOS address as shown is different from mine.
       | 
       | See the section "Reboot Program" at:
       | 
       | https://susam.net/rebooting-with-jmp-instruction.html
        
         | fuzztester wrote:
         | My second paragraph above only outlines how to do it. It
         | assumes that you know the syntax of the DEBUG commands.
         | 
         | Susam's article linked above shows the exact procedure, with
         | precise syntax.
        
         | fuzztester wrote:
         | >So if you were playing a game in the office during working
         | hours, it could be faster to type R and hit Enter,
         | 
         | Of course, you would have to exit the game first, but many DOS
         | games could be exited fast, by just pressing Esc rapidly one or
         | more times, or Alt-X, or other similar methods.
        
         | fuzztester wrote:
         | The .COM program was only 5 bytes long, so it loaded instantly
         | too.
        
       | philkrylov wrote:
       | Actually, using DEBUG for creating anything besides one or two
       | simple DOS/BIOS calls was a PITA due to the lack of symbolic
       | labels. After that, the MASM/TASM boilerplate started to feel
       | bearable.
        
       ___________________________________________________________________
       (page generated 2024-04-08 23:02 UTC)