10

What is the oldest free-to-use and royalty-free assembler targeting the Intel 80386 CPU in 32-bit protected mode, which knows all the instructions and addressing modes, and doesn't have obvious bugs?

Some which I was able to find:

  • MASM (5.00 released in 1987) and TASM (1.0 released in 1988) already support the 386, but they were not free in the 1980s and 1990s.
  • MASM32 contains ml.exe version 6.14.8444, this is MASM written and released by Microsoft in 1999. The license terms for including and using ml.exe in MASM32 are unclear.
  • GNU Binutils 2.9.5 (released in 1999-08) has the GNU as(1) assembler, which is free, and its code generation seems to be good enough.
  • GNU Binutils 2.9.1 (released on 1998-05-01) has the GNU as(1) assembler, but it has some obvious bugs, for example it generates 2 extra NUL bytes after a mov $0x100, %bx instruction.
  • GNU Binutils 2.7 (released on 1996-07-15) has the GNU as(1) assembler, apparently with no extra bugs compared to 2.9.1 in my test program. See source code and Linux i386 binaries precompiled by me.
  • GNU Binutils 2.6 (found in Debian 1.1 Buzz, released on 1995-12-23) has the GNU as(1) assembler, but it seems to be very buy: it generates incorrect code for xor %al, %al and inc %al and hundreds of other instructions.

