关于asm:HW24-ECS-50-PrairieLearn

HW2.4 - ECS 50 | PrairieLearn What To SubmiteditDist.sPromptTranslate the given program, editDist.c , into an assembly program, editDist.s, that is capable of finding the edit distance between two strings that are up to 100 characters long.Inputstring1: The first string to use in the edit distance calculation Label: string1Space: 101 bytesstring2: The second string to use in the edit distance calculationLabel: string2 Space: 101 bytesOutputThe edit distance between string1 and string2 should be placed in EAX.Making Sure Your Code Works With The GraderAFTER the last line of code that you wish to be executed in your program please place the label done:.Make sure that there is an instruction after the done labelAfter the last instruction you should have at least one blank line.In general the .text section of your program should look like thisNotice that there is a blank line after the nop ...

February 16, 2024 · 4 min · jiezi

关于asm:关于IN指令和OUT指令

IN 指令通过指定的端口号输出数据(数据进入到cpu的寄存器)OUT指令则是把CPU寄存器中存储的数据输入到指定端口号的端口(数据从cpu的寄存器进去)I/O 控制器中有用于长期保留输入输出数据的内存。这个内存就是端口(port)IO控制器外部的内存,也被称为寄存器,用于长期存储数据的。(在IO控制器的内存即寄存器的地址就是端口)一个I/O控制器能够管制多个设施,各端口之间通过端口号进行辨别。端口号也被称为I/O地址。IN 指令和OUT指令在端口号指定的端口和CPU之间进行数据的输出和输入。

November 4, 2022 · 1 min · jiezi

关于asm:汇编语言-常用汇编指令

movaddjmploop

August 28, 2022 · 1 min · jiezi

关于asm:汇编语言-寄存器

罕用的寄存器 寄存器具体介绍 案例示范

August 28, 2022 · 1 min · jiezi

关于asm:ldr-特殊用法

0x104481078 <+4>: ldr x16, #0x7358 示意从以后地址0x104481078+0x7358获取值寄存到x16寄存器,通过memory read能够验证此论断 反思:不晓得为什么我在网上搜寻都没搜寻到ldr x16, #0x7358这种写法是什么意思,所以遇到不意识的汇编,能够通过xcode调试反汇编本人验证

July 13, 2022 · 1 min · jiezi

关于asm:TransformASM牛刀小试

问题之前始终在听他们说函数插桩,字节码插桩,ASM,总感觉很牛逼很高大上,晓得一个大略意思,就是Java文件编译成字节码,批改字节码,达到批改函数的目标,那么明天就尝试一个Demo级别的工程,实现APP打包,插入本人的代码,并且通过Plugin插件的形式实现。 什么是Transform,怎么自定义Transform?Transform的作用周期是在哪里呢?在打包的哪一个阶段呢?ASM工具是干嘛用的呢?实在场景当初APP的奔溃是一个很失常不过的问题,为了放大影响范畴,因为每一秒对互联网来说都是钱啊,也就有了很多的热修复框架,很多都用到了ASM技术,在打包的时候批改class文件,注入本人的逻辑达到本人的目标。 举个栗子: 失常逻辑的代码,是间接返回字符串的长度,然而没有判空,可能就会有空指针异样,为了平安,一些框架,会在APP打包,class2dex的时候,去烦扰class文件,批改字节码,在每个函数的函数体减少if-else的操作,没有异样的时候走失常的逻辑,crash的值就是false,如果产生了奔溃,就会走到修复的逻辑,从而防止奔溃。然而这个注入是怎么操作的呢?这个就设计到ASM字节码插桩了。 // 这个失常逻辑的代码public int getStringLength(String name){ return name.length;}// 批改字节码之后的代码public int getStringLength(String name){ if(crash){ // 奔溃之后的解决逻辑 ... }else{ return name.length; }}什么是TransformGradle从1.5开始,内置了Transform的API,咱们能够通过插件,在class2dex的时候,对字节码文件进行操作,实现字节码插桩或者代码注入。每一个Transform都是一个工作,都是一个Task,他们是链式构造的,咱们只须要实现Transform的接口,并且实现注册,这些Transform就会通过TaskManager串联,每一个的输入都会是下一个输出,顺次执行。 Transform外围办法getName() 指定以后transform的名称 isIncremental() 以后transform是否反对增量编译,增量编译能够放慢编译速度。 getInputTypes 指定以后transform要解决的数据类型,能够不只一种类型。比方本地class文件,资源文件等。 getScopes() 指定以后transform的作用域,比方只解决以后我的项目,只解决jar包等,很好了解。 TransformInvocation外围办法getInputs() 返回输出文件,一般来说咱们关怀的是DirectoryInput和JarInput,前者指的是咱们源码形式参加编译的代码,后者就是Jar包形式参加编译的代码了。 getOutputProvider() 获取输入,能够取得输入的门路。 Transform应用注册Transform Transform逻辑指标,在源码的每个办法中,插入System.out.println()输入代码。 模板代码 public class LogTransform extends Transform { @Override public String getName() { // 名称 return getClass().getSimpleName(); } @Override public Set<QualifiedContent.ContentType> getInputTypes() { // 须要解决的数据类型 return TransformManager.CONTENT_CLASS; } @Override public Set<? super QualifiedContent.Scope> getScopes() { // 作用范畴 return TransformManager.SCOPE_FULL_PROJECT; } @Override public boolean isIncremental() { // 是否反对增量编译 return true; } @Override public void transform(TransformInvocation transformInvocation) throws IOException { boolean incremental = transformInvocation.isIncremental(); // 获取输入,如果没有上一级的输出,输入可能也就是空的 TransformOutputProvider outputProvider = transformInvocation.getOutputProvider(); // 如果不反对增量编译,须要把之前生成的都删除掉,不缓存复用 if (!incremental) { outputProvider.deleteAll(); } // 当然工作也能够放在并发的线程池进行,期待工作完结 for (TransformInput input : transformInvocation.getInputs()) { // 解决Jar Collection<JarInput> jarInputs = input.getJarInputs(); if (jarInputs != null && jarInputs.size() > 0) { for (JarInput jarInput : jarInputs) { processJarFile(jarInput, outputProvider, incremental); } } // 解决source Collection<DirectoryInput> directoryInputs = input.getDirectoryInputs(); if (directoryInputs != null && directoryInputs.size() > 0) { for (DirectoryInput directoryInput : directoryInputs) { processDirFile(directoryInput, outputProvider, incremental); } } } } ...外围逻辑办法梳理这份办法是Transform的必须要实现的办法,咱们能够从TransformInvocation获取输出,以及输入的门路,去做一些咱们本人的逻辑操作,执行转换。这里的逻辑很简略,就是获取输出,而后获取DirectoryInput源码,JarInput的jar包,进行转换,比方批改字节码。 ...

April 18, 2022 · 4 min · jiezi

关于asm:硬核乘以-001-和除以-100-哪个快

重要提醒:不想看汇编代码间接看后果的,请滚动到文章开端的后果局部 在知乎上看到这个问题,感觉挺乏味的。上面的答复形形色色,然而就是没有间接给出真正的benchmark后果的。还有间接搬反汇编代码的,只不过汇编里用了 x87 FPU 指令集,天那这都 202x 年了真的还有程序用这个老掉牙的浮点运算指令集的吗? 我也决定钻研一下,读反汇编,写 benchmark。平台以 x86-64 为准,编译器 clang 12,开编译器优化(不开优化谈速度无意义) 代码及反汇编https://gcc.godbolt.org/z/rvT9nEE9Y 简略汇编语言科普在深刻反汇编之前,先要对汇编语言有简略的理解。本文因为原始代码都很简略,甚至没有循环和判断,所以波及到的汇编指令也很少。 汇编语言与平台强相干,这里以 x86-64(x86的64位兼容指令集,因为被AMD最先创造,也称作AMD64)为例,简称x64x64汇编语言也有两种语法,一种为 Intel 语法(次要被微软平台编译器应用),一种为 AT&T 语法(是gcc兼容编译器的默认语法,然而gcc也反对输入intel 语法)。个人感觉 Intel 语法更易懂,这里以 Intel 语法为例根本语法。例如 mov rcx, rax:mov 是指令名“赋值”。rcx 和 rax 是 mov 指令的两个操作数,他们都是通用寄存器名。Intel 汇编语法,第一个操作数同时被用于存储运算后果。所以: mov rcx, rax,赋值指令,将寄存器 rax 中的值赋值给寄存器 rcx。翻译为C语言代码为 rcx = raxadd rcx, rax,加法指令,将寄存器 rcx 和 rax 的值相加后,后果赋值给 rcx。翻译为 C 语言代码为 rcx += rax寄存器。编译器优化后,少数操作都间接在寄存器中操作,不波及内存拜访。下文只波及三类寄存器(x64平台)。 以 r 打头的 rxx 是 64 位寄存器以 e 打头的 exx 是 32 位寄存器,同时就是同名 64 位 rxx 寄存器的低 32 位局部。xmmX 是 128 位 SSE 寄存器。因为本文不波及 SIMD 运算,能够简略的将其当做浮点数寄存器。对于双精度浮点数,只应用寄存器的低 64 位局部调用约定。C语言个性,所有代码都依附于函数,调用函数时父函数向子函数传值、子函数向父函数返回值的形式叫做函数调用约定。调用约定是保障应用程序 ABI 兼容的最根本要求,不同的平台。不同的操作系统有不同的调用约定。本文的反汇编代码都是应用 godbolt 生成的,godbolt 应用的是 Linux 平台,所以遵循 Linux 平台通用的 System V 调用约定 调用约定。因为本文波及到的代码都非常简单(都只有一个函数参数),读者只须要晓得三点: ...

December 5, 2021 · 2 min · jiezi

关于asm:Ubuntu系统下汇编语言环境配置

