A 23-byte “hello, world” program assembled with DEBUG.EXE in MS-DOS
github.com
github.com
xchg ax,bp
mov dx,107
int 21
ret
db "hello, world" 0d 0a '$'
22 bytes, of which 15 is the message.For anybody interested some DOS default register values documented: http://www.fysnet.net/yourhelp.htm
I love how, without the 'hello, world' message itself, 25% of your entire HELLO.ASM codebase is from a random HN comment
Edit: I saw the new commit, HN is lovely
By the way, I noticed just now that @userbinator occurs in the credits section of an earlier repository of mine too: https://github.com/susam/tucl#credits :)
Beat the enterprise Fizbuzz by 25 contribs.
db '4LOHA- W0rld' 0d 0a '$'
xchg ax, bp
xchg cx, dx
int 21
retCue lamentations of today's complexity mixed with feelings of life being great because of it.
CS pointed to the start of the code segment, DS to the start of the data segment. As long as the code segment plus the initialized data of the data segment was less than 64K, you could have a considerably larger runtime footprint (up to 128K) and still be a COM file with all near pointers.
(OK, so it'll often be the output of a C compiler, but there's usually some work to be done in asm to get the system in a state with a stack, clocks, RAM etc to run a C program!)
C:\>DEBUG.COM
-A
^ Error
-N HELLO.COM
-L
-U 100 107
0340:0100 B409 MOV AH,09
0340:0102 BA0801 MOV DX,0108
0340:0105 CD21 INT 21
0340:0107 C3 RET
-
There is another copy of the debugger for MS-DOS v2.0 here: https://github.com/microsoft/MS-DOS/blob/master/v2.0/bin/DEB... This one does does have an assembler. C:\>DEBUG.COM
-A
0482:0100 MOV AH, 2
0482:0102 MOV DL, 41
0482:0104 INT 21
0482:0106 RET
0482:0107
-N A.COM
-R CX
CX 0000
:7
-W
Writing 0007 bytes
-Q
C:\>A
A
C:\>https://github.com/dlang/dmd/blob/master/compiler/src/dmd/ba...
The fun thing to do with it is send it your name and have it disassemble it.
(I use it to check the object code generated by the compiler. Instead of pretty-printing the instruction stream, it disassembles the binary object code. This helps to find mistakes in either the code generation or the disassembler, as they must match.)
Anyhow, feel free to incorporate it into your obj file dumper, exe file dumper, debugger, or any tool that can benefit by examining code.
The limitations are similar and you can do assembly level coding
Mighta been file.com. Mighta been real men.
x)y'!B:)),DM!G@))p,T]l%)),@@@Hello, world!$XP5]A%=!P[PZ^V!wV!wX5.<(GV(gX534,!(GWr!
My first version was shorter than 80 characters and "symmetrical" (same number of bytes before and after the string), but it didn't work on the 80486 and under DOSBox. The '486 needed an extra jump to clear the pipeline, and DOSBox didn't support the CP/M "CALL 5" interface.
It dates back to CP/M days.
However, I always ended my programs with INT 20 instead of RET, or INT 21 with AH = 0. RET worked both for standalone programs or in debug, but there was one exception: if you ran your code repeatedly in debug with G 100. It would work the first time, but IIRC the stack wasn't reinitialized to return to the correct place on subsequent G commands. I spent a lot of time typing, running and patching in a single session and INT 20 always worked correctly.
That would exit the program with AL being the exit value. I'm not aware of AH = 0?
Sadly, I used to write these kinds of programs with a hex editor, so "B4 4C CD 21" is etched into my brain even though "CD 20" was shorter.
Good old days of writing small utilities directly using Debug and modifying the disk sectors etc etc!!!
The Ralf Brown's Interrupt List was the bible for me... It went on until late 2000s is when I moved out of ASM to C.
Now fiddling with Golang and Rust, but anytime would prefer C or ASM or C with ASM.
Please don't jump on me as I've been primarily in the System's programming and in Information Security for my entire life. No other lang supports me like C / ASM.
I have that printed and bound somewhere in my parent's house! great memories.
It's probably been over 3 decades since I had to use that command, but I still remember it for some reason...
Many years ago I used debug.exe create a bootable floppy that did nothing but display my name on the screen when booted from. I peed a little when it finally worked.
Why did MS stop shipping it???
How do I remember that number without looking it up?
https://01.org/sites/default/files/documentation/intel-gfx-p...
It's a very welcoming scene. Highly recommended, though I'll warn you that sizecoding can be an addictive hobby.
EXE2BIN program.exe program.com
Apps were super tiny doing this. Nice to see someone taking it to the extreme with Assembly.
mov ah, 9
mov dx, offset helloworld
int 21
mov ah, 0
int 21
.helloworld:
db 'hello, world', d, A, '$'MOV AH, 9
MOV DX, str
INT 21
MOV AH, 0
INT 21
Str:
DB 'hello, world', d, A, '$'
MOV AH, 9
MOV DX, str
INT 21
RET
str:
DB 'hello, world', d, A, '$'
Why can MOV AH, 0
INT 21
be replaced by RET? Here is the answer: https://stackoverflow.com/a/60805758UPDATE: Under https://news.ycombinator.com/item?id=33398592 userbinator posted an additional possible optimization.
INT 21
RET
can be replaced with JP 5I don't see why this would be faster than using RET directly and having the INT 20 opcode at address 0 do the work.
Since I was still learning the x86 CPUs, the intention here was not to save bytes but instead to have something working. I believe I picked up the style of having the string at the top and jumping over it from other similar code I had come across in those days.
You are right of course. Here is a complete example that moves the string to the bottom and saves two bytes:
C:\>DEBUG
-A
1165:0100 MOV AH, 9
1165:0102 MOV DX, 10B
1165:0105 INT 21
1165:0107 MOV AH, 0
1165:0109 INT 21
1165:010B DB 'hello, world', D, A, '$'
1165:011A
-G
hello, world
Program terminated normally
-N HELLO.COM
-R CX
CX 0000
:1A
-W
Writing 0001A bytes
-Q
C:\>HELLO
hello, world
C:\>
I have now updated the GitHub repository with this updated source code and binary. Thank you for the nice comment!Maybe someone here knows what was that purpose?
I was surprised when he told me how people normally passed arguments. They would put them after the JMS instruction in the caller. So, the callee would indirect from the "return address" stored in FOO to retrieve its arguments, incrementing the return address after each one. So the code to call FOO with arguments 1 and 2 would be (in Intel syntax)
JMS FOO
DW 1
DW 2
[code to run after returning]
Mark Smotherman explains this in more detail, using PDP-8-compatible syntax, in https://people.computing.clemson.edu/~mark/subroutines/pdp8.....It seems sort of intuitive to do things this way; if you're writing atan2(3, 4), it's nice to be able to translate this to
jms atan2
3
4
rather than, say, mov $3, %rdi; mov $4, %rsi; call atan2.Maybe someone had this habit in their assembly and adapted it to the 8086's calling convention by inserting MOVs and a JMP instruction?
You can also imagine that a compiler might be somewhat simpler if it generated code in the order in which it encountered the expressions. Gas lets you switch back and forth between .text and .data, so that if your compiler does this, you still don't have to insert JMP instructions. However, a few years ago when I wrote Ur-Scheme, I did do this for lambda expressions: the code for the lambda expression is generated inline in the middle of its containing code, which then has to contain a jump past it.
Hence you declare your variables at the start of the program and jump past them.
Minimizing the number of forward references may make the assembly run faster or use less memory, but usually it's just a convention to make the source code more neatly organized.
Many shipped apps in MS-DOS 6 had became .EXE:s.
One limit of a .COM is that it is one segment size (64 kilobytes) max file size.
mov eax, $c08e0040
Beat that!
Essentially, from MS DOs 2.0 onwards, with DEBUG.EXE you had an “adequate” development environment. No LSP, though :)
Masm and other assemblers have to be obtained and installed separately.
For those who want to re-live the moment, look at this PDF starting on internal page 873 (file page 894): http://vtda.org/books/Computing/OperatingSystems/DOSPowerToo...
If that isn't dark magic I don't know what is.
Nowadays we’re very, very far from the hardware - hardware that has grown very complicated in comparison.
Curiously, I found learning Rust way more challenging than learning 16-bit assembly (it was way simpler back then; no complex instructions, less baggage, simpler processors... and less expectations :)).
For x86, at least, I wouldn't even say that it was less complex. Segmented memory alone is a huge complication, and then on top of that there was all the legacy CISC stuff like the BCD helpers or ENTER/LEAVE; x64 is comparatively streamlined.
At least with DEBUG and assembler I could reasonably reason and do things by hand. With many modern languages I have no idea where to even start checking what they do and how.