对于本文
2018年的第二天Meltdown和Spectre破绽在计算机界如同放了一个核弹,IT码农们都炸开了锅,各大厂商的各路大神都在挑灯夜战的做各种测试和打补丁,各路技术爱好者也纷纷在微信群中热烈探讨破绽的技术问题和研读论文。
本文是猫王大神(Xiao Grangrong)同学花了一个周末工夫,参考了各路的论文和材料撰写进去,心愿对喜爱技术的各位朋友能有一丁点的帮忙,感激猫王大神的精彩文章。
本文作者简介:
Xiao Guangrong:Linux内核、KVM/QEMU社区外围开发者和维护者。
注释
2018年或者注定就是不平庸的一年,这一年刚开始就爆进去两个硬件设计级别的破绽,其影响之深令人咋舌。破绽之一是Meltdown,目前发现Intel CPU和ARM Cortex A75受影响。其次是Spectre,其影响了简直全副支流CPU包含Intel,AMD,ARM (IBM CPU是否受影响还未知)。破绽爆出之后,简直所有媒体都在做铺天盖地的报道,科技公司也在颁布各自的解决方案和修改日期。然而因为技术背景参差不齐,有些报道没有说到点子上,令人哭笑不得。
本文的次要材料来源于各论文3与以及相干的Blog1,其次补充了这些材料中有所疏忽或者是互相冲突的中央。因为篇幅起因,在这一篇文章里次要剖析了Meltdown。在后续的文章里再来剖析Spectre。
- 背景常识
在深入分析Meltdown之前,咱们须要理解一些背景常识。它包含CPU Cache,CPU指令执行,操作系统地址空间隔离的设计。接下来咱们顺次来看这些知识点。
1.1 CPU Cache
古代处理器执行指令的瓶颈曾经不在CPU端,而是在内存拜访端。因为CPU的处理速度要远远大于物理内存的访问速度,所以为了加重CPU期待数据的工夫,在古代处理器设计中都设置了多级的cache单元。
图1 经典处理器的存储构造
如图1所示,一个领有2个CPU的零碎, 每个CPU有两个Core, 每个Core有两个线程的Cache架构。每一个Core有独自的L1 cache, 它由其中的线程所共享, 每一个CPU中的所有Core共享同一个L2 Cache和L3 Cache。
L1 cache最靠近处理器外围,因而它的访问速度也是最快的,当然它的容量也是最小的。CPU拜访各级的Cache速度和提早是不一样的,L1 Cache的提早最小,L2 Cache其次,L3 Cache最慢。
上面是Xeon 5500 Series的各级cache的拜访提早:(依据CPU主频的不同,1个时钟周期代表的工夫也不一样,在1GHz主频的CPU下,一个时钟周期大略是1纳秒,在2.1GHz主频的CPU下,拜访L1 Cache也就2个纳秒)。
表1:各级存储构造的拜访提早
拜访类型 提早
L1 cache命中 约4个时钟周期
L2 cache 命中 约10个时钟周期
L3 cache命中 约40个时钟周期
拜访本地DDR 约60 纳秒
拜访远端内存节点DDR 约100纳秒
如表1所示,咱们能够看到各级内存拜访的提早有很大的差别。CPU拜访一块新的内存时,它会首先把蕴含这块内存的Cache Line大小的内容获取到L3 Cache,而后是载入到L2 Cache,最初载入到了L1 Cache。这个过程须要拜访主存储器,因而提早会很大,大概须要几十纳秒。当下次再读取雷同一块数据的时候间接从L1 Cache里取数据的话,这个提早大概只有4个时钟周期。当L1 Cache满了并且有新的数据要进来,那么依据Cache的置换算法会抉择一个Cache line置换到L2 Cache里,L3 Cache也是同样的情理。
1.2 cache攻打
咱们曾经晓得同一个CPU上的Core共享L2 cache和L3 Cache, 如果内存曾经被缓存到CPU cache里, 那么同一个CPU的Core就会用较短的工夫取到内存里的内容, 否则取内存的工夫就会较长。两者的工夫差别非常明显(大概有300个CPU时钟周期), 因而攻击者能够利用这个工夫差别来进行攻打。
来看上面的示例代码:
1: clflush for user_probe[]; // 把user_probe_addr对应的cache全副都flush掉
2: u8 index = (u8 ) attacked _mem_addr; // attacked_mem_addr寄存被攻打的地址
3: data = user_probe_addr[index * 4096]; // user_probe_addr
寄存攻击者能够放拜访的基地址
user_probe_addr[]是一个攻击者能够拜访的, 255 * 4096 大小的数组。
第1行, 把user_probe_addr数组对应的cache全副革除掉。
第2行, 咱们设法拜访到attacked_mem_addr中的内容. 因为CPU权限的爱护, 咱们不能间接获得外面的内容, 然而能够利用它来造成咱们能够察看到影响。
第3行, 咱们用拜访到的值做偏移, 以4096为单位, 去拜访攻击者有权限拜访的数组,这样对应偏移处的内存就能够缓存到CPU cache里。
这样, 尽管咱们在第2行处拿到的数据不能间接看到, 然而它的值对CPU cache曾经造成了影响。
接下来能够利用CPU cache来间接拿到这个值. 咱们以4096为单位顺次拜访user_probe_addr对应内存单位的前几个字节, 并且测量这该次内存拜访的工夫, 就能够察看到工夫差别, 如果拜访工夫短, 那么能够揣测访内存曾经被cache, 能够反推出示例代码中的index的值。
在这个例子里, 之所以用4096字节做为拜访单位是为了防止内存预读带来的影响, 因为CPU在每次从主存拜访内存的时候, 依据局部性原理, 有可能将邻将的内存也读进来。Intel的开发手册上指明 CPU的内存预取不会跨页面, 而每个页面的大小是4096。
Meltdown[3]论文中给出了他们所做试验的后果, 援用如下:
图2 cache攻打数据比照
据此, 他们反推出index的值为84。
1.3 指令执行
经典处理器架构应用五级流水线:取指(IF)、译码(ID)、执行(EX)、数据内存拜访(MEM)和写回(WB)。
古代处理器在设计上都采纳了超标量体系结构(Superscalar Architecture)和乱序执行(Out-of-Order)技术,极大地提高了处理器计算能力。超标量技术可能在一个时钟周期内执行多个指令,实现指令级的并行,无效进步了ILP(InstructionLevel Parallelism)指令级的并行效率,同时也减少了整个cache和memory层次结构的实现难度。
在一个反对超标量和乱序执行技术的处理器中,一条指令的执行过程被合成为若干步骤。指令首先进入流水线(pipeline)的前端(Front-End),包含预取(fetch)和译码(decode),通过散发(dispatch)和调度(scheduler)后进入执行单元,最初提交执行后果。所有的指令采纳程序形式(In-Order)通过前端,并采纳乱序的形式进行发射,而后乱序执行,最初用程序形式提交后果。若是一条存储读写指令最终后果更新到LSQ(Load-StoreQueue)部件。LSQ部件是指令流水线的一个执行部件,能够了解为存储子系统的最高层,其上接管来自CPU的存储器指令,其下连贯着存储器子系统。其次要性能是将来自CPU的存储器申请发送到存储器子系统,并解决其下存储器子系统的应答数据和音讯。
图3 经典x86处理器架构
如图3所示,在x86微处理器经典架构中,指令从L1指令cache中读取指令,L1指令cache会做指令加载、指令预取、指令预解码,以及分支预测。而后进入Fetch& Decode单元,会把指令解码成macro-ops微操作指令,而后由Dispatch部件散发到Integer Unit或者Float Point Unit。Integer Unit由Integer Scheduler和Execution Unit组成,Execution Unit蕴含算术逻辑单元(arithmetic-logic unit,ALU)和地址生成单元(address generation unit,AGU),在ALU计算实现之后进入AGU,计算无效地址结束后,将后果发送到LSQ部件。LSQ部件首先依据处理器零碎要求的内存一致性(memory consistency)模型确定拜访时序,另外LSQ还须要解决存储器指令间的依赖关系,最初LSQ须要筹备L1 cache应用的地址,包含无效地址的计算和虚实地址转换,将地址发送到L1 DataCache中。
1.4 乱序执行(out-of-order execution)
方才提到了古代的处理器为了进步性能,实现了乱序执行(Out-of-Order,OOO)技术。在古老的处理器设计中,指令在处理器外部执行是严格依照指令编程程序的,这种处理器叫做程序执行的处理器。在程序执行的处理器中,当一条指令须要拜访内存的时候,如果所须要的内存数据不在Cache中,那么须要去拜访主存储器,拜访主存储器的速度是很慢的,那么这时候程序执行的处理器会进行流水线执行,在数据被读取进来之后,而后流水线才持续工作。这种工作形式大家都晓得肯定会很慢,因为前面的指令可能不须要等这个内存数据,也不依赖以后指令的后果,在期待的过程中能够先把它们放到流水线下来执行。所以这个有点像在火车站排队买票,正在买票的人发现钱包不见了,正在焦急找钱,可是前面的人也必须停下来等,因为不能插队。
1967年Tomasulo提出了一系列的算法来实现指令的动静调整从而实现乱序执行,这个就是驰名的Tomasulo算法。这个算法的外围是实现一个叫做寄存器重命名(Register Rename)来打消寄存器数据流之间依赖关系,从而实现指令的并行执行。它在乱序执行的流水线中有两个作用,一是打消指令之间的寄存器读后写相干(Write-after-Read,WAR)和写后写相干(Write-after-Write,WAW);二是当指令执行产生例外或者转移指令猜想谬误而勾销前面的指令时,可用来保障现场的准确。其思路为当一条指令写一个后果寄存器时不间接写到这个后果寄存器,而是先写到一个两头寄存器过渡,当这条指令提交时再写到后果寄存器中。
通常处理器实现了一个对立的保留站(reservationstation),它容许处理器把曾经执行的指令的后果保留到这里,而后在最初指令提交的时候会去做寄存器重命名来保障指令程序的正确性。
如图3所示,经典的X86处理器中的“整数重命名”和“浮点重命名”部件(英文叫做reorder buffer,简称ROB),它会负责寄存器的调配、寄存器重命名以及指令抛弃(retiring)等作用。
x86的指令从L1 指令cache中预取之后,进入前端解决局部(Front-end),这里会做指令的分支预测和指令编码等工作,这里是程序执行的(in-order)。指令译码的时候会把x86指令变成泛滥的微指令(uOPs),这些微指令会依照程序发送到执行引擎(Execution Engine)。执行引擎这边开始乱序执行了。这些指令首先会进入到重命名缓存(ROB)里,而后ROB部件会把这些指令经由调度器单元产生到各个执行单元(Execution Unit,简称EU)里。假如有一条指令须要拜访内存,这个EU单元就进行期待了,然而前面的指令不须要停顿下来等这条指令,因为ROB会把前面的指令发送给闲暇的EU单元,这样就实现了乱序执行。
如果用高速公路要做比喻的话,多发射的处理器就像多车道一样,汽车不须要依照发车的程序在高速公路上按程序执行,它们能够随便超车。一个形象的比喻是,如果一个汽车抛锚了,前面的汽车不须要排队等待这辆汽车,能够超车。
在高速公里的起点设置了一个很大的停车场,所有的指令都必须在停车场里等待,而后停车场里有设置了一个进口,所有指令从这个进口进来的时候必须依照指令本来的程序,并且指令在进口的时候必须进行写寄存器操作。这样从进口的角度看,指令就是依照原来的逻辑程序一条一条进来并且写寄存器。
这样从处理器角度看,指令是程序发车,乱序超车,程序离队。那么这个停车场就是ROB,这个缓存机制能够称为保留站(reservation station),这种乱序执行的机制就是人们常说的乱序执行。
1.5 地址空间
古代的处理器为了实现CPU的过程虚拟化,都采纳了分页机制,分页机制保障了每个过程的地址空间的隔离性。分页机制也实现了虚拟地址到物理地址的转换,这个过程须要查问页表,页表能够是多级页表。那么这个页表除了实现虚拟地址到物理地址的转换之外还定义了拜访属性,比方这个虚构页面是只读的还是可写的还是可执行的还是只有特权用户能力拜访等等权限。
每个过程的虚拟地址空间都是一样的,然而它映射的物理地址是不一样的,所以每一个过程都有本人的页表,在操作系统做过程切换的时候,会把下一个过程的页表的基地址填入到寄存器,从而实现过程地址空间的切换。以外,因为TLB里还缓存着上一个过程的地址映射关系,所以在切换过程的时候须要把TLB对应的部份也革除掉。
当过程在运行的时候不可避免地须要和内核交互,例如零碎调用,硬件中断。当陷入到内核后,就须要去拜访内核空间,为了防止这种切换带来的性能损失以及TLB刷新,古代OS的设计都把用户空间和内核空间的映射放到了同一张页表里。这两个空间有一个显著的分界线,在Linux Kernel的源码中对应PAGE_OFFSET。
图4 过程地址空间
如图4所示,尽管两者是在同一张页表里,然而他们对应的权限不一样,内核空间部份标记为仅在特权层能够拜访,而用户空间部份在特权层与非特权层都能够拜访。这样就完满地把用户空间和内核空间隔离开来:当过程跑在用户空间时只能拜访用户空间的地址映射,而陷入到内核后就即能够拜访内核空间也能够拜访用户空间。
对应地,页表中的用户空间映射部份只蕴含本机程能够拜访的物理内存映射,而任意的物理内存都有可能会被映射到内核空间局部。
1.5 异样解决
CPU指令在执行的过程过有可能会产生异样,然而咱们的处理器是反对乱序执行的,那么有可能异样指令前面的指令都曾经执行了,那怎么办?
咱们从处理器外部来考查这个异样的产生。操作系统为了解决异样,有一个要求就是,当异样产生的时候,异样产生之前的指令都曾经执行实现,异样指令前面的所有指令都没有执行。然而咱们的处理器是反对乱序执行的,那么有可能异样指令前面的指令都曾经执行了,那怎么办?
那么这时候ROB就要起到清道夫的作用了。从之前的介绍咱们晓得乱序执行的时候,要批改什么货色都通过两头的寄存器临时记录着,等到在ROB排队进来的时候才真正提交批改,从而保护指令之间的程序关系。那么当一条指令产生异样的时候,它就会带着异样“宝剑”来到ROB中排队。ROB按程序把之前的失常的指令都提交发送进来,当看到这个带着异样“宝剑”的指令的时候,那么就启动应急预案,把进口封闭了,也就是异样指令和其前面的指令会被抛弃掉,不提交。
然而,为了保障程序执行的正确性,尽管异样指令前面的指令不会提交,可是因为乱序执行机制,前面的一些访存指令曾经把物理内存数据预取到cache中了,这就给Meltdown破绽留下来前面,尽管这些数据会最终被抛弃掉。
2.Meltdown剖析
上面咱们对Meltdown破绽做一些原理性的剖析和后续修补的计划。
2.1 破绽剖析
了解了上述的背景常识当前就能够来看Meltdown是怎么回事了. 咱们再回过头看看下面的例子:
1: clflush for user_probe[]; // 把user_probe_addr对应的cache全副都flush掉
2: u8 index = (u8 ) attacked _mem_addr; // attacked_mem_addr寄存被攻打的地址
3: data = user_probe_addr[index * 4096]; // user_probe_addr寄存攻击者能够放拜访的基地址
如果attached_mem_addr位于内核, 咱们就能够利用它来读取内核空间的内容。
如果CPU程序执行, 在第2行就会发现它拜访了一个没有权限地址, 产生page fault (缺页异样), 进而被内核捕捉, 第3行就没有机会运行。可怜的是, CPU会乱序执行, 在某些条件满足的状况下, 它获得了attacked _mem_addr里的值, 并在CPU将该指令标记为异样之前将它传递给了下一条指令, 并且随后的指令利用它来触发了内存拜访。在指令提交的阶段CPU发现了异样,再将曾经乱序执行指令的后果抛弃掉。这样尽管没有对指令的正确性造成影响, 然而乱序执行产生的CPU cache影响仍然还是在那里, 并能被利用。
该场景有个前置条件,该条件在Meltdown[3]的论文里没有被提到,但在cyber[1] 的文章指出,attached_mem_addr必须要曾经被缓存到了 CPU L1,因为这样才会有可能在CPU将指令标记为异样之前指数据传给后续的指令。 并且cyber 指出,只有attacked_mem_addr曾经被缓存到CPU L1 才有可能胜利,在L2,L3均不行,其理由是:
“The L1 Cache is a so called VIPT or Virtually Indexed, PhysicallyTagged cache. This means the data can be looked up by directly using thevirtual address of the load request”
“If the requested data was not found in the L1 cache the load must bepassed down the cache hierarchy. This is the point where the page tables comeinto play. The page tables are used to translate the virtual address into aphysical address. This is essentially how paging is enabled on x64. It isduring this translation that privileges are checked”
这几点理由很值得商讨:
1) AMD的开发手册[10]没有找到L1 cache是VIPT的证据,Intel的开发手册[9]上只能从” L1 Data Cache Context Mode”的形容上揣测NetBurst架构的L1 cache是VIPT的。
2) 就算L1 cache 是 VIPT,那也须要取得physicaladdress,必然会用到TLB里的内容或者进行页表的遍历。
那么如何来将要被攻打的内存缓存到L1里呢? 有两种办法:
1) 利用零碎调用进入内核。如果该零碎调用的门路拜访到了该内存,那么很有可能会将该内存缓存到L1 (在footprint不大于L1大小的状况下)。
2) 其次是利用prefetch指令。 有钻研[8]显示,Intel的prefetch指令会齐全疏忽权限查看,将数据读到cache。
咱们晓得,如果过程触发了不可修复的page fault,内核会向其发送SIGSEGV信号,而不能持续往下执行。所以这里有两种操作方法,其一是创立一个子过程,在子过程中触发上述的代码拜访,而后在父过程中去测算user_probe_addr[]的拜访工夫。 所以每一次探测都须要另起一个新过程,这样会影响效率。
另一种办法是利用Intel的事务内存解决(Intel®Transactional Synchronization Extensions),该机制以事务为单元来对一系列内存操作做原子操作,如果一个事务内的内存操作全副胜利实现且没有其它CPU造成内存的竞争,那么就会将该事务对应的后果进行提交,否则将中断该事务。如果咱们将上述代码蕴含到一个内存事务中,对被攻打地址的拜访并不会造成pagefault,只会被打断事务。 这样咱们能够在不须要生成子过程的条件下继续进行攻打。
这里有一个很有意思的景象,上述代码在第2行处读到的index有时会全为0,不同的材料有不同的解释:
1) cyber[1]给出的解释是: “Fortunately Idid not get a slow read suggesting that Intel null’s the result when the access is notallowed”。
2) google zero project[2]给出的解释是: “That (read from kernel address returns all-zeroes) seems to happen for memory that is not sufficiently cached but for which pagetable entries are present, at least after repeated read attempts. For unmapped memory, the kernel address read does not return a result at all.”
3) Meltdown paper[3]给出的解释是: “If the exception is triggered whiletrying to read from an inaccessible kernel address,the register where the data shouldbe stored,appears to bezeroed out. This is reasonable because if the exception is unhandled,the user space application isterminated,and the valuefrom the inaccessible kernel address could be observed in the register contentsstored in the core dump of the crashed process. The direct solution to fix thisproblem is to zero out the corresponding registers. If the zeroing out of theregister is faster than the execution of the sub- sequent instruction (line 5in Listing 2),the attackermay read a false value in the third step”。
这个解释比拟有意思,他们首先认为,将读到的内容清零是有必要的,否则读到的内容会保留到这个程序的core dump里。 果真会如此么? Terminate 过程和生产core dump都须要OS去做,软件不可能间接拜访到乱序执行所拜访的寄存器。
其次他们认为,该问到0是因为值传递给下一条指令的速度要慢于将值清0的操作,所以他们的解决办法是:
“prevent the tran- sientinstruction sequence from continuing with a wrong value,i.e.,‘0’,Meltdown retries reading the address until itencounters a value different from ‘0’”
所以他们的示例代码是长这样的:
图片
他们在读到0时现再重试. 然而, 如果真的读到清0的数据, retry并不会有机会再被执行到, 因为此时很有可能异样曾经被捕捉。
2.2 破绽修复
在破绽被报告给相干厂商后,各OS和开源社区开始了修复工作,LinuxKernel采纳的是Kernelpage-table isolation (KPTI)6,据说Windows和Mac OS的修复也是相似的思路。
在后面的背景常识中看到, 以后的OS采纳用户空间和内核空间分段的设计, 这样使得Kernel和Usersapce应用同一张页表, 位于同一个TLB context中, 所以CPU在做预取和乱序的时候能够应用TLB中的cache做地址转换, 进而取得CPU Cache中的数据, 如果咱们可能让用户空间不能应用TLB中对于内核地址映射的信息, 这样就能够断掉用户空间对Cache中kernel数据的拜访,这也是KPTI的思路。
KPTI将之前OS设计中, 每个过程应用一张页表分隔成了两张, kernelspace和userspace应用各自拆散的页表。咱们暂且称过程在kernel模式应用的页表称为Kernel页表, 相应地过程在用户空间应用的页表被称为用户页表。
具体地来说, 当过程运行在用户空间时, 应用的是用户页表, 当产生中断或者是异样时, 须要陷入到内核, 进入内核空间后, 有一小段内核跳板将页表切换到内核页表, 当过程从kernel空间跳回到用户空间时, 页表再次被切换回用户页表。
Kernel页表蕴含了过程用户空间地址的映射和Kernel应用的内存映射, 所以Kernel仍然能够应用以后过程的内存映射。用户页表仅仅蕴含了用户空间的内存映射以及内核跳板的内存映射。
2.3 性能影响
从这里能够看到, 每一次用户空间到内核空间的切换都须要切换页表, 在没有PCID反对的CPU上, 切换页表 (reload CR3) 会flush除了global page以外的所有TLB。在反对PCID的状况下, 大部分利用场景的性能损失微不足道。
(上面这段是编辑增加的)
援用几大科技公司的原话能够看出性能影响微不足道:
Apple: “Our testing with public benchmarks has shown that the changes in the December 2017 updates resulted in no measurable reduction in the performance of macOS and iOS as measured by the GeekBench 4 benchmark, or in common Web browsing benchmarks such as Speedometer, JetStream, and ARES-6.” (苹果示意在macOS和iOS上没发现有性能损失)
Microsoft: “The majority of Azure customers should not see a noticeable performance impact with this update. We’ve worked to optimize the CPU and disk I/O path and are not seeing noticeable performance impact after the fix has been applied.”(微软示意在Azure中没看到性能影响)
Amazon: “We have not observed meaningful performance impact for the overwhelming majority of EC2 workloads.”(亚马逊说在EC2利用场景中没察看到性能损失)
Google: “On most of our workloads, including our cloud infrastructure, we see negligible impact on performance.”(谷歌示意在它们绝大部分的利用场景中包含云设施,都只是微不足道的性能影响)
- 总结
在这里须要指出的是, 也是所有paper没有提到的, 尽管在以后的OS的设计中, 过程在内核空间和用户空间应用的是同一张页表, 然而该页表的内核部份映射的生成是on-demand的, 即在拜访的时候才会被逐步映射,因而只有在零碎调用上被touch到的内存才有可能被映射到页表里。所以被严格限度零碎调用的用户攻打难度会更大一些, 例如应用seccomp,而后尽管如此, 所有的代码门路不可能齐全被审计到。
Meltdown破绽并不需要利用已有的软件缺陷, 仅仅只需攻击者和受害者在只有一个地址空间中就会有影响, 比方基于container的Docker、 Xen的PV guest等等。基于硬件虚拟化的VM并不会受其影响,然而状况不容乐观, 接下来要剖析的Spectre具备更大范畴的破坏力。
尽管这篇文章是以cache为例来形容攻打, 然而对体系体构可察看到的影响都能够拿来作为攻打的伎俩, 比方诱发CPU算术单元的忙碌运算后再来观突某条算术指令执行的工夫, 再如察看不同状况下的电力耗费等等。
这一次破绽的影响之大足以被写进教科书, 甚至会影响接下来所有硬件和OS的设计, 2018年或者是OS, Hardware, Security的新起点。
参考资料
[1] Negative Result:Reading Kernel Memory From User Modehttps://cyber.wtf/2017/07/28/...
[2] Google Zero Projecthttps://googleprojectzero.blo...
[3] Meltdownhttps://meltdownattack.com/me...
[4] Spectre Attacks:Exploiting Speculative Execution https://spectreattack.com/spe...
[5] KAISER: hiding thekernel from user space https://lwn.net/Articles/738975/
[6] The current state ofkernel page-table isolation https://lwn.net/Articles/741878/
[7] Kernel page-tableisolation https://en.wikipedia.org/wiki...
[8] Prefetch Side-ChannelAttacks: Bypassing SMAP and Kernel ASLR https://gruss.cc/files/prefet...
[9] Intel® 64 and IA-32architectures software developer’s manualhttps://software.intel.com/si...
[10] AMD64 ArchitectureProgrammer’s Manual https://developer.amd.com/res...