The well-known GNU C/C++ Compiler (GCC), an optimizing 32-bit compiler at the heart of the GNU project, supports the x86 architecture quite well, and includes the ability to insert assembly code in C programs, in such a way that register allocation can be either specified or left to GCC. GCC works on most available platforms, notably Linux, *BSD, VSTa, OS/2, *DOS, Win*, etc.
The original GCC site is the GNU FTP site ftp://prep.ai.mit.edu/pub/gnu/gcc/ together with all the released application software from the GNU project. Linux-configured and precompiled versions can be found in ftp://metalab.unc.edu/pub/Linux/GCC/ There exists a lot of FTP mirrors of both sites. everywhere around the world, as well as CD-ROM copies.
GCC development has split in two branches recently. See more about the experimental version, egcs, at http://www.cygnus.com/egcs/
Sources adapted to your favorite OS, and binaries precompiled for it, should be found at your usual FTP sites.
For most popular DOS port of GCC is named DJGPP, and can be found in directories of such name in FTP sites. See:
There is also a port of GCC to OS/2 named EMX, that also works under DOS, and includes lots of unix-emulation library routines. See around the following site: ftp://ftp-os2.cdrom.com/pub/os2/emx09c/. Other URLs listed in previous versions of this HOWTO seem to be as dead as OS/2.
The documentation of GCC includes documentation files in texinfo format. You can compile them with tex and print then result, or convert them to .info, and browse them with emacs, or convert them to .html, or nearly whatever you like. convert (with the right tools) to whatever you like, or just read as is. The .info files are generally found on any good installation for GCC.
The right section to look for is:
C Extensions::Extended Asm::
Section
Invoking GCC::Submodel Options::i386 Options::
might help too.
Particularly, it gives the i386 specific constraint names for registers:
abcdSDB correspond to
%eax
,
%ebx
,
%ecx
,
%edx
,
%esi
, %edi
, %ebp
respectively (no letter for %esp
).
The DJGPP Games resource (not only for game hackers) had this page specifically about assembly, but it's down. Its data have nonetheless been recovered on the DJGPP site, that contains a mine of other useful information: http://www.delorie.com/djgpp/doc/brennan/
GCC depends on GAS for assembling, and follow its syntax (see below); do mind that inline asm needs percent characters to be quoted so they be passed to GAS. See the section about GAS below.
Find lots of useful examples in the linux/include/asm-i386/
subdirectory of the sources for the Linux kernel.
Because assembly routines from the kernel headers
(and most likely your own headers,
if you try making your assembly programming as clean
as it is in the linux kernel)
are embedded in extern inline
functions,
GCC must be invoked with the -O
flag (or -O2
, -O3
, etc),
for these routines to be available.
If not, your code may compile, but not link properly,
since it will be looking for non-inlined extern
functions
in the libraries against which your program is being linked !!!
Another way is to link against libraries that include fallback
versions of the routines.
Inline assembly can be disabled with -fno-asm
,
which will have the compiler die when using extended inline asm syntax,
or else generate calls to an external function named asm()
that the linker can't resolve.
To counter such flag, -fasm
restores treatment of the asm
keyword.
More generally, good compile flags for GCC on the x86 platform are
gcc -O2 -fomit-frame-pointer -W -Wall
-O2
is the good optimization level in most cases.
Optimizing besides it takes longer, and yields code that is a lot larger,
but only a bit faster;
such overoptimization might be useful for tight loops only (if any),
which you may be doing in assembly anyway.
In cases when you need really strong compiler optimization for a few files,
do consider using up to -O6
.
-fomit-frame-pointer
allows generated code to skip the stupid
frame pointer maintenance, which makes code smaller and faster,
and frees a register for further optimizations.
It precludes the easy use of debugging tools (gdb
),
but when you use these,
you just don't care about size and speed anymore anyway.
-W -Wall
enables all warnings and helps you catch obvious stupid errors.
You can add some cpu-specific -m486
or such flag so that
GCC will produce code that is more adapted to your precise computer.
Note that EGCS (and perhaps GCC 2.8) have -mpentium
and such flags,
whereas GCC 2.7.x and older versions do not.
A good choice of CPU-specific flags should be in the Linux kernel.
Check the texinfo documentation of your current GCC installation for more.
-m386
will help optimize for size,
hence also for speed on computers whose memory is tight and/or loaded,
since big programs cause swap, which more than counters
any "optimization" intended by the larger code.
In such settings, it might be useful to stop using C,
and use instead a language that favors code factorization,
such as a functional language and/or FORTH,
and use a bytecode- or wordcode- based implementation.
Note that you can vary code generation flags from file to file, so that performance-critical files use maximal optimization, whereas other files be optimized for size.
To optimize even more, option -mregparm=2
and/or corresponding function attribute might help,
but might pose lots of problems when linking to foreign code,
including the libc.
There are ways to correctly declare foreign functions
so the right call sequences be generated,
or you might want to recompile the foreign libraries
to use the same register-based calling convention...
Note that you can add make these flags the default by editing file
/usr/lib/gcc-lib/i486-linux/2.7.2.3/specs
or wherever that is on your system (better not add -Wall there, though).
The exact location of the GCC specs files on your system
can be found by asking gcc -v
.
GAS is the GNU Assembler, that GCC relies upon.
Find it at the same place where you found GCC, in a package named binutils.
Because GAS was invented to support a 32-bit unix compiler, it uses standard ``AT&T'' syntax, which resembles a lot the syntax for standard m68k assemblers, and is standard in the UNIX world. This syntax is no worse, no better than the ``Intel'' syntax. It's just different. When you get used to it, you find it much more regular than the Intel syntax, though a bit boring.
Here are the major caveats about GAS syntax:
%
, so that
registers are %eax
, %dl
and suches
instead of just eax
, dl
, etc.
This makes it possible to include external C symbols directly
in assembly source, without any risk of confusion, or any need
for ugly underscore prefixes.mov ax,dx
(move contents of
register dx
into register ax
) will be in att syntax
mov %dx, %ax
.b
for (8-bit) byte,
w
for (16-bit) word,
and l
for (32-bit) long.
For instance, the correct syntax for the above instruction
would have been movw %dx,%ax
.
However, gas does not require strict att syntax,
so the suffix is optional when length can be guessed from register operands,
and else defaults to 32-bit (with a warning).$
prefix,
as in addl $5,%eax
(add immediate long value 5 to register %eax
).movl $foo,%eax
puts the address of variable foo
in register %eax
,
but movl foo,%eax
puts the contents of variable foo
in register %eax
.testb $0x80,17(%ebp)
(test the high bit of the byte value at offset 17
from the cell pointed to by %ebp
).
A program exists to help you convert programs from TASM syntax to AT&T syntax. See ftp://x2ftp.oulu.fi/pub/msdos/programming/convert/ta2asv08.zip. (Since the original x2ftp site is closing, use a mirror site). There also exists a program for the reverse conversion: http://www.multimania.com/placr/a2i.html.
GAS has comprehensive documentation in TeXinfo format,
which comes at least with the source distribution.
Browse extracted .info pages with Emacs or whatever.
There used to be a file named gas.doc or as.doc
around the GAS source package, but it was merged into the TeXinfo docs.
Of course, in case of doubt, the ultimate documentation
is the sources themselves!
A section that will particularly interest you is
Machine Dependencies::i386-Dependent::
Again, the sources for Linux (the OS kernel), come in as good examples;
see under linux/arch/i386, the following files:
kernel/*.S, boot/compressed/*.S, mathemu/*.S
If you are writing kind of a language, a thread package, etc you might as well see how other languages (OCaml, gforth, etc), or thread packages (QuickThreads, MIT pthreads, LinuxThreads, etc), or whatever, do it.
Finally, just compiling a C program to assembly might show you the syntax for the kind of instructions you want. See section Do you need Assembly? above.
GAS is a 32-bit assembler, meant to support a 32-bit compiler.
It currently has only limited support for 16-bit mode,
which consists in prepending the 32-bit prefixes to instructions,
so you write 32-bit code that runs in 16-bit mode on a 32 bit CPU.
In both modes, it supports 16-bit register usage,
but what is unsupported is 16-bit addressing.
Use the directive .code16
and .code32
to switch between modes.
Note that an inline assembly statement
asm(".code16\n")
will allow GCC to produce 32-bit code that'll run in real mode!
I've been told that most code needed to fully support 16-bit mode programming was added to GAS by Bryan Ford (please confirm?), but at least, it doesn't show up in any of the distribution I tried, up to binutils-2.8.1.x ... more info on this subject would be welcome.
A cheap solution is to define macros (see below) that somehow produce
the binary encoding (with .byte
) for just the 16-bit mode instructions
you need (almost nothing if you use code16 as above,
and can safely assume the code will run on a 32-bit capable x86 CPU).
To find the proper encoding, you can get inspiration from
the sources of 16-bit capable assemblers for the encoding.
GASP is the GAS Preprocessor. It adds macros and some nice syntax to GAS.
GASP comes together with GAS in the GNU binutils archive.
It works as a filter, much like cpp and the like. I have no idea on details, but it comes with its own texinfo documentation, so just browse them (in .info), print them, grok them. GAS with GASP looks like a regular macro-assembler to me.
The Netwide Assembler project is producing yet another i386 assembler, written in C, that should be modular enough to eventually support all known syntaxes and object formats.
Binary release on your usual metalab mirror in
devel/lang/asm/
Should also be available as .rpm or .deb in your usual RedHat/Debian
distributions' contrib.
At the time this HOWTO is written, version 0.98 of NASM is just out.
The syntax is Intel-style. Some macroprocessing support is integrated.
Supported object file formats are
bin
, aout
, coff
, elf
, as86
,
(DOS) obj
, win32
, (their own format) rdf
.
NASM can be used as a backend for the free LCC compiler (support files included).
Surely NASM evolves too fast for this HOWTO to be kept up to date. Unless you're using BCC as a 16-bit compiler (which is out of scope of this 32-bit HOWTO), you should definitely use NASM instead of say AS86 or MASM, because it is actively supported online, and runs on all platforms.
Note: NASM also comes with a disassembler, NDISASM.
Its hand-written parser makes it much faster than GAS, though of course, it doesn't support three bazillion different architectures. For the x86 target, it should be the assembler of choice...
AS86 is a 80x86 assembler, both 16-bit and 32-bit, part of Bruce Evans' C Compiler (BCC). It has mostly Intel-syntax, though it differs slightly as for addressing modes.
A completely outdated version of AS86 is distributed by HJLu just to compile the Linux kernel, in a package named bin86 (current version 0.4), available in any Linux GCC repository. But I advise no one to use it for anything else but compiling Linux. This version supports only a hacked minix object file format, which is not supported by the GNU binutils or anything, and it has a few bugs in 32-bit mode, so you really should better keep it only for compiling Linux.
The most recent versions by Bruce Evans (bde@zeta.org.au) are published together with the FreeBSD distribution. Well, they were: I could not find the sources from distribution 2.1 on :( Hence, I put the sources at my place: http://www.tunes.org/~fare/files/asm/bcc-95.3.12.src.tgz
The Linux/8086 (aka ELKS) project is somehow maintaining bcc (though I don't think they included the 32-bit patches). See around http://www.linux.org.uk/ELKS-Home/index.html and ftp://linux.mit.edu/pub/linux/ELKS/. I haven't followed these developments, and would appreciate a reader contributing on this topic.
Among other things, these more recent versions, unlike HJLu's, supports Linux GNU a.out format, so you can link you code to Linux programs, and/or use the usual tools from the GNU binutils package to manipulate your data. This version can co-exist without any harm with the previous one (see according question below).
BCC from 12 march 1995 and earlier version has a misfeature that makes all segment pushing/popping 16-bit, which is quite annoying when programming in 32-bit mode. I wrote a patch at a time when the TUNES Project used as86: http://www.tunes.org/~fare/files/asm/as86.bcc.patch.gz. Bruce Evans accepted this patch, but since as far as I know he hasn't published a new release of bcc, the ones to ask about integrating it (if not done yet) are the ELKS developers.
Here's the GNU Makefile entry for using bcc
to transform .s
asm
into both GNU a.out .o
object
and .l
listing:
%.o %.l: %.s bcc -3 -G -c -A-d -A-l -A$*.l -o $*.o $<
Remove the %.l
, -A-l
, and -A$*.l
,
if you don't want any listing.
If you want something else than GNU a.out,
you can see the docs of bcc about the other supported formats,
and/or use the objcopy utility from the GNU binutils package.
The docs are what is included in the bcc package. I salvaged the man pages that used to be available from the FreeBSD site at http://www.tunes.org/~fare/files/asm/bcc-95.3.12.src.tgz. Maybe ELKS developers know better. When in doubt, the sources themselves are often a good docs: it's not very well commented, but the programming style is straightforward. You might try to see how as86 is used in ELKS or Tunes 0.0.0.25...
Linus is buried alive in mail, and since HJLu (official bin86 maintainer) chose to write hacks around an obsolete version of as86 instead of building clean code around the latest version, I don't think my patch for compiling Linux with a modern as86 has any chance to be accepted if resubmitted. Now, this shouldn't matter: just keep your as86 from the bin86 package in /usr/bin, and let bcc install the good as86 as /usr/local/libexec/i386/bcc/as where it should be. You never need explicitly call this ``good'' as86, because bcc does everything right, including conversion to Linux a.out, when invoked with the right options; so assemble files exclusively with bcc as a frontend, not directly with as86.
These are other, non-regular, options, in case the previous didn't satisfy you (why?), that I don't recommend in the usual (?) case, but that could prove quite useful if the assembler must be integrated in the software you're designing (i.e. an OS or development environment).
Win32Forth is a free 32-bit ANS FORTH system that successfully runs under Win32s, Win95, Win/NT. It includes a free 32-bit assembler (either prefix or postfix syntax) integrated into the reflective FORTH language. Macro processing is done with the full power of the reflective language FORTH; however, the only supported input and output contexts is Win32For itself (no dumping of .obj file, but you could add that feature yourself, of course). Find it at ftp://ftp.forth.org/pub/Forth/Compilers/native/windows/Win32For/.
Terse is a programming tool that provides THE most compact assembler syntax for the x86 family! See http://www.terse.com. However, it is not quite free software. It is said that there was a project for a free clone somewhere, that was abandonned after worthless pretenses that the syntax would be owned by the original author. Thus, if you're looking for a nifty programming project related to assembly hacking, I invite you to develop a terse-syntax frontend to NASM, if you like that syntax.
You may find more about them, together with the basics of x86 assembly programming, in Raymond Moon's FAQ for comp.lang.asm.x86: http://www2.dgsys.com/~raymoon/faq/asmfaq.zip.
Note that all DOS-based assemblers should work inside the Linux DOS Emulator, as well as other similar emulators, so that if you already own one, you can still use it inside a real OS. Recent DOS-based assemblers also support COFF and/or other object file formats that are supported by the GNU BFD library, so that you can use them together with your free 32-bit tools, perhaps using GNU objcopy (part of the binutils) as a conversion filter.