阐明1、零碎:Ubuntu codists@pc:~$ lsb_release -aNo LSB modules are available.Distributor ID: UbuntuDescription: Ubuntu 21.10Release: 21.10Codename: impish2、工夫:2021年11月14日 3、教材:《汇编语言》(第4版,作者:王爽) 装置DOSBOX$ sudo apt install -y dosbox装置后果验证: $ dosbox 下载Debug等程序这里不一一列举下载地址了,已将相干的程序压缩到 DEBUG.zip,大家能够间接下载。下载后放到任意一个目录,如“~/Desktop/masm”。 codists@pc:~$ cd ~/Desktop/masmcodists@pc:~/Desktop/masm$ lltotal 1792drwxrwxr-x 2 codists codists 4096 11月 14 21:05 ./drwxr-xr-x 3 codists codists 4096 11月 7 20:53 ../-rw-rw-r-- 1 codists codists 143 11月 7 22:10 1.asm-rw-rw-r-- 1 codists codists 527 11月 9 22:49 1.EXE-rw-rw-r-- 1 codists codists 66 11月 9 22:48 1.OBJ-rw-rw-r-- 1 codists codists 0 10月 2 11:12 2.asm-rw-rw-r-- 1 codists codists 109 11月 14 21:05 2x2.asm-rw-rw-r-- 1 codists codists 118 11月 11 21:15 3.ASM-rw-rw-r-- 1 codists codists 20634 4月 14 2008 DEBUG.EXE-rw-rw-r-- 1 codists codists 72174 5月 13 1998 EDIT.COM-rw-rw-r-- 1 codists codists 3050 4月 6 2001 EXE2BIN.EXE-rw-rw-r-- 1 codists codists 49661 5月 2 2011 LIB.EXE-rw-rw-r-- 1 codists codists 69133 4月 6 2001 LINK.EXE-rw-rw-r-- 1 codists codists 103175 4月 6 2001 MASM.EXE-rw-rw-r-- 1 codists codists 1474560 4月 7 2009 masm.IMg将masm目录映射为虚构C盘 ...

November 15, 2021 · 1 min · jiezi

关于asm:直播预告阿里云服务网格-ASM-产品易用性改善实践与思考

