In Part 1 of the Performance-Aware Programming course, we learn to read assembly language through fun homework exercises where we simulate a few 8086 instructions. These assignments are designed to be completable in any programming language, so people can take the course in the language of their choice.
In Part 2, we learn to analyze compiler output and assess the performance of instruction sequences. This requires writing small snippets of code in a language that can be compiled. To allow people to take the course in as many programming languages as possible, I am collecting instructions on how to produce assembly-language listings in different languages.
Hopefully this will provide a growing quick-reference for people learning to read ASM to better understand the performance of their programs.
If it supports machine code output for your language, Compiler Explorer is often the simplest way to get a quick glance at the ASM for small programs. You cannot "step through" the ASM to see what it's doing like you can in a debugger, but it lets you see multiple compilations of the same program at once, which can be very useful.
- Go to godbolt.org.
- Select your language from the language drop-down.
- Select your compiler from the compiler drop-down. If you do not see a compiler drop-down (or if you'd like to add a second compiler), you can add a new compiler window by selecting
Add new... -> Compiler
- If you are unsure what compiler options you should put in the
Compiler Options
box, consult the following table for recommendations:
Language | Compiler | Typical Compiler Options |
---|---|---|
C / C++ | MSVC | -O2 |
C / C++ | GCC | -O3 |
C / C++ | CLANG | -O3 |
C# | .NET | |
ZIG | ZIG | -OReleaseFast / -OReleaseSmall / -OReleaseSafe |
Note that not all languages supported by Compiler Explorer provide actual ASM output. Some - like Python - only provide interpreter bytecode output, which is not particularly useful. The table above includes only those languages for which Compiler Explorer can produce real ASM.
To see source code along with reasonable ASM when using a debugger, you must ensure that your executable has been built with both debug information and optimizations. If you are unsure what switches are required for this, the following table can get you started:
Language | Compiler | Typical Compiler Options |
---|---|---|
C / C++ | MSVC | -Zi -O2 |
C / C++ | GCC | -g -O3 |
C / C++ | CLANG | -g -O3 |
ZIG | ZIG | -femit-asm -OReleaseFast |
Once you have an executable that has debug information and optimizations enabled, you can load the executable into a debugger and step through the ASM using the instructions below:
- If you did not build the executable inside Visual Studio to begin with, you can open an executable directly by selecting
Open -> Project/Solution...
from theFile
menu, then pickingExe Project Files (*.exe)
from the file type drop-down. This will allow you to open any executable for debugging. - Press
F11
or selectStep Into
from theDebug
menu to begin debugging the program. - If you are viewing source code only, right-click in the source code window and select
Go To Disassembly
from the context menu. - To step through system or library ASM without Visual Studio warning you, make sure
Enable address-level debugging
andShow disassembly if source is not available
are both checked from theOptions
dialog under theDebug
menu (Debugging -> General
).
If your binary has debugging symbols (see above), you can disassemble whole functions or stack frames.
- Start
lldb
and read the binary:lldb -- <binary>
(e.g.lldb -- test
) - Switch to Intel disassembly flavor:
settings set target.x86-disassembly-flavor intel
(See the note below if you wonder what this means!). - To disassemble a specific function, e.g.
main
:disassemble -b -n main
- Set a breakpoint and run to it, e.g.
b main
thenrun
- To disassemble the current function for the current stack frame:
disassemble -b
- To display disassembly on every step/stepi:
settings set stop-disassembly-display always
- To step by instruction use
stepi
,nexti
(as apposed tostep
,next
)
There are more options to the disassembler you may wish to use (see help disassemble
), but the most interesting ones ones are:
-F intel
(also set bytarget.x86-disassembly-flavor
) - use intel dialect. This is not explicitly discussed in the course so far, but by default LLDB will use an ASM dialect known as AT&T style. For the purposes of this course, it's recommended to always specify-F intel
. If you get a message aboutDisassembler flavors are currently only supported for x86 and x86_64 targets
, you might be on an Apple Silicon Mac; see the note below.-b
- show the instruction bytes. This shows the actual bytes as hex as well as the disassembly.-n <name>
- disassemble function named<name>
Note for Apple Silicon: If you are on an Apple Silicon Mac and following
this course, you might be confused by the disassembly output not matching the
x86_64 instructions. That's because Apple Silicon is an ARM procesor. SImply
pass -arch x86_64
to the clang
compiler to produce an Intel/x86_64 binary
and use the above instructions unchanged.
To avoid having to type settings set ...
every time, you can put these
commands into a .lldbinit
file in your home directory:
settings set target.x86-disassembly-flavor intel
settings set stop-disassembly-display always
Several command line utilities are available
- Install Visual Studio if you do not already have it installed.
- Open the "x64 Native Tools Command Prompt" from the Start Menu.
- To disassemble an executable (in this case, "test.exe") to an ASM listing file, use:
dumpbin /disasm test.exe > test_disassembly.asm
otool is a command-line utility which examines Mach-O files for macOS.
- Install Xcode or the Xcode Command Line Tools if you do not already have otool installed.
- To disassemble an executable (in this case, "test") to an ASM listing file, use:
otool -tV test > test_disassembly.asm
To demangle C++ symbols, also use c++filt (which is also installed with Xcode):
otool -tV test | c++filt > test_disassembly.asm
objdump is a command-line utility which examines many different executable files, including ELF for Linux.
There are two tools called objdump: the original objdump from GNU binutils and LLVM's objdump. This section describes the objdump from GNU binutils, but these instructions may work for LLVM's objdump too.
- Install GNU binutils if you do not already have objdump installed.
- To disassemble an executable (in this case, "test") to an ASM listing file, use:
objdump -d --demangle test > test_disassembly.asm
... please submit a pull request! Note that in order to be included here, you must provide comprehensive instructions that allow someone to either step through or conveniently view the assembly language output of a program snippet.
- Casey