Is there anything earlier than 1996-07? (That's about 11 years after the 80386 CPU was made available.)

17
  • 2
    Also keep in mind that while MASM and TASM were not free, they were of course pirated a lot, so what would have been the incentive for someone to spend the effort and make a free version? The "as" assembler came from a completely different direction (and uses AT&T-inspired syntax instead of Intel syntax, which many found weird back then). Also, completeness as in "has all instructions and addressing modes" definitely wouldn't have been a priority.
    – dirkt
    Commented Mar 30 at 6:50
  • 3
    @dirkt I remember I found as with its weird AT&T syntax (the guys who invented it have really earned the prize for the most convoluted syntax....) basically unusable.
    – tofro
    Commented Mar 30 at 8:35
  • 2
    @PeterCordes The answer you linked clearly explains that it goes back to the PDP-11, and cc was written for the PDP-11, and if you ever used cc -S (which still works today) then you also see why all other ISAs stuck to the same syntax. So it's not "AT&T's fault, they had to come up with something different", it was made this way to simplify porting cc/as for other CPUs.
    – dirkt
    Commented Mar 30 at 16:27
  • 1
    Maybe generating asm output from the compiler was slightly easier with a destination-on-the-right syntax, but short-term gain, long term pain. (Except for people who like AT&T syntax. It does have its advantages, like clearer distinction between reg names vs. symbols, and clearer mapping of fields to machine code, less free-form in addressing modes. I personally think NASM is very nice, but MASM is a disaster with how [] don't mean anything unless there's a register inside, not just constants / symbols, and multi-character literals are a total mess, although GAS is no better about that.) Commented Mar 30 at 19:04
  • 3
    @PeterCordes yes, but ARM came way later than i386, and at that time extending the parser was less of an issue. Please don't try to rewrite history, I was there when it happened, and the reason really is "it was easier to take the existing toolchain and port it to i386 like this". I have used as with the AT&T syntax, and yes, it was painful, and at that time I also learned the reason for why they did it that way. "It's just AT&T being stupid" was not the reason.
    – dirkt
    Commented Mar 30 at 19:41

4 Answers 4

17

The Netwide Assembler (NASM) was first released in October 1996. From the beginning it supported Intel syntax (similar to MASM and TASM) and most if not all known x86 instructions, and could be used to write protected-mode programs. It didn’t include an extender or linker of its own however, but third-party linkers and extenders were quickly made available (I used ALINK and WDOSX).

I remember this being quite a sensation at the time, since there were no free x86 assemblers with good coverage of the instruction set. Initial releases were buggy (mostly in corner-cases), so perhaps sometime in 1997 should be considered as the first mostly bug-free release.

Another early assembler with support for 32-bit segments and 386 instructions is Bruce Evans’ as86. The earliest version I can find dates back to November 1991 and includes support for 386 instructions — in fact it’s described as an updated assembler adding 386 support (to previous versions with 286 support). Later versions were released by Robert de Bath. As far as I’m aware this was never used much except to build portions of the Linux kernel and as a machine-code generator for bcc (Bruce Evans’ C compiler), so it might have bugs for patterns which don’t appear in those two scenarios. The project is available as dev86 and is packaged in many Linux distributions (bin86 in Debian and derivatives).

2
  • 1
    What I find interesting is strictly speaking you could use the netwide assembler without a linker if you had enough RAM (several times the size of the output binary). I've had only a little trouble getting NASM to spit out relocation tables.
    – Joshua
    Commented Mar 30 at 22:04
  • 1
    That’s interesting, I used it without a linker to build .COM files but always used a linker for .EXEs. Commented Mar 30 at 22:13
8

TL;DR NASM 0.95 released on 1997-07-27 works for my test program, earlier NASM versions don't.

Here are some more details about early NASM versions (as suggested by @Stephen Kitt) downloaded from the old NASM project on SourceForge:

  • NASM 0.91 was released on 1996-11-22. It doesn't support %macro, %define, %ifdef or %if.
  • NASM 0.92 was never released.
  • NASM 0.93 was released on 1997-01-23.
  • NASM 0.94 was released on 1997-04-08. New: it supports %macro, %define and %ifdef. Both the precompiled DOS version and the Linux version I compiled fail with out of memory for my test program (output .bin file smaller than 38 kB), so it looks like I've hit a bug. Simpler test programs do work.
  • NASM 0.95 was released on 1997-07-27. New: it has __NASM_MAJOR__ and __NASM_MINOR__ macros predefined; it supports -l listfile output. This is the first version which works for my test program.
  • NASM 0.96 was released on 1997-11-07. New: it supports %if, align and alignb.
  • NASM 0.97 was released on 1997-12-06.

All the downloaded files were .zip archives containing C source and the nasm.exe program precompiled for DOS 8086. I've used the file time of each nasm.exe as the release date above.

The Makefile in each .zip archive contains working build instructions for GCC (for Linux etc.). The build is very simple: compile a bunch of .c files, and link them together.

2
  • Maybe I am overlooking something, but where did you specify in your question that you need %macro, %define, and %ifdef? "I have a specific file that I need to assemble" is different from "what is the oldest assembler that supports the complete ISA".
    – dirkt
    Commented Apr 5 at 15:23
  • I have many projects going on, some of them need %ifdef, some of them don't. An assembler without %ifdef support is still a good answer to my question. However, it's informative to include such detail in the answer.
    – pts
    Commented Apr 5 at 16:42
5

The as86 assembler (part of dev86) suggested by @Stephen Kitt is indeed a good answer to the question: it is under the GPL; it supports all 386 instructions; it supports 32-bit (protected) mode; it can create a raw binary output file which doesn't need linking. It is even older than NASM!

The earliest as86 version I could find (using the link provided by @Stephen Kitt) doesn't even have a version number, and the file date is 1991-11-29. Both the C soure and the precompiled Linux i386 a.out executables are available from here. After a few small changes I managed to compile it with both GCC 4.8 and the OpenWatcom C compiler. It works, and it was able to compile the bundled 32-bit asm/imul.asm correctly. The test command line: ./as -b t.bin asm/imul.asm && ndisasm -b 32 -o 5 -e 5 t.bin. I haven't tried more complicated test programs, because even as86 0.0.5 has problems with them (see below).

The earliest as86 version which worked for my test program was 0.0.7 (1996-09-03). a86 0.0.6 (1996-05-31) has macro support broken (it's impossible to define a macro), and as86 0.0.5 (1996-03-24) can't generate a raw binary output file without adding header and footer bytes (and it also has macro support broken). However, as86 is older than that, some if its releases contain a file named README.1994, which explains how it was released as open source under the GPL. However, the earliest version I was able to find and download is 0.0.5.

I appreciated that as86 uses very little memory: in fact, it keeps only macros and symbol address in memory, but never the entire input or output file. Thus an assembly source containing lots of instructions but only a few labels needs only little memory.

When testing various versions of as86, I discovered a bug which affected at least versions 0.0.11 and 0.12.0 (but not 0.16.2 anymore): memory offset calculations were incorrect when -j was used, org was specified (even org 0), and the assembler autogenerated a longer-than-8-bit conditional jump instruction. I was able to work around this bug by not using -j and manually adding near to the jump instructions for which as86 reported the address out of bounds error.

Also I was able to test as86 (as86.exe) 0.0.11 and 0.12.0 precompiled for DOS 8086 (part of the official releases Dev86dos-0.0.11.zip and Dev86dos-0.12.0.zip, respectively). The compile-time preallocated heap was too small, this caused compilation of my test program to fail. I fixed it by writing and running a script on my test program source code to rename labels to make them shorter. This made the symbol table shorter, and now it fit to the heap. Later releases of as86.exe (such as 0.16.2) didn't have such a low heap limit. The 32-bit ports of as86 had a larger heap preallocated at compile time, to which my test program fit.

I was also able to get version 0.0.5 (part of Dev86-0.0.5.tar.gz released on 1996-03-24) to work for my 32-bit i386 test program by recompiling as86 from sources using a somewhat modern GCC targeting Linux i386, and manually stripping 5 bytes of header and 4 bytes of footer from the output raw binary file. Macros don't work, all jumps are 32 bits wide, and there is no near keyword.

The name as86 and the packaging is a bit misleading: as86 is bundled with BCC (Bruce's C compiler), a C compiler targeting the 16-bit 8086 CPU) in the dev86 package. This seems to suggest that as86 is also 16-bits only. But in fact it supports all 386 and 486 instructions and 32-bit (protected) mode. The use32 directive or the -3 command-line flag can be used to activate 32-bit mode.

I wasn't able to find any precompiled release of dev86 0.0.7 for DOS 8086, i.e. Dev86dos-0.0.7.tar.gz. (Does anyone have a copy?)

2
  • 1
    I think the first release of as86 is still available here; it seems to support 386 instructions too (at least, it can be built with I80386 and then supports -0 and -3 options). This dates back to 1991. Commented Apr 5 at 12:18
  • @StephenKitt: Thank you for mentioning this very early as86! I've tried it and updated my answer.
    – pts
    Commented Apr 5 at 17:54
4

Coherent, a Unix clone contained the Mark Williams 80386 assembler since 1992. See the assembler source code (written in C, I'm not sure which version the link points to). Coherent was open sourced in 2015.

Coherent version 4 (released in 1992) required a 386 CPU, so most probably that's when the assembler supporting 386 appeared.

Version 4.2.10 was released in 1994, and that's available for download. After extraction and mounting the disk image base/ddk32.dd, the file 386DK/bin/as (69024 bytes, last modified 1992-11-11) looks like an assembler program executable. It contains the string Mark Williams 80386 assembler. file(1) displays: as: Intel 80386 COFF executable, no relocation info, no line number info, stripped, 3 sections, optional header size 28. objdump -x displays the 3 sections (.text, .data and .bss), ndisasm -b 32 -o 0xa8 -e 0xa8 as disassebles the text section right from the entry point, with mov eax,0x1 followed by call 0x7:0x0 being the _exit(...) call at the end of _start.

We need further investigation, but this can easily be the oldest free-to-use assembler supporting the 386 CPU. However, it was released as free only in 2015.

I was able to build a Linux i386 hello-world with it. The source code (file hellol.s) looks like this:

.globl _start

_start:
mov $4, %eax  / SYS_write.
mov $1, %ebx  / STDOUT_FILENO.
mov $msg, %ecx
mov $msg_end1-1-msg, %edx
int $0x80  / Linux i386 syscall.

mov $1, %eax  / SYS_exit.
xor %ebx, %ebx  / EXIT_SuCCESS == 0.
int $0x80  / Linux i386 syscall.

msg: .string "Hello, World!\n"
msg_end1:

Here is how I build an run it on my Linux amd64 system:

$ ls -l as
-r-x--x--x 1 pts pts 69024 Nov 11  1992 as
$ ls -l mw386as
-rwxrwxr-x 1 pts pts 69024 Apr  8 02:24 mw386as
$ ./mw386as -V
Mark Williams 80386 assembler
no work
$ ./mw386as hellol.o
$ file hellol.o
hellol.o: Intel 80386 COFF object file, no line number info, not stripped, 3 sections, symbol offset=0xc6, 6 symbols
$ ld -m elf_i386 -N -o hellol hellol.o
$ ./hellol; echo $?
Hello, World!
0

The ld command above runs the GNU ld(1) from GNU Binutils, as natively installed to my Linux system. Because of libbfd, GNU Binutils supports multiple object file formats, including the Intel 80386 COFF built by the Mark Williams 80836 assembler. However, the COFF generated by the assembler is slightly incorrect (hence the workaround with the ld -N flag). I've written the Perl script fixcoff.pl to fix it.

The mw386as program is a native Linux i386 executable, a port of the Mark Williams 80386 assembler as above to Linux i386. It's available here: https://github.com/pts/pts-mw386as-linux

FYI There is no .ascii directive in this version of the Mark Williams 80836 assembler. There is .string though, but that generates an extra NUL byte at the end. Some Coherent source code seems to use it, but the latest version of this assembler doesn't support it either.

You must log in to answer this question.

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