Assemble x86-64 MinGW Code and Inspect the Object Safely
You will turn one small assembly source file into an x86-64 Windows object, inspect its sections and symbols, and know which assembler choices affect the result. The examples use the installed x86_64-w64-mingw32-as from the binutils-mingw-w64-x86-64 package. Expect about 10 minutes if the toolchain is already installed. No elevated privileges are needed.
The route
Jump straight to the step you need, or tick off Done means at the end.
Before you start
Have a shell, the MinGW assembler, and an object inspection tool available. Check the exact binary and package-facing target before relying on examples:
command -v x86_64-w64-mingw32-as
x86_64-w64-mingw32-as --version
x86_64-w64-mingw32-as --dump-config
On the machine used for this guide, the assembler reports GNU Binutils 2.41.90.20240122 and the target x86_64-w64-mingw32. The paired x86_64-w64-mingw32ucrt-as manpage describes the same assembler build and target family. Treat those details as local facts: another package revision may accept a different set of target extensions.
Checkpoint
Continue only when the version output names the assembler you intend to use. If the command is missing, install the distribution package through your normal package-management process. That is an administrative change and is outside the assembly steps here.
1. Create a minimal source file
Save this as add-one.s. It follows the GNU assembler syntax shown by the installed target. The x86-64 Windows calling convention passes the first integer argument in %ecx; the function returns it in %eax.
.text
.globl add_one
.def add_one; .scl 2; .type 32; .endef
add_one:
movl %ecx, %eax
addl $1, %eax
ret
.text selects the code section and .globl makes the entry point visible to the linker. The .def line supplies COFF symbol metadata for this MinGW object. This is an object-file input, not a complete executable, so assembling it does not run the function or change a system.
Check the source for accidental typographical substitutions before assembling. A straight ASCII hyphen in an option and a percent sign in a register name matter; copying formatted text from a document can silently change either.
2. Assemble deliberately as 64-bit
Run the assembler with an explicit output path:
x86_64-w64-mingw32-as --64 -o add_one.o add-one.s
The target is already x86-64, but --64 records the intended word size in the command itself. The manpage defines --32, --x32, and --64 for the i386 backend. Those switches describe ELF word-size modes in the generic documentation; this MinGW invocation produces a PE/COFF object, so verify the actual file rather than inferring its format from the switch.
A successful assembly normally prints nothing and returns status zero:
test -f add_one.o && echo "object created"
file add_one.o
Expected output includes object created and wording similar to Intel amd64 COFF object file, with the file format identified as pe-x86-64. The exact descriptive wording belongs to file, not to as, so do not use it as a stable parser interface.
Checkpoint
You now have an object file, not a Windows executable. Linking it requires a suitable linker and runtime or entry point. Do not try to execute add_one.o.
3. Inspect sections and symbols
Use the target-prefixed inspection tools when they are installed:
x86_64-w64-mingw32-objdump -h add_one.o
x86_64-w64-mingw32-nm add_one.o
The section listing should contain a .text section marked as code. The symbol listing should contain add_one. This verifies that the source was assembled into the intended kind of object and that the entry point was not left local.
For a byte-level view, disassemble the object:
x86_64-w64-mingw32-objdump -dr add_one.o
You should see the move from %ecx to %eax, the increment, and the return instruction. Relocations are shown if the source refers to symbols whose final addresses are not known during assembly.
4. Add a listing when the source is hard to read
The -a family enables listings. For an assembly listing with source lines, write it to a separate file:
x86_64-w64-mingw32-as --64 -al=add_one.lst -o add_one.o add-one.s
sed -n '1,30p' add_one.lst
The listing combines source lines with addresses and emitted bytes. Keep it as a debugging aid, not as a replacement for inspecting the object. If you pass -a without a sub-option, the assembler defaults to a broader listing; -al is more focused for this example.
5. Make architecture choices explicit
Use -march when the source deliberately depends on an instruction set. For example, the installed manpage lists generic64, znver4, and many extension names. This command asks the assembler to accept AVX2 instructions:
x86_64-w64-mingw32-as --64 -march=generic64+avx2 -o vector.o vector.s
Only choose an extension supported by every CPU that will execute the resulting program. Assembly success proves that the selected assembler accepts the instruction; it does not prove that an older deployment machine can execute it. If -march rejects an instruction, either change the source or choose a target that matches the real deployment boundary.
When the source uses / in an expression, remember the target-specific --divide option. The installed documentation says it makes slash an ordinary expression character on systems where slash is otherwise a comment marker, while leaving a slash at the beginning of a comment line unaffected. Do not add it by habit; use it only when the source needs that interpretation.
6. Diagnose failures without hiding them
For a quick option summary, use:
x86_64-w64-mingw32-as --help
x86_64-w64-mingw32-as --target-help
Common traps are straightforward:
- A missing input file is usually a path or working-directory error. Pass the source path explicitly and check it with
test -r add-one.s. - An unknown instruction often means the
-marchchoice is too old or an extension was omitted. Check the target option summary before changing syntax. - A warning can indicate an assumption that lets assembly continue. Add
--fatal-warningsin CI when warnings must fail the build. --defsym NAME=VALUEdefines an integer symbol before assembly. Values beginning with0xare hexadecimal and values beginning with0are octal. Keep such build-time inputs visible in logs.
Do not use -Z as a recovery technique. The manpage says it can generate an object after errors, which is useful for specialised diagnostics but dangerous if that object is allowed into a normal build. If assembly fails, remove or quarantine the output before retrying. The example above only creates local files, so recovery is simply to delete add_one.o and add_one.lst and rerun the corrected command.
Done means
x86_64-w64-mingw32-as --versionidentified the intended toolchain.- The source assembled with status zero and produced
add_one.o. filereported an x86-64 PE/COFF object.objdumpshowed a code section andnmshowedadd_one.- You chose instruction extensions based on the CPUs that will run the program, not just on what the assembler accepts.