一、直播主题《服务网格 ASM 产品易用性改善实际与思考》 二、直播工夫9月26日 16:00-17:00 三、直播嘉宾阿里云高级技术专家,李云(至简) 四、直播简介为了最终共建寰球事实标准,阿里云服务网格 ASM 产品抉择了以开源的 Istio 为根底进行能力加强和产品化。Istio 面向微服务治理的将来做了很好的概念形象而功能强大,也因为功能强大带来了易用性问题。 用户通过 YAML 文件运维服务网格不仅技术门槛高,而且很容易出错。本次直播将分享过来几个月 ASM 在产品易用性上的工作进展和背地的思考。 五、立刻预约点击链接(https://live.bilibili.com/23021247),或扫描海报二维码关注阿里云云原生B站直播间(本课程将在阿里云云原生视频号同步直播,敬请关注~)

September 18, 2021 · 1 min · jiezi

关于asm:汇编格式-ATT-与-Intel

汇编格局 AT&T 与 Intel《CSAPP》中为 AT&T 格局,《汇编语言 王爽》中为 Intel 格局前言机器指令是用二进制代码示意的 CPU 能够间接辨认和执行的一种指令系统的汇合,不同的 CPU 架构有不同的机器指令。汇编指令是机器指令便于记忆的书写格局,汇编指令编写实现后通过汇编器将其翻译成机器指令供 CPU 执行,因而,汇编器的性能是将汇编指令翻译成机器指令 同一条机器指令能够用不同的汇编指令表白,确保汇编器执行时无谬误即可。不同的汇编指令格局衍生出不同的汇编语法且都有一个与之对应的汇编器。 随着计算机的倒退,不同厂家造成了自成一派的汇编语言,并有本人的汇编器。不同的汇编语言,实现雷同的机器指令的语法可能不统一 常见的汇编器有: GAS (GNU Assembler),应用AT&T语法格局MASM (Microsoft Macro Assembler),应用Intel语法格局NASM (Netwide Assembler),应用的语法格局与Intel相似,然而更简略FASM (Flat Assembler)GAS的AT&T的语法格局查问 MASMT的Intel语法格局查问 语法格局寄存器名AT&T 中寄存器名要加前缀%,而 Intel 则不须要。例如: pushl %eax # AT&T 格局push eax # Intel 格局立刻操作数AT&T 中用$前缀示意一个立刻数,而 Intel 不必带任何前缀。例如: pushl $1 # AT&Tpush 1 # Intel操作方向AT&T 与 Intel 中的源操作数和指标操作数的地位正好相同。 AT&T 指标操作数在源操作数的左边,Intel 指标操作数在源操作数的右边。例如: addl $1,%eax # AT&Tadd eax,1 # Intel操作字长AT&T 操作数的字长由操作数的最初一个字母决定,后缀以及示意字长如下: b:byte,8 比特(bit)w:word,16比特l:long,32比特Intel 操作数的字长用byte ptr和word ptr等指令前缀来示意。例如: ...

September 5, 2021 · 3 min · jiezi

关于asm:汇编语言中的指令系统

8086/8088 共有 92 条指令,正确理解和把握每一条指令的性能和格局,是汇编语言程序设计的根底。 一、指令系统概述(一)指令分类92 条指令形成了 8086/8088 指令系统,它们可分为六大类。 (1)传送类指令(Transfer instructions)。 (2)算术运算类指令(Arithmetic instructions)。 (3)位操作类指令(Bit manipulation instructions)。 (4)串操作类指令(String instructions)。 (5)程序转移类指令(Program transfer instructions)。 (6)处理器管制类指令(Processor Control instructions)。 (二)指令格局8086/8088 指令系统中的指令有 3 种格局。 (1)双操作数指令:OPR DEST,SRC (2)单操作数指令:OPR DEST (3)无操作数指令:OPR 其中,OPR 是指令操作符,也称为指令助记符,它示意指令要执行何种操作。双操作数指令指定两个操作数,第一个为目标操作数,第二个为源操作数,两操作数之间要用逗号“,”分隔,它们的地位不能调换,操作的后果个别在目标操作数中。因而,目标操作数不能是常数,而只能是寄存器操作数或存储器操作数。语句执行后,目标操作数原来的内容将产生扭转,而源操作数的内容不会扭转。 单操作数指令只须要一个操作数,它或是源操作数(SRC),或是目标操作数(DEST)。 对于无操作数指令,尽管指令自身未指明操作数是什么、在哪里,但指令却隐含规定了操作数及寄存地点。 (三)指令规定8086/8088 指令在应用时有较严格的规定,要正确地把握和使用这些指令,首先要精确地把握这些规定。8086/8088 汇编语言指令独特恪守如下规定。 (1)规定 1:除通用数据传送指令(MOV、PUSH、POP)之外,段寄存器不容许作为操作数。 (2)规定 2:段寄存器不能间接用立刻数赋值。 (3)规定 3:代码段寄存器 CS 和立刻数不能作为目标操作数。 (4)规定 4:指令中两个操作数不能同时为段寄存器。 (5)规定 5:指令中两个操作数不能同时为存储器操作数(串指令除处)。 (6)规定 6:指令中两个操作数的类型(字节类型或字类型等)必须统一。 (7)规定 7:指令中至多要有一个操作数的类型是明确的,否则须用操作符PTR长期指定操作数类型。 上面介绍 8086/8088 指令。 二、传送类指令传送类指令共有 12 条,包含通用数据传送指令(MOV、PUSH、POP)、替换指令(XCHG)、查表指令(XLAT)、地址传送指令(LEA、LDS、LES)和标记传送指令(PUSHT、POPT、LAHF、SAHF)。它们能够将各种类型的操作数从源传送到目标,其传送路径见图 1。图中实线示意非法传送路径,虚线为非法传送路径。所有的传送类指令对标记位均无影响。 图 1 数据的传送路径 (一)通用数据传送指令这种指令共有3条。 传送指令(Mov)格局:MOV DEST,SRC 性能:把源操作数的内容传送给目标操作数,即 DEST←SRC。 ...

June 4, 2021 · 6 min · jiezi

关于asm:汇编语言中的寻址方式

计算机的一条指令通常蕴含两局部: 其中,操作码规定了指令应实现的具体操作,在汇编语言中操作码用助记符示意,例如加法 ADD、传送 MOV,等等。操作数示意指令的操作对象,比方一条加法指令,操作数局部就要给出加数和被加数,它们可能寄存在不同的中央,或者在某一寄存器中,或者在存储器的某一存储单元中,指令要通过某种办法示意操作数是哪一种,操作数具体在哪里。 指令中提供操作数或操作数地址的办法叫寻址形式。8086/8088 CPU 各种指令中所需的操作数次要有 3 类:寄存器操作数(操作数在 CPU 的通用寄存器中),存储器操作数(操作数在内存的存储单元中),立刻数操作数(操作数是指令中给出的常数)。还有一类是输出/输入端口操作数(操作数在输出/输入的接口寄存器中),仅用在 IN 和 OUT 指令中。 上面按操作数的类型介绍 8086/8088 CPU 的寻址形式。 一、隐含寻址有的指令中没有明确的操作数字段,操作的对象隐含在指令代码中,这种指令的寻址形式称为隐含寻址或叫固定寻址。例如: DAA 这是一条十进制加法调整指令,尽管无操作数字段,但隐含规定是对寄存器 AL 的内容进行操作。 二、立刻寻址如果操作数是一个常数,就毋庸寻找,这个常数就蕴含在指令代码中,立刻能够失去这个操作数,因而把这种模式叫立刻寻址。把一个常数操作数叫立刻数,也是这个起因。 三、寄存器寻址如果指令要操作的数据在 CPU 外部的寄存器中,指令就能够间接书写这个寄存器名字示意这个操作数,利用这种提供操作数的办法叫寄存器寻址。例如: MOV AX,BX 这是一条传送指令,把寄存器 BX 中的内容传送至寄存器 AX。如果 BX=4258H,那么指令执行后 AX=4258H,而 BX 的值放弃不变。 又如:“ADD AX,1234H”是将 AX 的数与常数 1234H 相加,对于指标操作数(第一个操作数)是寄存器寻址,源操作数(第二个操作数)则为立刻寻址。 四、存储器操作数的寻址形式存储器操作数的寻址是一个如何在指令中给出该操作数在内存中寄存的存储单元地址问题。在程序中,一个存储单元的地址是采纳逻辑地址模式示意的,即: 段基值:偏移量 其中,段基值在某个段寄存器中。不同操作类别的指令主动对应不同的段寄存器,这是隐含约定的。如不想扭转这些约定,就毋庸在指令中给出段值。偏移量(又叫偏移地址或偏移值)示意了该存储单元与段起始地址之间的间隔(字节数),它须要在指令中通过某种模式的表达式给出,在对源程序进行汇编时,由汇编程序计算出表达式的值,这个由汇编程序计算出的操作数的偏移量叫作无效地址,用 EA 示意。 指令中给出存储器操作数地址的办法有两种。一种办法是间接给出操作数的偏移地址,这种办法叫间接寻址。这是最间接、简略的办法,但不便于拜访成组的数据。另一种办法是将操作数的偏移量放入某个寄存器中,将其作为地址指针,这种办法叫寄存器间接寻址。若地址指针的内容在程序运行期间进行批改,就能使得用该寻址形式的同一指令,能够对不同存储单元进行操作。上面具体讲述这些寻址形式。 (一)间接寻址这种寻址形式是在指令中间接给出存储器操作数的偏移地址。无效地址 EA 可间接由偏移地址失去。这种寻址形式次要用于存取简略变量。 在间接寻址形式的指令中的偏移量能够用常数或变量名示意。 (1)用常数示意。例如: MOV AX,DS:[100H] 该指令是把以后数据段偏移 100H 的字存储单元内容送至 AX。用常数示意时,段寄存器必须指明,不能缺省。 (2)用变量名示意。例如: MOV BX,VARMOV AH,DA+2第一条指令是把由变量名 VAR 所指的存储单元内容传送给 BX。第二条指令是把由变量名 DA 代表的地址偏移再加 2 的那个字节单元内容送给 AH。假如 VAR 的偏移量为 1000H,DA 的偏移量为 2000H,则上述两条指令等效为: ...

June 4, 2021 · 1 min · jiezi

关于asm:Flagger-on-ASM基于Mixerless-Telemetry实现渐进式灰度发布系列-3-渐进式灰度发布

简介: 作为CNCF成员,Weave Flagger提供了继续集成和继续交付的各项能力。Flagger将渐进式公布总结为3类: - 灰度公布/金丝雀公布(Canary):用于渐进式切流到灰度版本(progressive traffic shifting) - A/B测试(A/B Testing):用于依据申请信息将作为CNCF成员,Weave Flagger提供了继续集成和继续交付的各项能力。Flagger将渐进式公布总结为3类: 灰度公布/金丝雀公布(Canary):用于渐进式切流到灰度版本(progressive traffic shifting)A/B测试(A/B Testing):用于依据申请信息将用户申请路由到A/B版本(HTTP headers and cookies traffic routing)蓝绿公布(Blue/Green):用于流量切换和流量复制 (traffic switching and mirroring)本篇将介绍Flagger on ASM的渐进式灰度公布实际。 Setup Flagger1 部署Flagger执行如下命令部署flagger(残缺脚本参见:demo_canary.sh)。 alias k="kubectl --kubeconfig $USER_CONFIG"alias h="helm --kubeconfig $USER_CONFIG" cp $MESH_CONFIG kubeconfigk -n istio-system create secret generic istio-kubeconfig --from-file kubeconfigk -n istio-system label secret istio-kubeconfig istio/multiCluster=true h repo add flagger https://flagger.apph repo updatek apply -f $FLAAGER_SRC/artifacts/flagger/crd.yamlh upgrade -i flagger flagger/flagger --namespace=istio-system \ --set crd.create=false \--set meshProvider=istio \--set metricsServer=http://prometheus:9090 \--set istio.kubeconfig.secretName=istio-kubeconfig \--set istio.kubeconfig.key=kubeconfig2 部署Gateway在灰度公布过程中,Flagger会申请ASM更新用于灰度流量配置的VirtualService,这个VirtualService会应用到命名为public-gateway的Gateway。为此咱们创立相干Gateway配置文件public-gateway.yaml如下: ...

April 20, 2021 · 6 min · jiezi

关于asm:Flagger-on-ASM基于Mixerless-Telemetry实现渐进式灰度发布系列-2-应用级扩缩容

简介: 利用级扩缩容是绝对于运维级而言的。像监控CPU/内存的利用率就属于利用无关的纯运维指标,针对这种指标进行扩缩容的HPA配置就是运维级扩缩容。而像申请数量、申请提早、P99散布等指标就属于利用相干的,或者叫业务感知的监控指标。 本篇将介绍3种利用级监控指标在HPA中的配置,以实现利用级主动扩缩容。利用级扩缩容是绝对于运维级而言的。像监控CPU/内存的利用率就属于利用无关的纯运维指标,针对这种指标进行扩缩容的HPA配置就是运维级扩缩容。而像申请数量、申请提早、P99散布等指标就属于利用相干的,或者叫业务感知的监控指标。 本篇将介绍3种利用级监控指标在HPA中的配置,以实现利用级主动扩缩容。 Setup HPA1 部署metrics-adapter执行如下命令部署kube-metrics-adapter(残缺脚本参见:demo_hpa.sh)。: helm --kubeconfig "$USER_CONFIG" -n kube-system install asm-custom-metrics \ $KUBE_METRICS_ADAPTER_SRC/deploy/charts/kube-metrics-adapter \ --set prometheus.url=http://prometheus.istio-syste...执行如下命令验证部署状况: 验证PODkubectl --kubeconfig "$USER_CONFIG" get po -n kube-system | grep metrics-adapter asm-custom-metrics-kube-metrics-adapter-6fb4949988-ht8pv 1/1 Running 0 30s 验证CRDkubectl --kubeconfig "$USER_CONFIG" api-versions | grep "autoscaling/v2beta" autoscaling/v2beta1autoscaling/v2beta2 验证CRDkubectl --kubeconfig "$USER_CONFIG" get --raw "/apis/external.metrics.k8s.io/v1beta1" | jq . { "kind": "APIResourceList", "apiVersion": "v1", "groupVersion": "external.metrics.k8s.io/v1beta1", "resources": []}2 部署loadtester执行如下命令部署flagger loadtester: kubectl --kubeconfig "$USER_CONFIG" apply -f $FLAAGER_SRC/kustomize/tester/deployment.yaml -n testkubectl --kubeconfig "$USER_CONFIG" apply -f $FLAAGER_SRC/kustomize/tester/service.yaml -n test3 部署HPA3.1 依据利用申请数量扩缩容首先咱们创立一个感知利用申请数量(istio_requests_total)的HorizontalPodAutoscaler配置: ...

April 20, 2021 · 3 min · jiezi

关于asm:Flagger-on-ASM基于Mixerless-Telemetry实现渐进式灰度发布系列-1-遥测数据

简介: 服务网格ASM的Mixerless Telemetry技术,为业务容器提供了无侵入式的遥测数据。遥测数据一方面作为监控指标被ARMPS/prometheus采集,用于服务网格可观测性;另一方面被HPA和flaggers应用,成为利用级扩缩容和渐进式灰度公布的基石。 本系列聚焦于遥测数据在利用级扩缩容和渐进式灰度公布上的实际,将分三篇介绍遥测数据(监控指标)、利用级扩缩容,和渐进式灰度公布。序服务网格ASM的Mixerless Telemetry技术,为业务容器提供了无侵入式的遥测数据。遥测数据一方面作为监控指标被ARMPS/prometheus采集,用于服务网格可观测性;另一方面被HPA和flaggers应用,成为利用级扩缩容和渐进式灰度公布的基石。 本系列聚焦于遥测数据在利用级扩缩容和渐进式灰度公布上的实际,将分三篇介绍遥测数据(监控指标)、利用级扩缩容,和渐进式灰度公布。 总体架构本系列的总体架构如下图所示: ASM下发Mixerless Telemetry相干的EnvoyFilter配置到各ASM sidecar(envoy),启用利用级监控指标的采集。业务流量通过Ingress Gateway进入,各ASM sidecar开始采集相干监控指标。Prometheus从各POD上采集监控指标。HPA通过Adapter从Prometheus查问相干POD的监控指标,并依据配置进行扩缩容。Flagger通过Prometheus查问相干POD的监控指标,并依据配置向ASM发动VirtualService配置更新。ASM下发VirtualService配置到各ASM sidecar,从而实现渐进式灰度公布。 Flagger渐进式公布流程Flagger官网形容了渐进式公布流程,这里翻译如下: 探测并更新灰度Deployment到新版本灰度POD实例数从0开始扩容期待灰度POD实例数达到HPA定义的最小正本数量灰度POD实例衰弱检测由flagger-loadtester实例发动acceptance-test验证灰度公布在验证失败时终止由flagger-loadtester实例发动load-test验证在配置流量复制时开始从生产全流量复制到灰度每分钟从Prometheus查问并检测申请成功率和申请提早等监控指标灰度公布在监控指标不符预期的数量达到阈值时终止达到配置中迭代的次数后进行流量复制开始切流到灰度POD实例更新生产Deployment到新版本期待生产Deployment滚动降级结束期待生产POD实例数达到HPA定义的最小正本数量生产POD实例衰弱检测切流回生产POD实例灰度POD实例缩容至0发送灰度公布剖析后果告诉原文如下: With the above configuration, Flagger will run a canary release with the following steps: detect new revision (deployment spec, secrets or configmaps changes)scale from zero the canary deploymentwait for the HPA to set the canary minimum replicascheck canary pods healthrun the acceptance testsabort the canary release if tests failstart the load testsmirror 100% of the traffic from primary to canarycheck request success rate and request duration every minuteabort the canary release if the metrics check failure threshold is reachedstop traffic mirroring after the number of iterations is reachedroute live traffic to the canary podspromote the canary (update the primary secrets, configmaps and deployment spec)wait for the primary deployment rollout to finishwait for the HPA to set the primary minimum replicascheck primary pods healthswitch live traffic back to primaryscale to zero the canarysend notification with the canary analysis result前提条件已创立ACK集群,详情请参见创立Kubernetes托管版集群。已创立ASM实例,详情请参见创立ASM实例。Setup Mixerless Telemetry本篇将介绍如何基于ASM配置并采集利用级监控指标(比方申请数量总数istio_requests_total和申请提早istio_request_duration等)。次要步骤包含创立EnvoyFilter、校验envoy遥测数据和校验Prometheus采集遥测数据。 ...

April 20, 2021 · 2 min · jiezi

关于asm:CPU-提供了什么

为了不便了解,CPU 能够简略认为是: 一堆的寄存器,用于临时存放数据能够执行机器指令,实现运算 / 数据读写 等操作寄存器CPU 有很多的寄存器,这里咱们只介绍 指令寄存器 和 通用寄存器。 指令寄存器64 位下,指令寄存器叫 rip (32 位下叫 eip)。指令寄存器用于寄存下一条指令的地址,CPU 的工作模式,就是从 rip 指向的内存地址取一条指令,而后执行这条指令,同时 rip 指向下一条指令,如此循环,就是 CPU 的根本工作。 也就意味着,通常模式下 CPU 是依照程序执行指令的。然而,CPU 也有一些非凡的指令,用于间接批改 rip 的地址。比方,jmp 0xff00 指令,就是把 rip 改为 0xff00,让 CPU 接下来执行内存中 0xff00 这个地位的指令。 通用寄存器以 x86_64 来说,有 16 个“通用”寄存器。“通用”意味着能够放任意的数据,这 16 个寄存器并没有什么区别,然而实际上还是存在一些约定俗称的用法: 先看看这 8 个:(这是原来 32 位架构下就有的,只是 32 位下是 e 结尾的) rax: "累加器"(accumulator), 很多加法乘法指令的缺省寄存器,函数返回值个别也放在这里rbx: "基地址"(base)寄存器, 在内存寻址时寄存基地址rcx: 计数器(counter), 是反复(REP)前缀指令和 LOOP 指令的内定计数器rdx: 用来放整数除法产生的余数,或者读写I/O端口时,用来寄存端口号rsp: 栈顶指针,指向栈的顶部rbp: 栈底指针,指向栈的底部,通常用`rbp+偏移量`的模式来定位函数寄存在栈中的局部变量rsi: 字符串操作时,用于存放数据源的地址rdi: 字符串操作时,用于寄存目标地址的,和 rsi 常常搭配一起应用,执行字符串的复制等操作另外还有 8 个,是 64 位架构下新增的: ...

March 21, 2021 · 1 min · jiezi

关于asm:2020最新RabbitMQ图文教程通俗易懂版下内附视频链接

RabbitMQ音讯的事务机制 在应用RabbitMQ的时候,咱们能够通过音讯长久化操作来解决因为服务器的异样奔溃导致的音讯失落,除此之外咱们还会遇到一个问题,当音讯的发布者在将音讯发送进来之后,音讯到底有没有正确达到broker代理服务器呢?如果不进行非凡配置的话,默认状况下公布操作是不会返回任何信息给生产者的,也就是默认状况下咱们的生产者是不晓得音讯有没有正确达到broker的,如果在音讯达到broker之前曾经失落的话,长久化操作也解决不了这个问题,因为音讯基本就没达到代理服务器,你怎么进行长久化,那么这个问题该怎么解决呢? RabbitMQ为咱们提供了两种形式: 通过AMQP事务机制实现,这也是AMQP协定层面提供的解决方案;通过将channel设置成confirm模式来实现;AMQP事物机制管制 RabbitMQ中与事务机制无关的办法有三个:txSelect(), txCommit()以及txRollback(), txSelect()用于将以后channel设置成transaction模式,txCommit()用于提交事务,txRollback()用于回滚事务,在通过txSelect()开启事务之后,咱们便能够公布音讯给broker代理服务器了,如果txCommit()提交胜利了,则音讯肯定达到了broker了,如果在txCommit()执行之前broker异样解体或者因为其余起因抛出异样,这个时候咱们便能够捕捉异样通过txRollback()回滚事务。 SendTx.java try { // 通过工厂创立连贯 connection = factory.newConnection(); // 获取通道 channel = connection.createChannel(); // 开启事务 channel.txSelect(); // 申明队列 channel.queueDeclare(QUEUE_NAME, false, false, false, null); // 创立音讯 String message = "Hello World!"; // 将产生的音讯放入队列 channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8")); System.out.println(" [x] Sent '" + message + "'"); // 模拟程序异样 int i = 1 / 0; // 提交事务 channel.txCommit();} catch (IOException | TimeoutException e) { e.printStackTrace(); try { // 回滚事务 channel.txRollback(); } catch (IOException e1) { e1.printStackTrace(); }} 事务的确可能解决producer与broker之间音讯确认的问题,只有音讯胜利被broker承受,事务提交能力胜利,否则咱们便能够在捕捉异样进行事务回滚操作同时进行音讯重发,然而应用事务机制的话会升高RabbitMQ的性能,那么有没有更好的办法既能保障producer晓得音讯曾经正确送到,又能基本上不带来性能上的损失呢?从AMQP协定的层面看是没有更好的办法,然而RabbitMQ提供了一个更好的计划,行将channel信道设置成confirm模式。 ...

December 25, 2020 · 4 min · jiezi

关于asm:汇编开发环境搭建DOSBOX-MASM50

汇编开发环境搭建(DOSBOX + MASM5.0)一、工具装置次要工具: DOSBOX:传送门,windows玩家抉择0.74-3 Win32 installer即可。装置上根本就是一路Next,两头记得选一下装置目录。 MASM5.0:官网版本:传送门官网版本没有debug.exe,不能进行调试。这里放一个网友整顿的版本:传送门(举荐应用) 二、环境配置 呐,其实也没啥好配置的,关上就能用,上面演示一下: 应用演示开始演示之前呢先筹备一下资料,学习编程语言嘛,Hello World必定不能少,咱们新建一个hello.asm(能够先建一个hello.txt而后手动批改后缀),而后,放到masm5.0的门路下。hello.asm内容如下: assume cs:code,ds:data data segment str db 'HelloWorld!','$' data ends code segment mov ax,data mov ds,ax lea dx,str ; 获取str的偏移地址 mov ah,9 ; 输入字符串 int 21h mov ah,4ch int 21h code ends end程序来自这里:传送门 首先,运行DOSBOX装置目录下的”DOSBOX.exe“,会弹出两个窗口,一个是常见的windows命令行窗口(win下作为控制台利用运行的程序根本都会弹这个),用不到然而不要敞开。另一个是DOSBOX的窗口(有蓝色文字的那个),操作都在这进行。 为了可能应用masm中的那些程序(提供的指令),咱们须要把masm5.0的装置目录挂载到DOSBOX(并作为DOSBOX的C盘)。输出的指令如下: mount c: <masm5.0在你电脑上的地位,如“D://xxx/xxx/masm5.0”> (尖括号只是用来示意的,不要一起敲进去啊) 而后,输出c:并回车切换到DOSBOX的C盘(即masm所在目录),为了确认挂载是否失常,能够应用dir指令列出目录下的文件来查看,能够看到,挂载失常。 而后咱们就能够来编译、运行筹备好的测试程序了。 第一步,编译。输出masm hello并回车来编译源程序,之后会让你命名编译产生的文件,个别回车三次跳过即可。第二步,连贯。输出link hello并回车来链接编译产生的文件,之后也会让命名,回车三次跳过。第三步,输出hello.exe或者hello来运行第二步中生成的可执行文件,而后你就会看到··· Bingo! 优化 后面的应用过程中有个显著的槽点——masm的门路要手动挂载,有点麻烦,所以,让咱们来做点配置。 首先,关上DOSBOX装置目录下的“DOSBox 0.74-3 Options.bat”(最好先敞开DOSBOX),会关上一个名为“DOSBox 0.74-3 Options.conf”的文件。而后,拉到最初,能够看到如下的一段内容: [autoexec]# Lines in this section will be run at startup.# You can put your MOUNT lines here.正文通知咱们,写在[autoexec]所在行之后的内容会在DOSBOX启动当前被当作指令主动执行。那么,如果把挂载文件夹的指令写到这,当然就能实现“主动挂载”了。所以,咱们批改这一部分内容如下: ...

August 8, 2020 · 1 min · jiezi

彻底弄懂为什么不能把栈上分配的数组字符串作为返回值

背景最近准备从 C 语言零基础到 PHP 扩展开发实战,案例的过程中准备了如下代码碎片,演示解析http scheme #include <stdio.h>#include <stdlib.h>#include <string.h>char *parse_scheme(const char *url){ char *p = strstr(url,"://"); return strndup(url,p-url);}int main(){ const char *url = "http://static.mengkang.net/upload/image/2019/0907/1567834464450406.png"; char *scheme = parse_scheme(url); printf("%s\n",scheme); free(scheme); return 0;}上面是通过strndup的方式,背后也依托了malloc,所以最后也需要free。有人在微信群私信parse_scheme能用char []来做返回值吗?我们知道栈上的数组也能用来存储字符串,那我们可以改写成下面这样吗? char *parse_scheme(const char *url){ char *p = strstr(url,"://"); long l = p - url + 1; char scheme[l]; strncpy(scheme, url, l-1); return scheme;}大多数人都知道不能这样写,因为返回的是栈上的地址,当从该函数返回之后,那段栈空间的操作权也释放了,当再次使用该地址的时候,值就是不确定的了。 那我们今天就一起探讨下出现这样情况的背后的真正原理。 基础预备每个函数运行的时候因为需要内存来存放函数参数以及局部变量等,需要给每个函数分配一段连续的内存,这段内存就叫做函数的栈帧(Stack Frame)。因为是一块连续的内存地址,所以叫帧;为什么叫要加一个栈呢?想必大家都熟悉了函数调用栈,为什么叫函数调用栈呢?比如下面的表达式 array_values(explode(",",file_get_contents(...)));函数的执行顺序是最内层的函数最先执行,然后依次返回执行外层的函数。所以函数的执行就是利用了栈的数据结构,所以就叫栈帧。 x86_64 cpu上的 rbp 寄存器存函数栈底地址,rsp 寄存器存函数栈顶地址。 ...

October 13, 2019 · 2 min · jiezi

Go-函数调用-━-栈和寄存器视角

函数的调用过程主要要点在于借助寄存器和内存帧栈传递参数和返回值。虽然同为编译型语言,Go 相较 C 对寄存器和栈的使用有一些差别,同时,Go 语言自带协程并引入 defer 等语句,在调用过程上显得更加复杂。 理解Go函数调用在CPU指令层的过程有助于编写高效的代码,在性能优化、Bug排查的时候,能更迅速的确定要点。本文以简短的示例代码和对应的汇编代码演示了Go的调用过程,展示了不同数据类型的参数的实际传递过程,同时分析了匿名函数、闭包作为参数或者返回值传递时,在内存上的实际数据结构。对于协程对栈的使用和实现细节,本文不展开。 阅读本文需要掌握计算机体系结构基础知识(至少了解程序内存布局、栈、寄存器)、Go 基础语法。参考文档提供了这些主题更详细的知识。 以下: 术语栈:每个进程/线程/goroutine有自己的调用栈,参数和返回值传递、函数的局部变量存放通常通过栈进行。和数据结构中的栈一样,内存栈也是后进先出,地址是从高地址向低地址生长。栈帧:(stack frame)又常被称为帧(frame)。一个栈是由很多帧构成的,它描述了函数之间的调用关系。每一帧就对应了一次尚未返回的函数调用,帧本身也是以栈的形式存放数据的。caller 调用者callee 被调用者,如在 函数 A 里 调用 函数 B,A 是 caller,B 是 callee寄存器(X86) ESP:栈指针寄存器(extended stack pointer),存放着一个指针,该指针指向栈最上面一个栈帧(即当前执行的函数的栈)的栈顶。注意: ESP指向的是已经存储了内容的内存地址,而不是一个空闲的地址。例如从 0xC0000000 到 0xC00000FF是已经使用的栈空间,ESP指向0xC00000FFEBP:基址指针寄存器(extended base pointer),也叫帧指针,存放着一个指针,该指针指向栈最上面一个栈帧的底部。EIP:寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。注意:16位寄存器没有前缀(SP、BP、IP),32位前缀是E(ESP、EBP、EIP),64位前缀是R(RSP、RBP、RIP) 汇编指令 PUSH:进栈指令,PUSH指令执行时会先将ESP减4,接着将内容写入ESP指向的栈内存。POP :出栈指令,POP指令执行时先将ESP指向的栈内存的一个字长的内容读出,接着将ESP加4。注意: 用PUSH指令和POP指令时只能按字访问栈,不能按字节访问栈。CALL:调用函数指令,将返回地址(call指令的下一条指令)压栈,接着跳转到函数入口。RET:返回指令,将栈顶返回地址弹出到EIP,接着根据EIP继续执行。LEAVE:等价于 mov esp,ebp; pop ebp;MOVL:在内存与寄存器、寄存器与寄存器之间转移值LEAL:用来将一个内存地址直接赋给目的操作数注意:8位指令后缀是B、16位是S、32位是L、64位是Q 调用惯例 调用惯例(calling convention)是指程序里调用函数时关于如何传参如何分配和清理栈等的方案。一个调用惯例的内容包括: 参数是通过寄存器传递还是栈传递或者二者混合通过栈传递时参数是从左至右压栈还是从右至左压栈函数结果是通过寄存器传递还是通过栈传递调用者(caller)还是被调用者(callee)清理栈空间被调用者应该为调用者保存哪些寄存器例如,C 的调用惯例(cdecl, C declaration)是: 函数实参在线程栈上按照从右至左的顺序依次压栈。函数结果保存在寄存器EAX/AX/AL中浮点型结果存放在寄存器ST0中编译后的函数名前缀以一个下划线字符调用者负责从线程栈中弹出实参(即清栈)8比特或者16比特长的整形实参提升为32比特长。受到函数调用影响的寄存器(volatile registers):EAX, ECX, EDX, ST0 - ST7, ES, GS不受函数调用影响的寄存器: EBX, EBP, ESP, EDI, ESI, CS, DSRET指令从函数被调用者返回到调用者(实质上是读取寄存器EBP所指的线程栈之处保存的函数返回地址并加载到IP寄存器) cdecl 将函数返回值保存在寄存器中,所以 C 语言不支持多个返回值。另外,cdecl 是调用者负责清栈,因而可以实现可变参数的函数。如果是被调用者负责清理的话,无法实现可变参数的函数,但是编译代码的效率会高一点,因为清理栈的代码不用在每次调用的时候(编译器计算)生成一遍。(x86的ret指令允许一个可选的16位参数说明栈字节数,用来在返回给调用者之前解堆栈。代码类似ret 12这样,如果遇到这样的汇编代码,说明是被调用者清栈。) ...

July 14, 2019 · 14 min · jiezi

操作系统学习笔记(1)

bootloader 部分笔记bootloader 比较枯燥,主要是对各个寄存器进行设置,然后进行 BIOS 的 int10H 调用。需要用到一些汇编的知识,这里简要记录一些要点。BIOS int10H第十七个中断向量(interrupt vector),通常在实模式用于设置显示服务。需要配合 AH 一起使用,指定其子函数。清屏功能AH = 06H,向上滚动窗口AL = 00H,这时开启清屏功能BH 指定颜色属性,其余寄存器可暂时忽略(07即为黑底白字)设置 focusAH = 02HBH 为页码DH 为行数DL 为列数显示字符串AH = 13HAL 为写入模式BH 为页码BL 为颜色CX 存放字符串长度DH 为游标坐标行号DL 为游标坐标列号ES:BP 需要设置为字符串的偏移地址汇编要点org 指令,设置程序的起始段,避免再需要的地方手动就设置 0x7c00(主要影响绝对地址寻址指令)。最简单的显示字符串程序共用了代码断和数据段/extra 段,因此数据段放到了最后。org 0x7c00 ; set origin as 0x7c00mov ax, csmov es, ax ; es is equal to cs in this case; using int10h ah=06, al = 0 to clear screenmov ax, 0600hmov bx, 0700h; black background and white colormov cx, 0000hmov dx, 0xffffint 10h; using int10h, ah = 02h to set focusmov ax, 0200hmov bx, 0000hmov dx, 0000hint 10h; show stringmov ax, 1301h; AL = 01 indicates that after display string, the cursor will be the endmov bx, 0007hmov dx, 0000hmov cx, 10; length of stringmov bp, DisplayStringint 10hjmp $DisplayString: db “Hello Boot"times 510 - ($ - $$) db 0db 0x55, 0xaa ...

April 9, 2019 · 1 min · jiezi

汇编基本命令整理

今晚上汇编,因为下课很无聊所以老猪我抽空整理了一下汇编的基本命令,发上来给大家分享一下 ^ _ ^-r 查看、改变CPU寄存器的内容(1)查看CPU寄存器里面的内容-r (2)改变寄存器里面的内容-r axAX 0000:1111-rcs-rip-d 查看内存中的内容(1)查看指定内存位置中的内容查看内存10000H处的内容-d 1000:0-d 1000:0 f-e 改写内存中的内容(1)从内存10010的位置开始改写-e 1000:10(2)从内存10000位置开始写入数值1、字符"a"……-e 1000:0 1 ‘a’ 2 ‘b’ 3 ‘c’(3)向内存中写入字符串-e 1000:0 1 “a+b” 2 “c++” 3 “IBM”(4)将机器码写入内存中-e 1000:0 b8 01 00 b9 02 00 01 c8b80100 mov ax,0001b90200 mov cx,000201c8 add ax,cx-u 将命令翻译为汇编指令-u 1000:0-t 执行CS:IP指向的命令-a 以汇编命令的形式在内存中写入机器命令-a 1000:01000:0000 mov ax,11000:0003 mov bx,2

March 6, 2019 · 1 min · jiezi

《深入理解计算机系统》读书笔记:5.5 vs 5.6

0x00 前言没有看过或者没有看到这里的小伙伴们,看到这个标题一定觉得摸不着头脑。那这里就先来解释一下背景。double poly(double a[], double x, long degree){ long i; double result = a[0]; double xpwr = x; for (i = 1; i <= degree; i++) { result += a[i] * xpwr; xpwr = x * xpwr; } return result;}double polyh(double a[], double x, long degree){ long i; double result = a[degree]; for (i = degree; i >= 0; i–) { result = a[i] + x * result; } return result;}这是 CSAPP 的两道题,每一题是一段代码,这两段代码实现了同一个功能。这两道题有一个共同的问题,比较这两段代码的性能。0x01 答案这里的答案是,poly 的性能比 polyh 的性能要高。poly 的 CPE 是 5,而 polyh 的 CPE 是 8。这就显得很尴尬了,我原以为两个函数的 CPE 都是 8。0x02 我的猜想polyh 的 CPE 是 8 我没有疑问,因为这个循环里的操作是无法并行的,也就是下一次迭代会依赖上一次迭代产生的结果。所以,CPE = 5 + 3,5 是浮点数乘法的延迟下届,3 是浮点数加法的延迟下界。poly 的 CPE 我原本认为也是 8,两个乘法是可以并行的,但是这个加法的是依赖于第一个乘法的值,无法并行,所以 CPE = 5 + 3 = 8。0x03 指令集并行和流水线上面的是我的猜想,所以我认为这里的答案是它们的 CPE 是相同的,性能也是相同的。但是如前面所写,答案并不是这样的。于是,我把之前看的东西都翻出来想了一下,真的不是这样的。现代 CPU 是有一个流水线的概念的。什么是流水线呢,想象一下汽车车间,我们造一辆汽车,是分成了很多道工序的,比如装配发动机、装车门、轮子等等。现代 CPU 也是类似的,我们看到的一条指令,在执行的时候,经历了一长串的流水线,导致了指令真正的执行顺序和我们看到的可能是不一样的,但是由于现代出来的这种机制,可以确保最后的结果是和我们看到的是一样的。0x04 解释poly 函数,在执行的时候,由于有两个浮点数乘法单元,所以 a[i] * xpwr 和 xpwr = x * xpwr 可以并行执行。而 a[i] * xpwr 可以通过流水线的数据转移,让这个加法 result + a[i] * xpwr 可以在下一次迭代的时候执行,因为每次迭代的时候,两个乘法都不会依赖 result 这个结果。这样,加法和乘法可以并行执行。浮点乘法的延迟下界是 5,浮点加法的延迟下界是 3,所以浮点乘法是关键路径,CPE 也自然就是 5 了。再来看看 polyh 函数。这个函数的循环里只有一个浮点乘法运算和一个浮点加法运算。先来看看浮点乘法运算,x * result,很显然,每一次乘法都需要依赖上一次迭代的结果,导致了加法无法和乘法并行执行。于是,CPE 就成了 5 + 3 = 8 了。0x05 最后这个例子,我觉得很有趣,因为它涉及到了一个流水线的细节。同时,也说明了,并不是操作少的代码,效率就高。本文为作者自己读书总结的文章,由于作者的水平限制,难免会有错误,欢迎大家指正,感激不尽。0x06 参考文献《深入理解计算机系统(第 3 版)》第 4、5 章 ...

February 19, 2019 · 1 min · jiezi

x86汇编-EFLAGS寄存器&JCC

CF //第0位, Carry Flag进位标志位(如果运算结果的最高位产生了一个进位或者借位,CF为1, 否则, CF位0) //无符号运算的时候, 应该关注CF(非常重要)PF //第2位, Parity Flag奇偶标志位(运算结果中二进制1的个数的奇偶, 如果1的个数为偶数,则PF为1)AF //第4位, Auxiliary Carry Flag辅助进位标志位(发生以下情况时, AF为1) 1.在字操作时,发生低字节向高字节进位或者借位时 2.在字节操作时,发生低4位向高4位进位或借位时ZF //第6位, Zero Flag零标志位(运算结果为0, ZF为1, 否则, ZF为0)SF //第7位, Sign Flag符号标志位(用来反映运算结果的符号位, 与结果的最高位相同)OF //第11位, Overflow Flag溢出标致位(反映有符号数加减运算所得结果是否溢出,如果溢出,OF为1) //有符号运算的时候,应该关注OF(非常重要) //正 + 正 = 负 //如果两个正数相加, 结果为负数, 则溢出, OF = 1 //负 + 负 = 正 //如果两个负数相加, 结果为正数, 则溢出, OF = 1 //正 + 负 //永远不会溢出DF //第10位, Direction Flag(操作ESI,EDI 是递增还是递减) //决定movs, stos, cmps, scas, lods… 这种指令时, ESI和EDI的增长方向 //DF = 1, 递减 //DF = 0, 递增如果是无符号数运算, 是否溢出看CF位如果是有符号数运算, 是否溢出看OF位ADC指令 //带进位加法(两边不能同时为内存, 宽度要一样)SBB指令 //带借位减法(两边不能同时为内存, 宽度要一样)XCHG指令 //交换数据(两边不能同时为内存, 宽度要一样)MOVS指令 //移动数据,ESI => EDI, 两边都为内存 byte/word/dword movs byte ptr es:[edi], byte ptr ds:[esi] //简写为movsb movs word ptr es:[edi], word ptr ds:[esi] //简写为movsw movs dword ptr es:[edi], dword ptr ds:[esi] //简写为movsdSTOS指令 //将AL/AX/EAX的值存储到[EDI]指定的内存单元 stos byte ptr es:[edi] //简写为stosb stos word ptr es:[edi] //简写为stosw stos dword ptr es:[edi] //简写为stosdREP指令 //按计数寄存器(ECX)中指定的次数重复执行字符串指令 mov ecx, 10h rep movsd rep stosd以下指令不会影响两个操作数, 但会影响标志位cmp指令 //比较两个操作数, 相当于sub指令, 结果不保存test指令 //两个操作数进行与操作, 结果不保存 ...

February 13, 2019 · 1 min · jiezi

ASM汇编常用跳转指令-极速查

作者:逆向驿站微信公众号:逆向驿站知乎:逆向驿站若不是老鸟,是不是经常为各种JXX汇编跳转指令查资料?影响效率,更影响潜意识整体分析的"灵光一现"。本公众号《汇编跳转指令速查图片》已更新,公众号回复:跳转速查版公众号回复:跳转即可获得完整表格版如果你连N都不想自己拆解的话,那就在这里找吧,基本比较全了跳转指令分三类一、无条件跳转JMP ;无条件跳转二、根据CX、ECX寄存器的值跳转JCXZ ;CX 为 0 则跳转JECXZ;ECX 为 0 则跳转三、根据EFLAGS寄存器的PSW标志位跳转JE ;等于则跳转 同JZJNE ;不等于则跳转 同JNZJA ;无符号大于则跳转JNA ;无符号不大于则跳转JAE ;无符号大于等于则跳转 同JNBJNAE ;无符号不大于等于则跳转 同JBJB ;无符号小于则跳转JNB ;无符号不小于则跳转JBE ;无符号小于等于则跳转 同JNAJNBE ;无符号不小于等于则跳转 同JAJG ;有符号大于则跳转JNG ;有符号不大于则跳转JGE ;有符号大于等于则跳转 同JNLJNGE ;有符号不大于等于则跳转 同JLJL ;有符号小于则跳转JNL ;有符号不小于则跳转JLE ;有符号小于等于则跳转 同JNGJNLE ;有符号不小于等于则跳转 同JGJZ ;为零则跳转JNZ ;不为零则跳转JS ;为负则跳转JNS ;不为负则跳转JC ;进位则跳转JNC ;不进位则跳转JO ;溢出则跳转JNO ;不溢出则跳转JP ;为偶则跳转JNP ;不为偶则跳转JPE ;奇偶位置位则跳转 同JPJPO ;奇偶位复位则跳转 同JNP最后,喜欢这里的请推荐给你身边的朋友吧(渗透测试、逆向破解、病毒分析、信息安全等)欢迎关注微信公众号:逆向驿站相关文章● CrackMe-005精解(下)● CrackMe-005精解(上)● CrackMe-004精解● CrackMe-003精解● CrackMe-002精解● CrackMe-001精解

January 25, 2019 · 1 min · jiezi

菜鸟做 bomb lab 之第一关

第一题比较简单,但本菜鸡也做了两个小时(╯‵□′)╯︵┻━┻。。。首先打开事先已经反汇编的 bomb.s 文件,通过 bomb.c 已经知道每一关都是一个函数,它们的命名都是 phase_x,x 代表该关卡的数字,如果某个关卡输入的不正确,就会引爆炸弹 explode_bomb。首先看 main 函数的这几行400e1e: bf 38 23 40 00 mov $0x402338,%edi400e23: e8 e8 fc ff ff callq 400b10 <puts@plt>400e28: bf 78 23 40 00 mov $0x402378,%edi400e2d: e8 de fc ff ff callq 400b10 <puts@plt>400e32: e8 67 06 00 00 callq 40149e <read_line>400e37: 48 89 c7 mov %rax,%rdi400e3a: e8 a1 00 00 00 callq 400ee0 <phase_1>400e3f: e8 80 07 00 00 callq 4015c4 <phase_defused>400e44: bf a8 23 40 00 mov $0x4023a8,%edi打开 gdb,先给这一行打上断点 break *0x400e23,然后 run 起来。这里可以看到调用了 puts 这个函数,寄存器 %edi 存储的是函数的第一个参数,我们把它的结果打印出来 x/s 0x402338、x/s 0x402378,发现得到了运行 bomb 后输出的字符串。说明第一关就是从这里开始的。由于返回值是存在 %rax 中的,这里 mov %rax %rdi,说明输入的内容传参给了 phase_1。在 gdb 里给 phase_1 打断点 break phase_1。0000000000400ee0 <phase_1>: 400ee0: 48 83 ec 08 sub $0x8,%rsp 400ee4: be 00 24 40 00 mov $0x402400,%esi 400ee9: e8 4a 04 00 00 callq 401338 <strings_not_equal> 400eee: 85 c0 test %eax,%eax 400ef0: 74 05 je 400ef7 <phase_1+0x17> 400ef2: e8 43 05 00 00 callq 40143a <explode_bomb> 400ef7: 48 83 c4 08 add $0x8,%rsp 400efb: c3 retq通过这里的代码,就可以分析出来,通过调用 string_not_equal 比较输入的字符串与 0x402400 存储的字符串是否相等,来决定是不是 explode_bomb。通过这个函数名也可以知道一定要输入与 0x402400 相同的字符串就可以通过第一关了。所以在这里打个断点 break *0x400ee9,然后 x/s 0x402400 打印出来这里的字符串,我这里是 Border relations with Canada have never been better.,然后输入这个字符串,第一关就过了~string_not_equal虽然这样就过关了,但是我还是对这里的代码好奇,毕竟是学习嘛,看看这里的代码熟悉熟悉汇编。0000000000401338 <strings_not_equal>: 401338: 41 54 push %r12 40133a: 55 push %rbp 40133b: 53 push %rbx 40133c: 48 89 fb mov %rdi,%rbx 40133f: 48 89 f5 mov %rsi,%rbp 401342: e8 d4 ff ff ff callq 40131b <string_length> 401347: 41 89 c4 mov %eax,%r12d 40134a: 48 89 ef mov %rbp,%rdi 40134d: e8 c9 ff ff ff callq 40131b <string_length> 401352: ba 01 00 00 00 mov $0x1,%edx 401357: 41 39 c4 cmp %eax,%r12d 40135a: 75 3f jne 40139b <strings_not_equal+0x63> 40135c: 0f b6 03 movzbl (%rbx),%eax 40135f: 84 c0 test %al,%al 401361: 74 25 je 401388 <strings_not_equal+0x50> 401363: 3a 45 00 cmp 0x0(%rbp),%al 401366: 74 0a je 401372 <strings_not_equal+0x3a> 401368: eb 25 jmp 40138f <strings_not_equal+0x57> 40136a: 3a 45 00 cmp 0x0(%rbp),%al 40136d: 0f 1f 00 nopl (%rax) 401370: 75 24 jne 401396 <strings_not_equal+0x5e> 401372: 48 83 c3 01 add $0x1,%rbx 401376: 48 83 c5 01 add $0x1,%rbp 40137a: 0f b6 03 movzbl (%rbx),%eax 40137d: 84 c0 test %al,%al 40137f: 75 e9 jne 40136a <strings_not_equal+0x32> 401381: ba 00 00 00 00 mov $0x0,%edx 401386: eb 13 jmp 40139b <strings_not_equal+0x63> 401388: ba 00 00 00 00 mov $0x0,%edx 40138d: eb 0c jmp 40139b <strings_not_equal+0x63> 40138f: ba 01 00 00 00 mov $0x1,%edx 401394: eb 05 jmp 40139b <strings_not_equal+0x63> 401396: ba 01 00 00 00 mov $0x1,%edx 40139b: 89 d0 mov %edx,%eax 40139d: 5b pop %rbx 40139e: 5d pop %rbp 40139f: 41 5c pop %r12 4013a1: c3 retq看代码,发现很符合书上讲的,%r12、%rbp、%rbx 都是被调用者保存的寄存器。首先 0x401342 ~ 0x40135a,判断了它们的长度是不是相同,如果长度不相同,那么它们必然不是同一个字符串。mov $0x1,%edx 和 mov %edx,%eax 返回了 1。0x40135c ~ 0x401361 这几行,判断了所输入的字符串的第一个字符是不是 \0。因为走到这条命令,已经判断过长度是相同的了,如果其中的一个字符串的首字符是 \0,那么另外一个必然是一样的(所有的字符串一定都包含一个 \0),所以这里直接就返回 0。0x401361 ~ 0x40137f 是一个循环,它遍历了两个字符串,每一个字符是不是相同的,直到遇到 \0。string_length000000000040131b <string_length>: 40131b: 80 3f 00 cmpb $0x0,(%rdi) 40131e: 74 12 je 401332 <string_length+0x17> 401320: 48 89 fa mov %rdi,%rdx 401323: 48 83 c2 01 add $0x1,%rdx 401327: 89 d0 mov %edx,%eax 401329: 29 f8 sub %edi,%eax 40132b: 80 3a 00 cmpb $0x0,(%rdx) 40132e: 75 f3 jne 401323 <string_length+0x8> 401330: f3 c3 repz retq 401332: b8 00 00 00 00 mov $0x0,%eax 401337: c3 retq这个函数就比较简单了,其实就是找到 \0 的位置,然后返回其余首地址的差,即长度。这个翻译成 C 语言可以这么写。int string_length(char *s){ char *b = a; while (*b != 0) b = b + 1; return (int) (b - a);}本文是作者在看《深入理解计算机系统》以及完成 bomb lab 时的理解与总结,谨此记录下来已被日后翻阅。同时,也分享给各位希望了解这些知识的同道者们。由于作者水平有限,如有错误之处,望不吝赐教,深表感谢。 ...

October 15, 2018 · 3 min · jiezi

Go 程序是如何编译成目标机器码的

今天我们一起来研究 Go 1.11 的编译器,以及它将 Go 程序代码编译成可执行文件的过程。以便了解我们日常使用的工具是如何工作的。本文还会带你了解 Go 程序为什么这么快,以及编译器在这中间起到了什么作用。首先,编译器的三个阶段:逐行扫描源代码,将之转换为一系列的 token,交给 parser 解析。parser,它将一系列 token 转换为 AST(抽象语法树),用于下一步生成代码。最后一步,代码生成,会利用上一步生成的 AST 并根据目标机器平台的不同,生成目标机器码。注意:下面使用的代码包(go/scanner,go/parser,go/token,go/ast)主要是让我们可以方便地对 Go 代码进行解析和生成,做出更有趣的事情。但是 Go 本身的编译器并不是用这些代码包实现的。扫描代码,进行词法分析任何编译器的第一步都是将源代码文本分解成 token,由扫描程序(也称为词法分析器)完成。token 可以是关键字,字符串,变量名,函数名等等。每一个有效的词都由 token 表示。在 Go 中,我们写在代码上的 “package”,“main”,“func” 这些都是 token。token 由代码中的位置,类型和原始文本组成。我们可以使用 go/scanner 和 go/token 包在 Go 程序中自己执行扫描程序。这意味着我们可以像编译器那样扫描检视自己的代码。下面,我们将通过一个打印 Hello World 的示例来展示 token。package mainimport ( “fmt” “go/scanner” “go/token”)func main() { src := []byte(package mainimport "fmt"func main() { fmt.Println("Hello, world!")}) var s scanner.Scanner fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(src)) s.Init(file, src, nil, 0) for { pos, tok, lit := s.Scan() fmt.Printf("%-6s%-8s%q\n", fset.Position(pos), tok, lit) if tok == token.EOF { break } }}首先通过源代码字符串创建 token 集合并初始化 scan.Scanner,它将逐行扫描我们的源代码。接下来循环调用 Scan() 并打印每个 token 的位置,类型和文本字符串,直到遇到文件结束(EOF)标记。输出:2:1 package “package"2:9 IDENT “main"2:13 ; “\n"4:1 import “import"4:8 STRING “"fmt"“4:13 ; “\n"6:1 func “func"6:6 IDENT “main"6:10 ( ““6:11 ) ““6:13 { ““7:2 IDENT “fmt"7:5 . ““7:6 IDENT “Println"7:13 ( ““7:14 STRING “"Hello, world!"“7:29 ) ““7:30 ; “\n"8:1 } ““8:2 ; “\n"8:3 EOF ““以第一行为例分析这个输出,第一列 2:1 表示扫描到了源代码第二行第一个字符,第二列 package 表示 token 是 package,第三列 “package” 表示源代码文本。我们可以看到在 Scanner 执行过程中将 \n 换行符标记成了 ; 分号,像在 C 语言中是用分号表示一行结束的。这就解释了为什么 Go 不需要分号:它们是在词法分析阶段由 Scanner 智能地解释的。语法分析源代码扫描完成后,扫描结果将被传递给语法分析器。语法分析是编译的一个阶段,它将 token 转换为 抽象语法树(AST)。 AST 是源代码的结构化表示。在 AST 中,我们将能够看到程序结构,比如函数和常量声明。我们使用 go/parser 和 go/ast 来打印完整的 AST:package mainimport ( “go/ast” “go/parser” “go/token” “log”)func main() { src := []byte(package mainimport "fmt"func main() { fmt.Println("Hello, world!")}) fset := token.NewFileSet() file, err := parser.ParseFile(fset, “”, src, 0) if err != nil { log.Fatal(err) } ast.Print(fset, file)}输出: 0 *ast.File { 1 . Package: 2:1 2 . Name: *ast.Ident { 3 . . NamePos: 2:9 4 . . Name: “main” 5 . } 6 . Decls: []ast.Decl (len = 2) { 7 . . 0: *ast.GenDecl { 8 . . . TokPos: 4:1 9 . . . Tok: import 10 . . . Lparen: - 11 . . . Specs: []ast.Spec (len = 1) { 12 . . . . 0: *ast.ImportSpec { 13 . . . . . Path: *ast.BasicLit { 14 . . . . . . ValuePos: 4:8 15 . . . . . . Kind: STRING 16 . . . . . . Value: “"fmt"” 17 . . . . . } 18 . . . . . EndPos: - 19 . . . . } 20 . . . } 21 . . . Rparen: - 22 . . } 23 . . 1: *ast.FuncDecl { 24 . . . Name: *ast.Ident { 25 . . . . NamePos: 6:6 26 . . . . Name: “main” 27 . . . . Obj: *ast.Object { 28 . . . . . Kind: func 29 . . . . . Name: “main” 30 . . . . . Decl: *(obj @ 23) 31 . . . . } 32 . . . } 33 . . . Type: *ast.FuncType { 34 . . . . Func: 6:1 35 . . . . Params: *ast.FieldList { 36 . . . . . Opening: 6:10 37 . . . . . Closing: 6:11 38 . . . . } 39 . . . } 40 . . . Body: *ast.BlockStmt { 41 . . . . Lbrace: 6:13 42 . . . . List: []ast.Stmt (len = 1) { 43 . . . . . 0: *ast.ExprStmt { 44 . . . . . . X: *ast.CallExpr { 45 . . . . . . . Fun: *ast.SelectorExpr { 46 . . . . . . . . X: *ast.Ident { 47 . . . . . . . . . NamePos: 7:2 48 . . . . . . . . . Name: “fmt” 49 . . . . . . . . } 50 . . . . . . . . Sel: *ast.Ident { 51 . . . . . . . . . NamePos: 7:6 52 . . . . . . . . . Name: “Println” 53 . . . . . . . . } 54 . . . . . . . } 55 . . . . . . . Lparen: 7:13 56 . . . . . . . Args: []ast.Expr (len = 1) { 57 . . . . . . . . 0: *ast.BasicLit { 58 . . . . . . . . . ValuePos: 7:14 59 . . . . . . . . . Kind: STRING 60 . . . . . . . . . Value: “"Hello, world!"” 61 . . . . . . . . } 62 . . . . . . . } 63 . . . . . . . Ellipsis: - 64 . . . . . . . Rparen: 7:29 65 . . . . . . } 66 . . . . . } 67 . . . . } 68 . . . . Rbrace: 8:1 69 . . . } 70 . . } 71 . } 72 . Scope: *ast.Scope { 73 . . Objects: map[string]*ast.Object (len = 1) { 74 . . . “main”: *(obj @ 27) 75 . . } 76 . } 77 . Imports: []*ast.ImportSpec (len = 1) { 78 . . 0: *(obj @ 12) 79 . } 80 . Unresolved: []*ast.Ident (len = 1) { 81 . . 0: *(obj @ 46) 82 . } 83 }分析这个输出,在 Decls 字段中,包含了代码中所有的声明,例如导入、常量、变量和函数。在本例中,我们只有两个:导入fmt包 和 主函数。为了进一步理解它,我们可以看看下面这个图,它是上述数据的表示,但只包含类型,红色代表与节点对应的代码:main函数由三个部分组成:Name、Type 和 Body。Name 是值为 main 的标识符。由 Type 字段指定的声明将包含参数列表和返回类型(如果我们指定了的话)。正文由一系列语句组成,里面包含了程序的所有行,在本例中只有一行fmt.Println(“Hello, world!")。我们的一条 fmt.Println 语句由 AST 中很多部分组成。该语句是一个 ExprStmt表达式语句(expression statement),例如,它可以像这里一样是一个函数调用,它可以是字面量,可以是一个二元运算(例如加法和减法),当然也可以是一元运算(例如自增++,自减–,否定!等)等等。同时,在函数调用的参数中可以使用任何表达式。然后,ExprStmt 又包含一个 CallExpr,它是我们实际的函数调用。里面又包括几个部分,其中最重要的部分是 Fun 和 Args。 Fun 包含对函数调用的引用,在这种情况下,它是一个 SelectorExpr,因为我们从 fmt 包中选择 Println 标识符。但是至此,在 AST 中,编译器还不知道 fmt 是一个包,它也可能是 AST 中的一个变量。Args 包含一个表达式列表,它是函数的参数。这里,我们将一个文本字符串传递给函数,因而它由一个类型为 STRING 的 BasicLit 表示。显然,AST 包含了许多信息,我们不仅可以分析出以上结论,还可以进一步检查 AST 并查找文件中的所有函数调用。下面,我们将使用 go/ast 包中的 Inspect 函数来递归地遍历树,并分析所有节点的信息。package mainimport ( “fmt” “go/ast” “go/parser” “go/printer” “go/token” “os”)func main() { src := []byte(package mainimport "fmt"func main() { fmt.Println("Hello, world!")}) fset := token.NewFileSet() file, err := parser.ParseFile(fset, “”, src, 0) if err != nil { fmt.Println(err) } ast.Inspect(file, func(n ast.Node) bool { call, ok := n.(ast.CallExpr) if !ok { return true } printer.Fprint(os.Stdout, fset, call.Fun) return false })}输出:fmt.Println上面代码的作用是查找所有节点以及它们是否为 ast.CallExpr 类型,上面也说过这种类型是函数调用。如果是,则使用 go/printer 包打印 Fun 中存在的函数的名称。构建出 AST 后,将使用 GOPATH 或者在 Go 1.11 及更高版本中的 modules 解析所有导入。然后,执行类型检查,并做一些让程序运行更快的初级优化。代码生成在解析导入并做了类型检查之后,我们可以确认程序是合法的 Go 代码,然后就走到将 AST 转换为(伪)目标机器码的过程。此过程的第一步是将 AST 转换为程序的低级表示,特别是转换为 静态单赋值(SSA)表单。这个中间表示不是最终的机器代码,但它确实代表了最终的机器代码。 SSA 具有一组属性,会使应用优化变得更容易,其中最重要的是在使用变量之前总是定义变量,并且每个变量只分配一次。在生成 SSA 的初始版本之后,将执行一些优化。这些优化适用于某些代码,可以使处理器执行起来更简单且更快速。例如,可以做 死码消除。还有比如可以删除某些 nil 检查,因为编译器可以证明这些检查永远不会出错。现在通过最简单的例子来说明 SSA 和一些优化过程:package mainimport “fmt"func main() { fmt.Println(2)}如你所见,此程序只有一个函数和一个导入。它会在运行时打印 2。但是,此例足以让我们了解SSA。为了显示生成的 SSA,我们需要将 GOSSAFUNC 环境变量设置为我们想要跟踪的函数,在本例中为main 函数。我们还需要将 -S 标识传递给编译器,这样它就会打印代码并创建一个HTML文件。我们还将编译Linux 64位的文件,以确保机器代码与您在这里看到的相同。在终端执行下面的命令:GOSSAFUNC=main GOOS=linux GOARCH=amd64 go build -gcflags -S main.go会在终端打印出所有的 SSA,同时也会生成一个交互式的 ssa.html 文件,我们用浏览器打开它。当你打开 ssa.html 时,将显示很多阶段,其中大部分都已折叠。start 阶段是从 AST 生成的SSA;lower 阶段将非机器特定的 SSA 转换为机器特定的 SSA,最后的 genssa 就是生成的机器代码。start 阶段的代码如下:b1: v1 = InitMem <mem> v2 = SP <uintptr> v3 = SB <uintptr> v4 = ConstInterface <interface {}> v5 = ArrayMake1 <[1]interface {}> v4 v6 = VarDef <mem> {.autotmp_0} v1 v7 = LocalAddr <[1]interface {}> {.autotmp_0} v2 v6 v8 = Store <mem> {[1]interface {}} v7 v5 v6 v9 = LocalAddr <[1]interface {}> {.autotmp_0} v2 v8 v10 = Addr <*uint8> {type.int} v3 v11 = Addr <*int> {”".statictmp_0} v3 v12 = IMake <interface {}> v10 v11 v13 = NilCheck <void> v9 v8 v14 = Const64 <int> [0] v15 = Const64 <int> [1] v16 = PtrIndex <interface {}> v9 v14 v17 = Store <mem> {interface {}} v16 v12 v8 v18 = NilCheck <void> v9 v17 v19 = IsSliceInBounds <bool> v14 v15 v24 = OffPtr <[]interface {}> [0] v2 v28 = OffPtr <*int> [24] v2If v19 → b2 b3 (likely) (line 6)b2: ← b1 v22 = Sub64 <int> v15 v14 v23 = SliceMake <[]interface {}> v9 v22 v22 v25 = Copy <mem> v17 v26 = Store <mem> {[]interface {}} v24 v23 v25 v27 = StaticCall <mem> {fmt.Println} [48] v26 v29 = VarKill <mem> {.autotmp_0} v27Ret v29 (line 7)b3: ← b1 v20 = Copy <mem> v17 v21 = StaticCall <mem> {runtime.panicslice} v20Exit v21 (line 6)这个简单的程序就已经产生了相当多的 SSA(总共35行)。然而,很多都是引用,可以消除很多(最终的SSA版本有28行,最终的机器代码版本有18行)。每个 v 都是一个新变量,可以点击来查看它被使用的位置。b 是块,这里有三块:b1,b2,b3。b1 始终会执行,b2 和 b3 是条件块,满足条件才执行。我们来看 b1 结尾处的 If v19 → b2 b3 (likely)。单击该行中的 v19 可以查看它定义的位置。可以看到它定义为 IsSliceInBounds <bool> v14 v15,通过 Go 编译器源代码,我们知道 IsSliceInBounds 的作用是检查 0 <= arg0 <= arg1。然后单击 v14 和 v15 看看在哪定义的,我们会看到 v14 = Const64 <int> [0],Const64 是一个常量 64 位整数。 v15 定义一样,放在 args1 的位置。所以,实际执行的是 0 <= 0 <= 1,这显然是正确的。编译器也能够证明这一点,当我们查看 opt 阶段(“机器无关优化”)时,我们可以看到它已经重写了 v19 为 ConstBool <bool> [true]。结果就是,在 opt deadcode 阶段,b3 条件块被删除了,因为永远也不会执行到 b3。下面来看一下 Go 编译器在把 SSA 转换为 机器特定的SSA 之后所做的另一个更简单的优化,基于amd64体系结构的机器代码。下面,我们将比较 lower 和 lowered deadcode。lower:b1: BlockInvalid (6)b2: v2 (?) = SP <uintptr> v3 (?) = SB <uintptr> v10 (?) = LEAQ <*uint8> {type.int} v3 v11 (?) = LEAQ <int> {”".statictmp_0} v3 v15 (?) = MOVQconst <int> [1] v20 (?) = MOVQconst <uintptr> [0] v25 (?) = MOVQconst <uint8> [0] v1 (?) = InitMem <mem> v6 (6) = VarDef <mem> {.autotmp_0} v1 v7 (6) = LEAQ <[1]interface {}> {.autotmp_0} v2 v9 (6) = LEAQ <[1]interface {}> {.autotmp_0} v2 v16 (+6) = LEAQ <*interface {}> {.autotmp_0} v2 v18 (6) = LEAQ <**uint8> {.autotmp_0} [8] v2 v21 (6) = LEAQ <**uint8> {.autotmp_0} [8] v2 v30 (6) = LEAQ <*int> [16] v2 v19 (6) = LEAQ <*int> [8] v2 v23 (6) = MOVOconst <int128> [0] v8 (6) = MOVOstore <mem> {.autotmp_0} v2 v23 v6 v22 (6) = MOVQstore <mem> {.autotmp_0} v2 v10 v8 v17 (6) = MOVQstore <mem> {.autotmp_0} [8] v2 v11 v22 v14 (6) = MOVQstore <mem> v2 v9 v17 v28 (6) = MOVQstoreconst <mem> [val=1,off=8] v2 v14 v26 (6) = MOVQstoreconst <mem> [val=1,off=16] v2 v28 v27 (6) = CALLstatic <mem> {fmt.Println} [48] v26 v29 (5) = VarKill <mem> {.autotmp_0} v27Ret v29 (+7)在HTML中,某些行是灰色的,这意味着它们将在下一个阶段中被删除或修改。例如,v15 (?) = MOVQconst <int> [1] 显示为灰色。点击 v15,我们看到它在其他地方都没有使用,而 MOVQconst 基本上与我们之前看到的 Const64 相同,只针对amd64的特定机器。我们把 v15 设置为1。但是,v15 在其他地方都没有使用,所以它是无用的(死的)代码并且可以消除。Go 编译器应用了很多这类优化。因此,虽然 AST 生成的初始 SSA 可能不是最快的实现,但编译器将SSA优化为更快的版本。 HTML 文件中的每个阶段都有可能发生优化。如果你有兴趣了解 Go 编译器中有关 SSA 的更多信息,请查看 Go 编译器的 SSA 源代码。这里定义了所有的操作以及优化。结论Go 是一种非常高效且高性能的语言,由其编译器及其优化支撑。要了解有关 Go 编译器的更多信息,源代码的 README 是不错的选择。 ...

September 27, 2018 · 8 min · jiezi