x64汇编开发介绍
Posted 2020-08-20 20:27 +0800 by ZhangJie ‐ 6 min read
最近在工作和学习中发现,其实汇编是非常重要的,即便现在高级语言已经非常方便了,但是了解汇编对于深入理解计算机系统,以及一些高深的知识点是不可或缺的。举几个例子,比如说Linux操作系统有一个系统调用函数叫Fork我们都知道Fork的返回值在子进程中是0,在父进程中是非0,那这个是如何实现的呢?对于不了解汇编的人也很难有能力去阅读Linux操作系统源码,只能道听途说了解到个大概原因。再比如接下来要讲的gomonkey测试框架实现的一些指令patching操作,这些都是与汇编操作分不开的。甚至你想了解下上下文切换开销,你都需要深入了解下指令执行周期等等的问题。
不懂汇编,不妨碍你开发上层应用,但是对你的深度就是一道坎,你很难跨国这个鸿沟去窥探更底层的一些原理。
有感而发,今天就回顾下intel官方开发发布的x64汇编知识,做一个简单的回顾,也为后面研究gomonkey指令patching等等做一些准备和铺垫。
介绍
大家使用x86汇编来写一些对性能比较敏感的程序嗯,这个情况已经持续很多年了嗯,但是现在32位机器应逐渐被64位机器取代了,对应的汇编代码也发生了变化。这篇文章主要就是介绍x64汇编的,如果不了解x86汇编也没什么大碍,当然了解的话理解起来会更简单一点。
x64是一个通用的名字,它表示的是对Intel以及AMD 32位指令集架构的一个64位扩展。AMD首先引入了x64指令集,最初叫x86-64,后面又改成了AMD64。Intel呢,将其支持64位指令集的架构称之为IA-32e,后面又改成了EMT64。这两个版本之间有一点细微的不兼容的地方,但是大部分指令在两个版本上都可以很好的工作,相关的细节可以参考Intel开发手册Intel 64 And IA-32 Architectures Software Developer’s Manuals,以及AMD64架构的技术文档。我们将这两个版本的交集部分称之为x64。不要将x64与64位Intel Itanium架构(称之为IA-64)混为一谈。
这篇文章没有涉及硬件相关的细节,如caches、分支预测,以及其他高级话题。文章最后会给出一些这些领域的参考手册供了解更多。
汇编语言,往往会用来编写对性能要求比较苛刻的程序或其中的一部分。但是对大部分普通程序员来说,与其让其写汇编,还不如写cc++然后配上一个好的编译器来的实在,后者编译器优化的性能可能比其写出的汇编代码质量更高。汇编语言对于调试代码也是有用的,有时一个编译器可能生成了一些不正确的汇编指令,通过调试器在程序中单步调试可以帮助定位到问题的原因。代码优化器,有时也会犯错。汇编的另外一个用途,你可以用它来研究没有源码的程序。反汇编让你能够改变、修复现有的可执行程序(推荐下几个工具hopper or cutter)。如果你想了解或者调查为什么某种编程语言比较慢,其他的比较快之类的问题,汇编也是你的好帮手。最后吧,掌握汇编知识,对于诊断一些恶意软件,也是必不可少的技能。
架构
当要去学习特定平台的汇编时,首先应该学习的是,该平台的寄存器集合。
通用架构
64位寄存器允许容纳更大的尺寸的数据,或者是地址,所以我们定义的更多的类型,将1个字节byte定义成8bits,将1个字word定义成16bits,将一个双字double word定义成32bits,将一个四字quadword定义成64位,将一个八字double quadword定义成128bits。关于字节序的问题,Intel是小端字节序,意味着低有效位存储在内存的低地址中。
上图显示了16个64bits的通用目的寄存器,前8个被命名成rax、rbx、rcx、rdx、rbp、rsi、rdi、rsp,这个命名和历史原因有关系,后面8个被命名成了r8~r15。如果前8个自己存器名,将字符r换成e,就变成了对应的地位的32位寄存器,比如rax的低32位是eax。类似地,如果想访问低16位,就直接把前缀去掉,如AX就是访问的rax的低16位,如果低8位呢,那就是AL了,AH就是次低8位(8~15位)。新加的8个寄存器r8~r15可以用类似的方式来访问低位数据,如r8(qword),r8d(lower dword),r8w(lowest word)、r8b(lowest byte MASM风格,intel风格是r8l)。注意没有r8h这种表示法。
使用REX操作码前缀去访问新添加的这8个通用寄存器的字节时,有一些限制,不能像访问之前的8个通用寄存器一样通过AH、BH、CH、DH来访问,并且一次只能访问一个(如R11B),但是可以使用AL、BL、CL、DL,为啥来,因为它就是强制要求将AH、BH、CH、DH转换成BPL、SPL、DIL、SIL来使用。
64位指令指针寄存器RIP,指向下一条要执行的指令的低质,并且支持64位平坦内存模型,当前操作系统中的内存地址布局将在后面提及。
栈指针寄存器RSP,指向当前刚push进栈的元素空间地址,也就是栈顶了,栈从高地址向低地址方向增长。栈用来存储调用例程(函数)的返回值、传递参数,或者用以支持ABI中的调用惯例(如保存调用方现场)。
RFLAGS寄存器,用来存储一些标识信息,它用来标识一些操作的结果(如是否溢出、运算结果的正负等)或者控制处理器的执行。这在x86 32位寄存器EFLAGS中就已经形成了这些,现在在以前基础上又添加了高32位,用来预留支持扩展,当前是没有使用的。下表列出了最常使用的一些flags。大多数其他flags是用于操作系统级别的任务。
Symbol | Bit | Name | Set if… |
---|---|---|---|
CF | 0 | Carry | Operation generated a carry or borrow |
PF | 2 | Parity | Last byte has even number of 1’s, else 0 |
AF | 4 | Adjust | Denotes Binary Coded Decimal in-byte carry |
ZF | 6 | Zero | Result was 0 |
SF | 7 | Sign | Most significant bit of result is 1 |
OF | 11 | Overflow | Overflow on signed operation |
DF | 10 | Direction | Direction string instructions operate (increment or decrement) |
ID | 21 | Identification | Changeability denotes presence of CPUID instruction |
浮点运算单元(FPU,Floating Point Unit)包含了8个寄存器FPR0-FPR7,还有状态寄存器、控制寄存器,以及其他的几个寄存器。FPR0-7这几个寄存器,每个都可以存储下表中列出的数据类型的值。浮点操作遵从IEEE 754标准。注意,大多数c/c++编译器支持32位和64位的float、double数据类型,但是没有支持80位的浮点数据类型,但是汇编是支持的。这8个寄存器和另外8个MMX?寄存器实际上是共享的同一组物理寄存器。
Data Type | Length | Precision (bits) | Decimal digits Precision | Decimal Range |
---|---|---|---|---|
Single Precision | 32 | 24 | 7 | 1.1810^-38 to 3.4010^38 |
Double Precision | 64 | 53 | 15 | 2.23 10^-308 to 1.7910^308 |
Extended Precision | 80 | 64 | 19 | 3.3710^-4932 to 1.1810^4932 |
有几个8位指令支持二进制编码的十进制(BCD),浮点寄存器支持的奇特格式还提供了一种80位,17位的BCD类型。
不确定是否翻译有误,原文:Binary Coded Decimal (BCD) is supported by a few 8-bit instructions, and an oddball format supported on the floating point registers gives an 80 bit, 17 digit BCD type.
这16个128bits的XMM寄存器(比x86多了8个)后面会有更详细介绍。
还有就是,段寄存器(在x64下大多数没有用)、控制寄存器、内存管理寄存器、调试寄存器、虚拟化寄存器、性能寄存器(跟踪记录各种类型的内部参数,如cache命中、miss,分支预测命中、miss,微码执行,定时等等)。最突出的性能相关的操作码就是RDTSC,它是用来技术处理器时钟周期的,经常通过它来测量一小段代码的执行耗时。通常c库里面提供的函数gettimeofday是比较耗时间的,所以在高频使用的时候会有性能问题,一般在网络框架里面做定时器、时间测量相关的任务,是会通过RDTSC来推送系统时间进而推算耗时的(在我的文章libmill定时器中也有提及,hitzhangjie.gitbook.io/libmill)。
更多其他细节信息,可以参考全5卷 “Intel 64 And IA-32 Architectures Software Developer’s Manuals”,可以从这里免费下载,http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html。
SIMD架构
单指令多数据(SIMD)指令对多条数据并行执行一条命令,这是汇编例程的常用用法。 MMX和SSE命令(分别使用MMX和XMM寄存器)支持SIMD操作,该操作可并行处理多达八段数据。例如,可以使用MMX在一条指令中将八个字节与另外八个字节相加。
八个64位MMX寄存器MMX0-MMX7是FPR0-7的别名,这意味着任何将FPR和MMX混合起来操作的代码都必须小心,不要覆盖所需的值。 MMX指令对整数类型进行操作,允许对MMX寄存器中的值并行执行字节,字和双字操作。大多数MMX指令以“P”开头表示“packed”。算术,移位/旋转,比较,例如:PCMPGTB表示“比较packed的的有符号1字节整数是否大于”。
16个128位XMM寄存器允许每条指令对四个单精度或两个双精度值进行并行运算。一些指令还适用于压缩字节,字,双字和四字整数。这些称为流式SIMD扩展(SSE)的指令具有多种形式:SSE,SSE2,SSE3,SSSE3,SSE4,以及在本文印制之时可能还会更多。英特尔已经宣布了这些扩展,称为英特尔®高级矢量扩展(Intel®AVX),具有新的256位宽数据路径。 SSE指令包含对浮点和整数类型的移动,算术,比较,重排和拆包以及按位运算。指令名称包括诸如PMULHUW和RSQRTPS之类的。最后,SSE引入了一些有关内存预取(出于性能)和内存屏障(出于多线程安全)的指令。
下表列出了一些命令集,操作的寄存器类型,并行操作的项目数以及项目类型。例如,使用SSE3和128位XMM寄存器,您可以并行处理2个(必须为64位)浮点值,或者并行处理16个(必须为字节大小)整数值。
为了找到给定芯片支持的技术,有一条CPUID指令返回特定于处理器的信息。
Technology | Register size/type | Item type | Items in Parallel |
---|---|---|---|
MMX | 64 MMX | Integer | 8, 4, 2, 1 |
SSE | 64 MMX | Integer | 8,4,2,1 |
SSE | 128 XMM | Float | 4 |
SSE2/SSE3/SSSE3… | 64 MMX | Integer | 2,1 |
SSE2/SSE3/SSSE3… | 128 XMM | Float | 2 |
SSE2/SSE3/SSSE3… | 128 XMM | Integer | 16,8,4,2,1 |
工具
assemblers
互联网搜索显示了具有x64功能的汇编程序,例如Netwide汇编程序NASM,在NASM基础上重写的YASM,快速的Flat Assembler FASM和传统的Microsoft MASM。甚至还有一个免费的用于x86和x64程序集的IDE,称为WinASM。每个汇编程序对其他汇编程序的宏和语法都有不同的支持,并不是完全兼容的。
对于以下示例,我使用平台SDK中免费提供的MASM的64位版本ML64.EXE。对于以下示例,请注意,MASM语法的格式为:“指令 目标操作数或地址,源操作数或地址”,有些汇编器中的语法中的源操作、目的操作的顺序是反着的。请参考对应汇编器的语法说明。
c/c++ compilers
C/C++编译器通常允许使用内联汇编将汇编嵌入代码中,但是Microsoft Visual Studio C/C++为x64代码删除了该汇编,这可能简化了代码优化器的工作。剩下两个选择:使用单独的汇编文件和外部汇编器,或使用头文件“ intrn.h”中的内在函数(请参见Birtolo和MSDN)。其他编译器具有类似的选项。
使用启发式的理由:
- x64中不支持内联汇编了;
- 方便使用,你可以使用变量名,来代替对寄存器的手动分配;
- 启发式相比写汇编而言更容易实现跨平台,编译器会针对不同的平台做对应的启发式优化处理;
- 配合启发式操作,优化器工作的更好;
例如,Microsoft Visual Studio 2008就有启发式操作,unsigned short _rotr16(unsigned short_rot16 b, unsigned char c)
,这个操作将一个16位操作数b中向右rotate c位,并返回结果。使用c来实现的话,可以这么写unsigned short a1 = (b>>c)|(b<<(16-c))
,这个汇编完成后大约是15条指令(debug模式下,如果是release模式下的话也差不太多),但是如果使用启发式操作unsigned short a1 = _rotr16(b,c)
的话呢,汇编完成后只有4条指令,你说哪个更牛逼呢?!
指令基础
寻址模式
在学习之前,得先了解下寻址模式,寻址模式指明了指令访问寄存器或者内存的方式 ,以下是常见的几种寻址模式:
立即数寻址(immediate):操作数就在指令中,如
ADD EAX, 14 ;将操作数14与32位寄存器EAX中值相加并存储到EAX中
寄存器寻址(register to register):操作数就在寄存器中,如
ADD R8L, AL ;将AL中的值与R8L中的值相加
间接寻址(indirect):就是指令中给出的不是操作数本身,也不是操作数本身所在的地址,而是存储操作数地址的地址,甚至有可能出现多重间址的情况。这样的寻址中允许使用8,16,32位偏移量,或者任何通用目的寄存器来作为基地址或者索引,也允许使用1,2,4,8来对索引进行乘积运算。也可以为其加上段前缀,如FS:, GS:等,但是比较少使用。下面是一个示例,
MOV R8W, 1234[8*RAX+RCX] ;将地址8*RAX+RCX+1234处的一个word移动到R8W
,这种方式常用来访问结构体数组中的成员,1234往往是数组起始地址,8表示数组元素大小,RAX表示数组索引,RCX表示结构体字段相对结构体起始地址的偏移量。这种寻址方式,起始有很多种写法了,下面这些都是等价的。
MOV ECX, dword ptr table[RBX][RDI] MOV ECX, dword ptr table[RDI][RBX] MOV ECX, dword ptr table[RBX+RDI] MOV ECX, dword ptr [table+RBX+RDI]
这里的dword ptr告诉汇编器如何编码MOV指令。
RIP相对寻址:这是x64中新加的寻址模式,它允许访问相对当前指令地址某偏移量出的数据,使得实现位置无关的代码更加容易了。如
MOV AL,[RIP] ;RIP指向下一条待执行指令的低质,aka NOP NOP
。可是,并不是所有汇编器都支持这种操作,MASM就不支持,但是FASM、YASM支持。MASM隐式地嵌入了RIP相对寻址,如MOV EAX, TABLE ;使用RIP相对寻址来获取表地址
。其他比较特殊的寻址:有些操作码使用寄存器的方式比较不一样,例如,有符号整数除操作IDIV,128位操作数RDX:RAX除以一个64位的操作数,会将商存储到RAX中,将余数存储到RDX中。
指令集
下表列出了一些比较常见的指令,其中*表示改指令有多个操作码,*表示后缀的意思:
Opcode | Meaning | Opcode | Meaning |
---|---|---|---|
MOV | Move to/from/between memory and registers | AND/OR/XOR/NOT | Bitwise operations |
CMOV* | Various conditional moves | SHR/SAR | Shift right logical/arithmetic |
XCHG | Exchange | SHL/SAL | Shift left logical/arithmetic |
BSWAP | Byte swap | ROR/ROL | Rotate right/left |
PUSH/POP | Stack usage | RCR/RCL | Rotate right/left through carry bit |
ADD/ADC | Add/with carry | BT/BTS/BTR | Bit test/and set/and reset |
SUB/SBC | Subtract/with carry | JMP | Unconditional jump |
MUL/IMUL | Multiply/unsigned | JE/JNE/JC/JNC/J* | Jump if equal/not equal/carry/not carry/ many others |
DIV/IDIV | Divide/unsigned | LOOP/LOOPE/LOOPNE | Loop with ECX |
INC/DEC | Increment/Decrement | CALL/RETCall | subroutine/return |
NEG | Negate | NOP | No operation |
CMP | Compare | CPUID | CPU information |
一个常见的指令就是LOOP指令,它将RCX,ECX或者CX的值减去1,然后如果结果不是0的话,就执行跳转,下面是个示例:
XOR EAX, EAX ; zero out eax
MOV ECX, 10 ; loop 10 times
Label: ; this is a label in assembly
INX EAX ; increment eax
LOOP Label ; decrement ECX, loop if not 0
不太常见的操作码可实现字符串操作,重复指令前缀,端口I / O指令,标志设置/清除/测试,浮点操作(通常以F开头,并支持move from一个整数、move to一个整数,算术,比较,先验,代数移入/移出)以及控制功能),用于多线程和性能问题的缓存和内存操作码等。英特尔®64和IA-32体系结构软件开发人员手册第2卷分为两部分,详细介绍了每个操作码。
操作系统
从理论上讲,64位系统允许寻址2^64字节的数据,但是当前没有芯片允许访问所有16 EB字节(18,446,744,073,709,551,616字节)。例如,AMD体系结构仅使用地址的低48位,并且48至63位必须是47位的副本,否则处理器会引发异常。因此,地址为0到00007FFFFFFFFFFF,从FFFF800000000000到FFFFFFFFFFFFFFFF,总共有256 TB(281,474,976,710,656字节)的可用虚拟地址空间。另一个缺点是,要寻址所有64位内存,需要更多的页表供OS存储,需要使用宝贵的内存。请注意,这些是虚拟地址,而不是物理地址。
结果,许多操作系统使用此空间的上半部分,从顶部开始,然后向下扩展;而用户程序则使用下半部分,从底部开始,然后向上扩展。当前的Windows *版本使用44位寻址(16 TB = 17,592,186,044,416字节)。结果地址如下图所示。由于地址是由OS分配的,因此结果地址对用户程序而言不太重要,但是用户地址和内核地址之间的区别对于调试很有用。
最后一个与OS相关的问题与多线程编程有关,但是此主题太大,无法在此处讨论。唯一要提到的是,有内存屏障操作码可帮助保护共享资源不受破坏。
调用约定
每种架构都有自己的例程(函数)调用的一些约束,操作系统与对应架构的CPU打交道都必须要考虑如何传递对应的参数、如何获取返回值的问题,这里的具体到某个平台的约束,就称为调用约定。
常见的x64调用约定是用于C样式函数调用的Microsoft 64调用约定(请参阅MSDN,Chen和Pietrek)。在Linux下,也将其称为应用程序二进制接口(ABI)。
请注意,此处涉及的调用约定与x64 Linux系统上使用的约定不同:对于Microsoft x64调用约定,附加的寄存器空间使fastcall成为唯一的调用约定(在x86下有很多:stdcall,thiscall,fastcall,cdecl等)。与C / C ++样式函数接口的规则:
- RCX, RDX, R8, R9 are used for integer and pointer arguments in that order left to right.
- XMM0, 1, 2, and 3 are used for floating point arguments.
- Additional arguments are pushed on the stack left to right.
- Parameters less than 64 bits long are not zero extended; the high bits contain garbage.
- It is the caller’s responsibility to allocate 32 bytes of “shadow space” (for storing RCX, RDX, R8, and R9 if needed) before calling the function.
- It is the caller’s responsibility to clean the stack after the call.
- Integer return values (similar to x86) are returned in RAX if 64 bits or less.
- Floating point return values are returned in XMM0.
- Larger return values (structs) have space allocated on the stack by the caller, and RCX then contains a pointer to the return space when the callee is called. Register usage for integer parameters is then pushed one to the right. RAX returns this address to the caller.
- The stack is 16-byte aligned. The “call” instruction pushes an 8-byte return value, so the all non-leaf functions must adjust the stack by a value of the form 16n+8 when allocating stack space.
- Registers RAX, RCX, RDX, R8, R9, R10, and R11 are considered volatile and must be considered destroyed on function calls.
- RBX, RBP, RDI, RSI, R12, R14, R14, and R15 must be saved in any function using them.
- Note there is no calling convention for the floating point (and thus MMX) registers.
- Further details (varargs, exception handling, stack unwinding) are at Microsoft’s site.
我们再看下Linux man手册,这里整理了两张调用约定相关的表: https://man7.org/linux/man-pages/man2/syscall.2.html,读后可以加深我们对调用约定 or ABI的认识。
## 示例
### MessageBox
前面讲这么多,现在用上面讲过的内容来写一个demo展示下x64汇编的使用,第一个demo是一个x64独立可运行的程序,运行之后会弹出一个Windows MessageBox。
```asm
; Sample x64 Assembly Program
; Chris Lomont 2009 www.lomont.org
extrn ExitProcess: PROC ; external functions in system libraries
extrn MessageBoxA: PROC
.data
caption db '64-bit hello!', 0
message db 'Hello World!', 0
.code
Start PROC
sub rsp,28h ; shadow space, aligns stack
mov rcx, 0 ; hWnd = HWND_DESKTOP
lea rdx, message ; LPCSTR lpText
lea r8, caption ; LPCSTR lpCaption
mov r9d, 0 ; uType = MB_OK
call MessageBoxA ; call MessageBox API function
mov ecx, eax ; uExitCode = MessageBox(...)
call ExitProcess
Start ENDP
End
将上述汇编程序保存为hello.asm,然后使用ML64进行编译,在Microsoft Windows x64 SDK中有这个程序的,这么编译:
ml64 hello.asm /link /subsystem:windows /defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Start
执行完成后会构建出一个可执行程序(已经链接好库函数、启动代码了),运行这个程序hello.exe,就会看到弹出一个消息窗口。
第二个示例,是在Visual Studio 2008这个IDE中,在C/C++文件中引用一个x64汇编文件中的代码,还记得Visual Studio后续删除了对支持内联汇编的支持吧。好。
- Create a new empty C++ console project. Create a function you’d like to port to assembly, and call it from main.
- To change the default 32-bit build, select Build/Configuration Manager.
- Under Active Platform, select New…
- Under Platform, select x64. If it does not appear figure out how to add the 64-bit SDK tools and repeat.
- Compile and step into the code. Look under Debug/Windows/Disassembly to see the resulting code and interface needed for your assembly function.
- Create an assembly file, and add it to the project. It defaults to a 32 bit assembler which is fine.
- Open the assembly file properties, select all configurations, and edit the custom build step.
- Put command line
ml64.exe /DWIN_X64 /Zi /c /Cp /Fl /Fo $(IntDir)\$(InputName).obj $(InputName).asm j:w
ok,下面开始,我们先写一个c++文件,如下,main里面会调用两个函数CombineC、CombineA先后打印出计算的结果,实际上我们准备让CombineA和CombineC实现完全一致的逻辑,区别就是CombineA是在外部的汇编文件中实现的。
// C++ code to demonstrate x64 assembly file linking
#include <iostream>
using namespace std;
double CombineC(int a, int b, int c, int d, int e, double f)
{
return (a+b+c+d+e)/(f+1.5);
}
// NOTE: 这里必须加上extern "C"来阻止C++ name mangling,否则连接的时候会出现符号解析错误
extern "C" double CombineA(int a, int b, int c, int d, int e, double f);
int main(void)
{
cout << "CombineC: " << CombineC(1,2,3,4, 5, 6.1) << endl;
cout << "CombineA: " << CombineA(1,2,3,4, 5, 6.1) << endl;
return 0;
}
好的,下面继续写汇编文件:
file: CombineA.asm
.code
PUBLIC CombineA
CombineA PROC
ADD ECX, DWORD PTR [RSP+28H] ; add overflow parameter to first parameter
ADD ECX, R9D ; add other three register parameters
ADD ECX, R8D ;
ADD ECX, EDX ;
MOVD XMM0, ECX ; move doubleword ECX into XMM0
CVTDQ2PD XMM0, XMM0 ; convert doubleword to floating point
MOVSD XMM1, realVal ; load 1.5
ADDSD XMM1, MMWORD PTR [RSP+30H] ; add parameter
DIVSD XMM0, XMM1 ; do division, answer in xmm0
RET ; return
CombineA ENDP
End
编译并运行上述程序,会发现输出了两次1.97368,第一次是CombineC的运算结果,第二次就是汇编实现的CombineA的运算结果
总结
这是对x64汇编编程的必要的简要介绍。下一步是浏览《英特尔®64和IA-32架构软件开发人员手册》。第1卷包含体系结构的详细信息,如果您知道汇编的话,这是一个很好的开始。其他地方是汇编书籍或在线汇编教程。为了了解代码的执行方式,指导您在调试器中逐步执行代码,查看反汇编,直到您可以阅读汇编代码以及您喜欢的语言为止,这对您很有帮助。对于C / C ++编译器,调试版本比发行版本更容易阅读,因此请确保从此处开始。最后,阅读masm32.com上的论坛以获取大量材料。
参考内容
- 原文地址: https://software.intel.com/content/www/us/en/develop/articles/introduction-to-x64-assembly.html
- NASM: http://www.nasm.us/
- YASM: http://www.tortall.net/projects/yasm/
- Flat Assembler (FASM): http://www.flatassembler.net/
- “Intel® 64 and IA-32 Architectures Software Developer’s Manuals,” available online at http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
- “Compiler Intrinsics”, available online at http://msdn.microsoft.com/en-us/library/26td21ds.aspx
- Matt Pietrek, “Everything You Need To Know To Start Programming 64-Bit Windows Systems”, available online at http://msdn.microsoft.com/en-us/magazine/cc300794.aspx, 2009.
- Intel® 64 and IA-32 Architectures Software Developer Manuals