精华内容
下载资源
问答
  • Unity基于模板生成代码的原理与应用

    unity中我们可以在Project面板右键或通过Assets菜单创建脚本或是shader,unity为我们提供了C#,javascript脚本以及四个shader代码的模板,当创建这些脚本时实际上是复制这些模板并改变其类名或是shader名,我们可以在Unity的安装目录下找到这些代码模板文件:D:\Program Files\Unity\Editor\Data\Resources\ScriptTemplates。

    通过反编译UnityEditor.dll,可以在ProjectWindowUtil这个类中找到如下私有方法:


    注意它先判断了模板文件是否包含test字符串,这一步是创建Editor Test C# script用的,因为这个脚本只能创建在Editor文件夹内,之后通过创建的代码类型获取图标,最后是ProjectWindowUtil.StartNameEditingIfProjectWindowExists。

    继续跟踪,先看ScriptableObject.CreateInstance<DoCreateScriptAsset>()。

    用反编译工具查看DoCreateScriptAsset这个类,


    这是继承自EndNameEditAction的类,在ScriptableObject.CreateInstance创建实例后作为ProjectWindowUtil.StartNameEditingIfProjectWindowExists的结束命名的回调,

    ProjectWindowUtil.StartNameEditingIfProjectWindowExists是一个公有方法,其作用是将设置焦点到某文件并进入重命名,因为我们知道当我们右键创建代码的时候除了会选中该代码文件,同时还是处于重命名代码的状态的,此时代码尚未编译,当我们结束重命名后才真正进入到DoCreateScriptAsset的Action方法,而这个方法中最重要的内容就是ProjectWindowUtil.CreateScriptAssetFromTemplete方法,继续进入该方法,看到这是一个内部方法:


    至此基本就是Unity创建模板代码的核心内容了,其实就是通过将关键字替换的方法实现的。

    知道这些内容后,我们就可以自己写一些代码模板,用反射的方式来让Unity帮我们处理这些模板并创建代码,或是直接照着unity的方式自己写一套模板创建的方式,如Lua模板,Shader模板,xml模板等。

    可以将模板代码放到任何位置,或者放到unity自己放置模板的位置,unity代码模板路径的访问方式:

    EditorApplication.applicationContentsPath+"/Resources/ScriptTemplates"

    using UnityEditor;
    using System.Reflection;
    
    public class ProjectWindowUtilEx
    {
        public static void CreateScriptUtil(string path, string templete)
        {
            MethodInfo method = typeof (ProjectWindowUtil).GetMethod("CreateScriptAsset",
                BindingFlags.Static | BindingFlags.NonPublic);
            if (method != null)
                method.Invoke(null, new object[] {templete, path});
        }
    }

    以Lua模板为例:

    [MenuItem("Assets/Create/Lua/MyLua.lua")]
    static void CreateMyLua()
    {
       ProjectWindowUtilEx.CreateScriptUtil(@"Templates\Lua\MyLua.lua.txt", "New MyLua.lua");
    }





    更多内容:http://www.lsngo.net/2017/11/10/unity_scripttemplate/


    展开全文
  • eclipse运行java代码的原理

    千次阅读 2018-02-28 11:24:28
    问题我们知道使用javac和java可以手工执行java代码,那么为什么eclipse可以直接运行了,eclipse为什么做了什么?分析1、我们在eclipse中创建一个项目时候,eclipse会让我们选定某个jdk,一般来说eclipse会自带有...

    问题

    我们知道使用javac和java可以手工的执行java代码,那么为什么eclipse可以直接运行了,eclipse为什么做了什么?

    分析

    1、我们在eclipse中创建一个项目的时候,eclipse会让我们选定某个jdk,一般来说eclipse会自带有jdk
    这里写图片描述

    2、如果我们想要我们创建的项目使用我们自己本机的jdk,那么首先需要如下配置,然后我们在创建项目的时候,就可以选择我们自己的jdk了。
    这里写图片描述

    3、如下两个项目,一个是使用eclipse自带的jdk,一个是使用本机的jdk。
    这里写图片描述

    4、如果我们想更换项目的jdk,可以进行如下操作:两种方式,但是原理都是一样,选中某个项目,右键单击,Build Path >>> config build path >>> libraries,然后在这里面新增修改library即可。第二种是右键单击项目,点击properties,然后找到libraries,和第一种就是一样的原理。

    5、既然我们告诉了eclipse我们jdk的路径,自然eclipse就能找到我们的javac和java路径了。那么eclipse在执行javac后生成的class文件在哪呢?在eclipse中也是可以配置的,一样是右键单击项目,选择properties
    这里写图片描述
    这里写图片描述

    展开全文
  • Android 混淆代码的原理与实施

    千次阅读 2012-04-20 16:52:47
    被泄露,我们往往需要混淆(Obfuscation code , 也就是把方法,字段,包和类这些java 元素的名称改成无意义的名称,这样代码结构没有变化,还可以运行,但是想弄懂代码的架构却很难。 proguar
    
    

    proguard 原理

    Java代码编译成二进制class 文件,这个class 文件也可以反编译成源代码 ,除了注释外,原来的code 基本都可以看到。为了防止重要code 被泄露,我们往往需要混淆(Obfuscation code , 也就是把方法,字段,包和类这些java 元素的名称改成无意义的名称,这样代码结构没有变化,还可以运行,但是想弄懂代码的架构却很难。 proguard 就是这样的混淆工具,它可以分析一组class 的结构,根据用户的配置,然后把这些class 文件的可以混淆java 元素名混淆掉。在分析class 的同时,他还有其他两个功能,删除无效代码(Shrinking 收缩),和代码进行优化 (Optimization Options)。

    缺省情况下,proguard 会混淆所有代码,但是下面几种情况是不能改变java 元素的名称,否则就会这样就会导致程序出错。

    一, 我们用到反射的地方。

    二, 我们代码依赖于系统的接口,比如被系统代码调用的回调方法,这种情况最复杂。

    三, 是我们的java 元素名称是在配置文件中配置好的。

    所以使用proguard时,我们需要有个配置文件告诉proguard 那些java 元素是不能混淆的。


    proguard 配置

    最常用的配置选项

    -dontwarn 缺省proguard 会检查每一个引用是否正确,但是第三方库里面往往有些不会用到的类,没有正确引用。如果不配置的话,系统就会报错。

    -keep 指定的类和类成员被保留作为 入口 。

    -keepclassmembers 指定的类成员被保留。

    -keepclasseswithmembers 指定的类和类成员被保留,假如指定的类成员存在的话。


    proguard 问题和风险

    代码混淆后虽然有混淆优化的好处,但是它往往也会带来如下的几点问题

    1,混淆错误,用到第三方库的时候,必须告诉 proguard 不要检查,否则proguard 会报错。

    2,运行错误,当code 不能混淆的时候,我们必须要正确配置,否则程序会运行出错,这种情况问题最多。

    3,调试苦难,出错了,错误堆栈是混淆后的代码 ,自己也看不懂。


    为了防止混淆出问题,你需要熟悉你所有的code ,系统的架构 ,以及系统和你code的集成的接口,并细心分析。 同时你必须需要一轮全面的测试。 所以混淆也还是有一定风险的。 为了避免风险,你可以只是混淆部分关键的代码,但是这样你的混淆的效果也会有所降低。


    常见的不能混淆的androidCode

    Android 程序 ,下面这样代码混淆的时候要注意保留。

    Android系统组件,系统组件有固定的方法被系统调用。

    被Android Resource 文件引用到的。名字已经固定,也不能混淆,比如自定义的View 。

    Android Parcelable ,需要使用android 序列化的。

    其他Anroid 官方建议 不混淆的,如

    android.app.backup.BackupAgentHelper
    android.preference.Preference
    com.android.vending.licensing.ILicensingService
    

    Java序列化方法,系统序列化需要固定的方法。

    枚举 ,系统需要处理枚举的固定方法。

    本地方法,不能修改本地方法名

    annotations 注释

    数据库驱动

    有些resource 文件

    用到反射的地方


    如何实施

    现在的系统已经配置为混淆时候会保留

    Android系统组件

    自定义View

    Android Parcelable

    Android R 文件

    Android Parcelable

    枚举

    各个开发人员必须检查自己的code 是否用到反射 ,和其他不能混淆的地方。告诉我来修改配置文件(已经保留的就不需要了)


    目前系统部检查的第三方库为

    -dontwarn android.support.**

    -dontwarn com.tencent.**

    -dontwarn org.dom4j.**

    -dontwarn org.slf4j.**

    -dontwarn org.http.mutipart.**

    -dontwarn org.apache.**

    -dontwarn org.apache.log4j.**

    -dontwarn org.apache.commons.logging.**

    -dontwarn org.apache.commons.codec.binary.**

    -dontwarn weibo4android.**

    各个开发人员如果新增加第三方库,需要考虑到其混淆的问题。


    具体如何过滤掉反射的R文件及第三方包?

    请参考http://blog.csdn.net/kepoon/article/details/7479953

    展开全文
  • erlang虚拟机代码执行原理

    千次阅读 2015-06-03 20:02:28
    erlang是开源的,很多人都研究过源代码。但是,从erlang代码到c代码,这...所以这里,我利用一些时间,整理下erlang代码的执行过程,从erlang代码编译过程,到代码执行过程做讲解,然后重点讲下虚拟机执行代码的原理
    erlang是开源的,很多人都研究过源代码。但是,从erlang代码到c代码,这是个不小的跨度,而且代码也比较复杂。所以这里,我利用一些时间,整理下erlang代码的执行过程,从erlang代码编译过程,到代码执行过程做讲解,然后重点讲下虚拟机执行代码的原理。将本篇文章,献给所有喜欢erlang的人。

    erlang代码编译过程

    erlang对开发者是友好的,从erlang程序文件编译成能被erlang虚拟机识别的beam文件,在这个编译过程还对开发者暴露中间代码。借助这个中间代码,我们就可以逐步探究erlang代码的执行过程。

    这是erlnag的编译过程,当然,最开始和大多数编译器一样,首先会将程序文件转换成语法树,但这个转换对我们来说阅读的意义不大,所以归结于以上3个过程。

    1. erlang核心代码
    确切的叫法是Core Erlang,使用了类似Haskell 的语法,每个变量都用“Let” 声明。在erlang shell通过以下方式可以获取模块的Core Erlang代码,将会生成test.core文件
    c(test, to_core).
    实际上core文件可以直接编译成beam文件,如下:
    c(test, from_core).

    2. erlang汇编码
    这个是erlang代码编译成beam前的汇编代码,虽然在erlang打包成beam,以及加载到VM时会进一步优化,但汇编码实际上可以看成erlang代码到c代码的纽带。但理解汇编码而不是很容易,这里要知道erlang VM的设计基于寄存器,其中有两类重要的寄存器,传递参数的x寄存器,和在函数内用作本地变量的y寄存器。在erlang shell通过以下方式可以获取模块的汇编代码,将会生成test.S文件
    c(test, to_asm). 或是 c(test, 'S').
    当然,S文件也支持编译成beam文件,如下:
    c(test, from_asm).

    3. erlang BEAM
    beam文件是不可阅读的,只是给VM识别,内容包括了代码,原子,导入导出函数,属性,编译信息等数据块。

    4.  erlang运行时代码
    运行时代码是指模块加载到VM后的代码,erlang对开发者暴露了底层的接口。当模块加载后,在erlang shell下通过以下方式可以获取模块的运行时代码,就会生成test.dis文件
    erts_debug:df(test).

    这里,细心的同学会发现,通过对比erlang汇编码和运行时代码,发现指令代码是不完全相同的。一方面,erlang会对指令进一步做优化;另外,erlang使用了两种指令集,有限指令集和扩展指令集,在beam文件使用了有限指令集,然后在加载到VM时展开为扩展指令集。有论文说是为了减少Beam的大小,这点我没有做过实质性的探究,我只是觉得有限指令集比较短,更容易阅读被人理解。关于有限指令集和扩展指令集的差别,我在文章最后的拓展阅读做了讨论。

    erlang代码从编译到执行过程

    前面介绍了erlang代码编译的过程,现在再来说明erlang代码从编译到执行的完整过程。文章erlang版本以R16B02作说明。

    这里,erlang代码先被编译成beam,然后加载到VM中,最后再被模拟器所识别和调用。
    其中,beam文件的加载过程会将beam的字节码形式的数据转成Threaded code和数据。前面也提到,beam文件的字节码数据包含有代码块,这里是将指令展开,转成Threaded code(线索化代码),每条指令包含了opcode(操作码)和operands(操作数),另外还对operands做修正,比如调用外部函数,这里会找到这个外部函数的导出地址,这样每次代码执行的时候就不用再去函数表查找到这个函数,就可以直接执行代码。

    Beam的加载逻辑是在 beam_load.c 完成的,指令集的转换在beam_opcodes.c做了映射,而beam_opcodes.c文件是在编译Erlang源码过程有Perl脚本beam_makeops根据ops.tab生成的。所有有限指令集可以在genop.tab找到。
    File 
    Path
    beam_makeops
    erts/emulator/utils/
    ops.tab
    erts/emulator/beam/
    beam_opcodes.c
    erts/emulator/<machine>/opt/smp/
    beam_load.c
    erts/emulator/beam/
    genop.tab
    lib/compiler/src/


    erlang 虚拟机执行代码的原理

    这里先简单说明下erlang虚拟机、进程、堆栈,寄存器,然后侧重从指令调度,代码线索化说明虚拟机代码执行原理。

    erlang虚拟机概述

    通常我们说的eralng虚拟机,是指BEAM虚拟机模拟器和erlang运行时系统(ERTS)。ERTS是erlang VM最底层的应用,负责和操作系统交互,管理I/O,实现erlang进程和BIF函数。BEAM模拟器是执行Erlang程序经编译后产出的字节码的地方。
    erlang虚拟机最早的版本是Joe Armstrong编写的,基于栈,叫JAM(Joe's Abstract Machine),很类似WAM(Warren's Abstract Machine)。后来改成基于寄存器的虚拟机,也就是现在的BEAM(Bogdan's Abstract Machine),执行效率有了较大幅度提升,这在Joe的erlang VM演变论文有说到。

    基于栈和基于寄存器的虚拟机有什么区别?

    基于栈(stack-based)的虚拟机的指令长度是固定的,执行多个操作数计算时,会先将操作数做压入栈,由运算指令取出并计算。而基于寄存器(register-based)的指令长度不是固定的,可以在指令中带多个操作数。这样,基于寄存器可以减少指令数量,减少入栈出栈操作,从而减少了指令派发的次数和内存访问的次数,相比开销少了很多。但是,如果利用寄存器做数据交换,就要经常保存和恢复寄存器的结果,这就导致基于寄存器的虚拟机在实现上要比基于栈的复杂,代码编译也要复杂得多

    erlang进程

    erlang进程是在代码执行过程中动态创建和销毁,每个进程都有自己私有的栈和堆。erlang进程是erlang虚拟机进行资源分配和调度的基本单位,erlang代码的执行要通过erlang进程来实现。
    1> spawn(fun() -> m:loop() end).
    <0.34.0>
    或许有人会问,启动erlang节点时没有使用任何进程,这是为什么?实际上,启动erlang节点的代码是运行在shell进程,同样受到erlang虚拟机调度,我们看到的是由shell进程执行后返回的结果。
    为了实现多进程并发,erlang虚拟机实现了进程挂起和调度机制。进程执行代码时会消耗调度次数(Reductions),当调度次数为0时就会挂起这个进程,然后从调度队列中取出第一个进程执行。如果进程在等待新消息时也会被挂起,直到这个进程接收到新消息后,就重新加到调度队列。

    进程的栈和堆

    erlang进程在执行代码的过程中,栈主要用来存放调用帧的本地变量和返回地址,堆则是用来存放执行过程创建的数据。在实现上,栈和堆是在同一个内存区域的。如下图:

    堆栈的内存空间是先申请一块较大的内存后一点一点使用,不够再重新申请一大块,这样避免频繁申请释放内存造成开销。以上,在已分配好的内存区域内,堆从最低的地址向上增长,而栈从最高的地址向下增长。中间堆顶和栈顶的空白区域,表示了进程堆栈还未使用到的空间,使用内存时就向里收缩,不够时就执行gc。这样,内存溢出检查就只要比较栈顶和堆顶就好。
    堆用于存储复杂的数据结构,如元组,列表或大整数。栈被用来存储简单的数据,还有指向堆中复杂数据的数据指针。栈有指针指向堆,但不会有指针从堆到栈。

    寄存器

    前面也提到,对于基于栈的虚拟机,操作数在使用前都会被压到栈,计算时取出。也就是先将本地变量的值压入栈,然后在计算时从栈取出赋值给本地变量。所以,这里有很大开销在本地变量和栈之间的交换上(出入栈)。为此,基于寄存器的虚拟机使用临时变量来保存这个本地变量,这个临时变量也就是寄存器。而且,这个寄存器变量通常都被优化成CPU的寄存器变量,这样,虚拟机访问寄存器变量甚至都不用访问内存,极大的提高了系统的执行速度。
        /*
         * X register zero; also called r(0)
         */
        register Eterm x0 REG_x0 = NIL;
    register修饰符的作用是暗示编译器,某个变量将被频繁使用,尽可能将其保存在CPU的寄存器中,以加快其存储速度。随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的编译器能比程序员做出更好的决定,往往会忽略register修饰符。但是就erlang虚拟机对寄存器变量的使用程度,应该是可以利用到CPU寄存器的好处。

    erlang有哪些寄存器?
    参数寄存器(R0-R1024) R0是最快的,是独立的寄存器变量,其他以reg[N]访问。R0还用来保存函数返回值
    指令寄存器(IP) 引用当前正在执行的指令,可以通过I[N]取到上下文指令。
    返回地址寄存器 (CP,原意Continuation Pointer) 记录当前函数调用的返回地址,在执行完当前函数后返回上一个函数中断处执行后面的代码。
    栈寄存器(EP) 指向栈的栈顶,以E[N]数组形式访问栈帧数据
    堆寄存器 (heap top)指向堆的堆顶,以HTOP[N]数组形式访问堆数据
    临时寄存器(tmp_arg1和tmp_arg2)用于指令实现需要临时变量的场合(尽可能重用临时变量,同时利用CPU寄存器优化)
    浮点寄存器(FR0-FR15)

    其他寄存器:
    'Live' 表示当前需要的寄存器数量,很多指令取这个值来判断是否要执行GC申请新的空间
    'FCALLS' 表示当前进程剩余的调度次数(Reductions)

    若不考虑多调度器,寄存器是所有进程共享的。当虚拟机调度运行某个进程的时候,寄存器就归这个进程使用。当进程被调出的时候,寄存器就给其他进程使用。(进程切换保存进程上下文时,只需要保存指令寄存器IP和当前函数信息,效率很高)

    指令调度

    erlang指令调度实现是一个巨大的switch结构,每一个case语句都对应一个指令操作码(opcode),这样就可以实现指令的分发和执行。但是,switch调度方式实现简单,但效率比较低下。所以,erlang虚拟机使用了goto语法,避免过多的使用switch造成性能损耗;同时,erlang还使用跳转表,在一些高级编译器下(如GCC),利用label-goto语法,效率比较高(针对跳转表的概念,我之前也有文章说明,见这里)。正因为这点,虚拟机调度时解释指令的代价不容忽视,基于寄存器的虚拟机指令少,就要比基于栈高效。
    while(1){
     opcode = *vPC++;
     switch(opcode){
       case i_call_fun:
              ..
           break;
       case call_bif_e:
              ..
           break;
     //and many more..
     }
    };
    字节码在虚拟机中执行,执行过程类似CPU执行指令过程,分为取指,解码,执行3个过程。通常情况下,每个操作码对应一段处理函数,然后通过一个无限循环加一个switch的方式进行分派。

    erlang进程创建时必须指定执行函数,进程创建后就会执行这个函数。从这个函数开始一直到结束,进程都会被erlang虚拟机调度。
    start()->
       spawn(fun() -> fun1(1) end).  %% 创建进程,执行 fun1/1
    
    fun1(A) ->
       A1 = A + 1,
       B = trunc(A1),  %% 执行 trunc/1
       {ok, A1+B}.
    以上,进程在执行函数 trunc/1调用前,会将当前的本地变量和返回地址指针CP写入栈。然后,在执行完这个函数(trunc/1)后再从栈取出CP指令和本地变量,根据CP指针返回调用处,继续执行后面的代码。

    这样,每次函数执行结束时,erlang从栈顶检查并取得CP指针(如果函数内过于简单,没有其他函数调用,就直接读取 (Process* c_p)->cp),然后将CP指针的值赋给指令寄存器IP,同时删除CP栈帧(根据需要还要回收Live借用的栈空间),继续调度执行。
    备注:这里讲到的栈帧删除操作,如CP指针,本地变量数据,删除时只要将栈顶指针向高位移动N个位置,没有GC操作,代价极小。另外,这里也显露出一个问题,如果非尾递归函数调用,erlang需要反复将本地变量和CP指针入栈,容易触发GC和内存复制,引发内存抖动。

    另外,在寄存器方面,函数调用时,erlang虚拟机会将传参写到参数寄存器x(N),然后更新返回地址寄存器CP,在函数调用返回时,会将返回值写到x(0)寄存器。

    Threaded Code(线索化代码)

    前面提到switch指令派发方式,每次处理完一条指令后,都要回到循环的开始,处理下一条指令。但是,每次switch操作,都可能是一次线性搜索(现代编译器能对switch语句进行优化, 以消除这种线性搜索开销,但也是只限于特定条件,如case的数量和值的跨度范围等)。如果是少量的switch case,完全可以接受,但是对于虚拟机来说,有着成百上千的switch case,而且执行频繁非常高,执行一条指令就需要一次线性搜索,确定比较耗性能。如果能直接跳转到执行代码位置,就可以省去线性搜索的过程了。于是在字节码的分派方式上,做了新的改进,这项技术叫作 Context Threading上下文线索化技术,Thread目前都没有合适的中文翻译,我这里意译为线索化,表示其中的线索关系)。

    这里取了Context Threading论文的例子,说明上下文线索化技术(Context Threading)
    1.首先,代码会被编译成字节码

    2.如果是switch派发指令,效率低下

    3.如果是线索化代码(Threaded Code),就直接跳转(goto),无需多次switch

    4.从字节码到最终执行代码的过程。

    左边是编译生成的字节码,中间就是字节码加载后生成的线索化代码,右边是对应的虚拟机实现代码。虚拟机执行时,vpc指向了iload_1指令,在执行iload_1指令操作后根据goto *vpc++ 跳转到下一条指令地址,继续执行,如此反复。这个过程就好像穿针引线,每执行完一条指令,就直接跳转到下一条指令的地址,而不再是Switch Loop那样,每执行一条指令都要做一次switch。(这里,vPC是指虚拟PC指令,在erlang中是IP指针)


    拓展阅读

    BIF(内建函数)

    BIF是erlang的内建函数,由C代码实现,用以实现在erlang层面实现效率不高或无法实现的功能。大多数BIF函数属于erlang模块,也有其他模块的BIF函数,ets或lists,os等
    1> erlang:now().
    {1433,217791,771000}
    2> lists:member(1,[1,2,3]).
    true

    这里重点解释下,BIF代码如何被执行的
    erlang源代码编译时生成bif函数表信息,见 erts\emulator\<machine>\erl_bif_table.c
    Export* bif_export[BIF_SIZE];
    BifEntry bif_table[] = {
        {am_erlang, am_abs, 1, abs_1, abs_1},
        {am_erlang, am_adler32, 1, adler32_1, wrap_adler32_1},
        {am_erlang, am_adler32, 2, adler32_2, wrap_adler32_2},
        {am_erlang, am_adler32_combine, 3, adler32_combine_3, wrap_adler32_combine_3},
        {am_erlang, am_apply, 3, apply_3, wrap_apply_3},
        {am_erlang, am_atom_to_list, 1, atom_to_list_1, wrap_atom_to_list_1},
    typedef struct bif_entry {
        Eterm module;
        Eterm name;
        int arity;
        BifFunction f;  // bif函数
        BifFunction traced;  // 函数调用跟踪函数
    } BifEntry;
    erlang BEAM模拟器启动时会初始化bif函数表,
    init_emulator:
    {
         
         em_call_error_handler = OpCode(call_error_handler);
         em_apply_bif = OpCode(apply_bif);
    
         beam_apply[0] = (BeamInstr) OpCode(i_apply);
         beam_apply[1] = (BeamInstr) OpCode(normal_exit);
         beam_exit[0] = (BeamInstr) OpCode(error_action_code);
         beam_continue_exit[0] = (BeamInstr) OpCode(continue_exit);
         beam_return_to_trace[0] = (BeamInstr) OpCode(i_return_to_trace);
         beam_return_trace[0] = (BeamInstr) OpCode(return_trace);
         beam_exception_trace[0] = (BeamInstr) OpCode(return_trace); /* UGLY */
         beam_return_time_trace[0] = (BeamInstr) OpCode(i_return_time_trace);
    
         /*
          * Enter all BIFs into the export table.
          */
         for (i = 0; i < BIF_SIZE; i++) {
             ep = erts_export_put(bif_table[i].module, //模块名
             bif_table[i].name,
             bif_table[i].arity);
             bif_export[i] = ep;
             ep->code[3] = (BeamInstr) OpCode(apply_bif);
             ep->code[4] = (BeamInstr) bif_table[i].f;  // BIF函数
             /* XXX: set func info for bifs */
             ep->fake_op_func_info_for_hipe[0] = (BeamInstr) BeamOp(op_i_func_info_IaaI);
         }

    下面写个简单的例子说明,

    bif函数编译后,opcode都是 call_bif_e,操作数是函数导出表地址,下面分析下这个opcode的实现:
    /*
     * 以下截取 bif 处理过程
     */
    OpCase(call_bif_e):
        {
     Eterm (*bf)(Process*, Eterm*, BeamInstr*) = GET_BIF_ADDRESS(Arg(0)); // 根据参数获取bif实际执行函数
     Eterm result;
     BeamInstr *next;
     PRE_BIF_SWAPOUT(c_p);
     c_p->fcalls = FCALLS - 1;
     if (FCALLS <= 0) {
        save_calls(c_p, (Export *) Arg(0));
     }
     PreFetch(1, next);
     ASSERT(!ERTS_PROC_IS_EXITING(c_p));
     reg[0] = r(0);
     result = (*bf)(c_p, reg, I); // 执行bif函数
     ASSERT(!ERTS_PROC_IS_EXITING(c_p) || is_non_value(result));
     ERTS_VERIFY_UNUSED_TEMP_ALLOC(c_p);
     ERTS_HOLE_CHECK(c_p);
     ERTS_SMP_REQ_PROC_MAIN_LOCK(c_p);
     PROCESS_MAIN_CHK_LOCKS(c_p);
     if (c_p->mbuf || MSO(c_p).overhead >= BIN_VHEAP_SZ(c_p)) {
         Uint arity = ((Export *)Arg(0))->code[2];
         result = erts_gc_after_bif_call(c_p, result, reg, arity);
         E = c_p->stop;
     }
     HTOP = HEAP_TOP(c_p);
     FCALLS = c_p->fcalls;
     if (is_value(result)) {
         r(0) = result;
         CHECK_TERM(r(0));
         NextPF(1, next);
     } else if (c_p->freason == TRAP) { 
         SET_CP(c_p, I+2);
         SET_I(c_p->i);
         SWAPIN;
         r(0) = reg[0];
         Dispatch();
    }
    上面涉及到一个宏,就是取得bif函数地址。
    #define GET_BIF_ADDRESS(p) ((BifFunction) (((Export *) p)->code[4]))
    根据前面提到的,((Export *) p)->code[4] 就是 bif_table表的中BIF函数的地址。

    扩展指令集

    BEAM文件使用的是有限指令集(limited instruction set),这些指令集会在beam文件被加载时,展开为扩展指令集(extended instruction set)。
    get_list -> get_list_rrx
    get_list ->get_list_rry
    call_bif -> call_bif_e

    扩展指令集和有限指令集的差别是,扩展指令集还描述了操作数类型。
    TypeDescription
    tAn arbitrary term, e.g. {ok,[]}
    IAn integer literal, e.g. 137
    xA register, e.g. R1
    yA stack slot
    cAn immediate term, i.e. atom/small int/nil
    aAn atom, e.g. 'ok'
    fA code label
    sEither a literal, a register or a stack slot
    dEither a register or a stack slot
    rA register R0
    PA unsigned integer literal
    jAn optional code label
    eA reference to an export table entry
    lA floating-point register
    以 call_bif_e 为例, e表示了操作数为函数导出表地址,所以 call_bif_e 可以这样取到bif代码地址
     ((Export *) Arg(0))->code[4]

    文献资料:
    [2] Virtual Machine Showdown: Stack Versus Registers Yunhe Shi, David Gregg, Andrew Beatty
    [3] Context Threading: A flexible and efficient dispatch technique for virtual machine interpreters

    参考:
    http://blog.csdn.net/mycwq/article/details/45653897
    展开全文
  • javascript代码混淆的原理

    千次阅读 2020-09-06 22:19:25
    如何对JavaScript进行保护 ...eval可以将其中参数按照JavaScript的的语法进行解析并执行,其实就是将JavaScript的代码变成了eval参数其中一些字符会被按照特定编码 可以使用eval加密网站进行加密 ...
  • Adaboost算法原理分析和实例+代码(简明易懂)

    万次阅读 多人点赞 2017-05-02 08:52:31
    Adaboost算法原理分析和实例+代码(简明易懂) 【尊重原创,转载请注明出处】 http://blog.csdn.net/guyuealian/article/details/70995333 本人最初了解AdaBoost算法着实是花了几天时间,才明白他基本原理。...
  • 原理类似socket通信,必须有一个作为服务端,一个作为客户端。特别注意两端uuid必须相同。一下就是简单蓝牙聊天代码。 1.客户端代码 package com.example.myblooth; import java.io.IOException; import java....
  • PageRank算法实现源代码原理

    万次阅读 2010-10-12 22:56:00
    最后面是源代码,前面是简单的原理介绍  曾经在网上找了很多关于PageRank的论文,基本上都是介绍原理的,没有找到计算过程的具体实现的源代码,前一阵公司有需要于是写了一个,比 较简单(没有反作弊,...
  • RocketMQ延迟消息的代码实战及原理分析

    万次阅读 多人点赞 2020-07-07 09:44:14
    RocketMQ是一款开源分布式消息系统,基于高可用分布式集群技术,提供低延时、高可靠、万亿级容量、灵活可伸缩消息发布与订阅服务。
  • IDCT的原理以及代码分析

    千次阅读 2015-12-23 17:10:42
    这篇文章基于HM16.2版本解码代码进行原理分析以及代码实现。原理分析: 令: H为编码端变换矩阵,X为像素值矩阵, Y为变换后得到像素值矩阵。 变换公式大体为:。 n为变换矩阵尺寸,在HEVC
  • ob原理的代码

    万次阅读 2016-09-13 16:11:40
  • 写好代码的一些基本原理

    千次阅读 2012-03-09 00:20:08
    v 影响局部化原理F 代码需要通过精心的组织和设计,这样修改某处代码的时候只会影响局部的范围F 当修改一处代码会导致不得不修改多个文件多处地方的代码时,修改的代价就会急剧上升F 当代码中的元素具有局部影响...
  • 随机森林的原理分析及Python代码实现

    万次阅读 多人点赞 2017-03-20 20:35:41
    考虑一个简单例子:在二分类任务中,假定三个分类器在三个测试样本上表现如下图,其中√表示分类正确,×表示分类错误,集成学习结果通过投票法产生,即“少数服从多数”。如下图,在(a)中,每个分类器都只有...
  • R语言igraph包编写网络分析代码原理代码例子,怎么在中国知网上搜索相关论文呢。表示自己查不到,有详细技术路线更好
  • javascript实现代码高亮原理

    千次阅读 2017-01-13 12:36:59
    网上有很多的代码高亮库,之前想自己写一个,但是想不出原理。我一开始想法是把代码赋值给一个变量,然后使用indexOf确定下标,再加上span,再给span颜色。然而,并没有什么卵用…… 2015年08月29日提问...
  • TensorFlow Serving的原理代码实现

    千次阅读 2018-08-22 19:50:30
    本文介绍Tensorflow Serving的原理代码实现, 并提供简要的代码阅读指导. 如何serve一个模型 具体的步骤可以参考官方文档. 主要包括两个部分: 1. 导出模型 1. 启动服务 需要说明的是导出模型部分. 如果要...
  • Simulink 自动代码生成原理

    千次阅读 2018-06-12 22:05:50
    如下图,Simulink模型会先变成一个文本式 .rtw 模型描述...然后把生成算法.c .h 源代码拷贝到自己工程目录下(比如 CCS或者CodeWarrior 或者 VC ),去做编译。 但是也有部分人希望把编译下载工作也集成到...
  • 基础 | batchnorm原理代码详解

    万次阅读 多人点赞 2018-01-12 22:02:03
    本文旨在用通俗易懂的语言,对深度学习的常用算法–batchnorm的原理及其代码实现做一个详细的解读。本文主要包括以下几个部分。 Batchnorm主要解决的问题 Batchnorm原理解读 Batchnorm的优点 B
  • 快速乘的原理及其代码

    千次阅读 2017-08-07 23:53:27
    基本原理 : 代码实现: (logn)inline ll mult_mod(ll a, ll b, ll m) { ll res = 0; while(b){ if(b&1) res = (res+a)%m; a = (a+a)%m; b >>= 1; } return res; }快速乘其实就是来防止有两个较大
  • 代码雨实现原理代码分析

    万次阅读 多人点赞 2016-11-13 22:24:42
    闲来无事,好奇代码雨是怎么实现,早就听说是利用链表,但自己却想不出实现思路,花了两个晚上把代码看完了,分析都在代码里,先看下效果吧。 在贴代码之前先简单说下代码,方便读者加深理解。 代码雨所...
  • Jeesite-代码生成原理

    千次阅读 2016-05-27 18:02:05
    最近在研究jeesite这套框架,我本身是很迷茫,不知道从何学起,看到这么多的代码头疼,但是就头疼着往下看,因为自己清楚也明白不看更头疼,一点点来吧,尽管自己是只小白。以下只是自己理解内容,如有误导...
  • 各种网站挂马的代码原理

    千次阅读 2008-03-31 09:43:00
    上面可能是一般挂马方式,也是初学者玩法,好了,再说深一点点,那就是js文件,好多网站上,你查看源代码都会调用js文件,其中语句为,想到了什么,这里也可以挂马,其中我们可以远程调用自己js,比如:这...
  • 二维码生成原理及解析代码

    万次阅读 多人点赞 2017-12-18 22:35:06
    二维码生成原理及解析代码 自从大街小巷小商小贩都开始布满了腾讯爸爸和阿里爸爸二维码之后,我才感觉到我大天朝共享支付优越性。最近毕业论文写差不多了,在入职之前多学一些东西也是好。这里秉着好奇心...
  • ConcurrentHashMap是Java并发包中提供一个线程安全且高效HashMap实现(若对HashMap实现原理还不甚了解,可参考另一篇文章HashMap实现原理及源码分析),ConcurrentHashMap在并发编程场景中使用频率非常之高...
  • BPE的原理代码解析

    万次阅读 2018-05-29 19:58:53
    BPE:在自然语言处理中,序列到序列模型中(机器翻译、对话)需要设置词表,使用较小词表,有助于提高系统性能。BPE在欧洲语系可能表现更为有效一些,主要由于欧洲语系中存在词缀等概念。 BPE训练 BPE大概...
  • 代码生成器原理

    千次阅读 2014-11-16 11:56:24
    C:一些辅助工具、基础组件,是为了加强自动产生代码的功能、简化代码复杂性,进行合理的分工协作用的。 D:自动生成的代码部分,用PowerDesigner设计简洁明了、易于沟通理解,代码分自动生成及人工部分,为了重复...
  • Android代码入侵原理解析(一)

    千次阅读 2017-05-19 14:50:06
    Original 2017-05-06 付超红 滴滴安全应急响应...现在,付超红详细整理了《Android代码入侵原理解析》,在滴滴安全应急响应中心微信公众号开始连载。技术干货满满,敬请关注。 1.代码入侵原理
  • 平衡二叉树实现原理(代码实现)

    千次阅读 2017-03-04 08:53:01
    平衡二叉树实现原理(代码实现)
  • 编译原理代码优化

    万次阅读 多人点赞 2017-12-18 10:43:49
    编译原理出于代码编译模块化组装考虑,一般会在语义分析阶段生成平台无关中间代码,经过中间代码的代码优化,而后作为输入进入代码生成阶段,产生最终运行机器平台上目标代码,再经过一次目标代码级别...
  • 多目标跟踪 DeepSort 代码原理分析

    千次阅读 2019-09-02 17:51:27
    关于 Deep Sort 一些理解代码部分原理部分 最近学习了多目标跟踪算法–Deep Sort,打个总结。 我认为是目前工程上效果还不错跟踪算法。首先,虽然算法类似于 two stages结构,没法完成端到端训练,但是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 132,826
精华内容 53,130
关键字:

代码的原理