目录:

1.脚本的执行要素

2.栈的模拟.

3.变量在栈中的地址计算

4.函数的调用过程

5.命令的解析

6.C的库函数调用

在前面的文章中,我主要讲解了语言的解析部分,最终我们生产了脚本的中间代码。接下来,将是一个最困难的时刻,怎么解析执行中间代码!
  执行代码其实是经过一定处理后的中间代码的另外一种表示。正如前面提到的,我们的中间代码是三元组的形式,比如:c = a + b * c; 可以表示成 @1 = b * c; @2 = a + @1; @3 = c = @2;但是,这种中间代码还得经过一定的转换才能更方便我们解析执行。接下来,我将一步步的说明,中间代码被执行的每个过程。

1.脚本的执行要素
  一个脚本要被执行,必须要为它创建一个环境,就想操作系统中为没有程序创建一个进程一样。
  一个C语言程序,其实只有几个要素:运算符,变量,函数。所以,一个C脚本要被执行,首先,它必须具备中间代码命令的解析;其次,必须要有变量的内存空间;再次,必须要有函数的调用解析,即函数调用栈的模拟。所以,一个脚本的执行,最重要的是变量内存的分配和栈的维护,还有命令的执行。

2.栈的模拟.
  如果你熟悉C的调用栈,那么这个就很容易理解了。我们先不说函数调用时,栈的变化,姑且先说明一个函数的执行过程。还是这个例子:

    int add( int a, int b )    {int c, d, e;        c = a + b;    }

那么它的中间代码是这样的:

  @1 = a + b;  @2 = c = @1;

在执行时,我们不能直接根据变量名去查找变量,这样既麻烦,而且效率也很低;而是应该根据变量的地址去存取变量。但是变量保存在哪里,怎么计算,这就是引入栈的原因了。我们首先看看上面的函数对应的栈:

  address  var  --------------  -20      a  -16      b  -12      eip  -8       esp   -4       return-address0        <-------------------esp0        c4        d8        e12       @116       @2  --------------

eip表示调用该函数时,当前的命令位置,当函数返回时,我们要pop出这个eip,继续执行eip的下一条命令。
  esp表示调用该函数时,当前函数的变量空间的开始位置,即调用者的esp,当函数返回时,我们要还原该esp。esp的意思是,一个函数的变量空间在栈中的基地址。每个函数在执行时,我们都会有一个固定的esp,每个变量在栈中都有具体的位置,这些变量相对于esp的距离都是固定。
  return-address主要是保存函数返回值得地址,即函数在被调用时产生的临时变量。在函数返回时,返回值会被填入该地址中。这样调用者就可以从这个临时变量中获取调用结果了。例如:int a = add( 3, 4 );  那么,return-address就应该是a的地址,或者是另一个临时变量的地址,总之,最后要为a赋值,必须依赖于return-address。

有了这个栈,我们的中间代码就应该被处理成这样:

  @1 = a + b   对应于  [esp+12] = [esp-20] + [esp-16];  @2 = c = @1  对应于  [esp+16] = [esp+0] = [esp+12];

上述的代码中"[xx]"表示地址xx中的值,因为esp在执行时,每个函数在实现时esp是固定的,所以我们可以省略esp不写,所以上面的代码可以改为:

  [12] = [-20] + [-16];  [16] = [0] = [12]; 

为了方便处理,我们将中间变量也放到栈里面,但是,中间变量的地址是可以被重用的,因为一条语句被执行完后,这条语句的中间变量就不会再被用到,所以,上一条语句的中间变量是可以被回收的。

3.变量在栈中的地址计算
  首先,每个函数中,都有一个固定的esp,可以视为该函数在栈中的起始位置。然后其他的变量都被表示为距离esp的值,即偏移量。例如上面的例子,我们在解析出一个函数的中间代码时,就知道了这个函数的所有的局部变量,形参列表,并且知道这些变量的类型。所有我们可以根据类型的大小,计算他们在栈中的位置。

4.函数的调用过程
  例如有下面的代码:

    int add( int a, int b )    {int c, d, e;        c = a + b;return c;    }int main(){        add( 4, 5 );          <---------①    }

当执行到①的时候,他的栈空间是这样的:

  address offset    var  --------------------------   ....15988   -12      eip15992   -8       esp15996   -4       return-address16000   0        <-------------------(main-esp假设为16000)

16000   -20      416004   -16      516008   -12      eip                 eip指向add(4,5)的下一条命令16012   -8       main-esp            1600016016   -4       return-address0        <-------------------(add-esp = 160000+20 = 160020 )16020   0        c16024   4        d16028   8        e16032   12       @116036   16       @2   ....  ---------------------------

当add函数返回时,该函数的栈会被回收。即变成:

  address offset    var  --------------------------   ....15988   -12      eip15992   -8       esp15996   -4       return-address16000   0        <-------------------(main-esp假设为16000)  --------------------------

5.命令的解析
  每条中间变量都由一个操作符和若干个操作数组成,这里没办法罗列出所有的操作符的解析。仅仅说明一个最简单的情况:
      @1 = a + b   对应于  [esp+12] = [esp-20] + [esp-16];
  这条中间代码,它的操作符是"+", 操作数是[-20],[-16], 目标操作数是[12]。所以解析过程相当简单,变成C代码就是这样的:
      *(int*)(esp+12) = *(int*)(esp-12) + *(int*)(esp-16);
  实际上我就是这么干的,只不过是为了适应各种命令的解析,显得比较的烦死,但是原理都是一样的。这里的int类型,是操作数中包含的类型信息,这是必须的,在中间代码的处理时,每个变量的类型都必须被确定,否则代码在执行时,没办法知道它所占的内存空间。
 
  这是每条命令的定义,它其实是一个双向链表,这有利于跳转语句的跳转。

typedef struct _cmd{char cmd;struct{char type;int  size;        union{            int64 i;double d;        }d;    }d[3];int ex;struct _cmd * next;struct _cmd * pre;}cmd_t;

  cmd   操作符  d[3]  操作数  ex    某些命令的附加信息  next  下一条命令  pre   前一条命令

6.C的库函数调用
  C语言有它的库函数,如果我们的解释器要自己实现这些库函数的话,那么工作量就大大增加了,有什么办法直接调用系统的库函数呢。如果能做到这点,那么也就能解释器的使用者提供更加强大的交换方式----即使用者可以注册自己的函数,供脚本使用。想了很多方法,唯有用汇编了。具体的做法就是:
  例如,脚本有一行代码 fopen( "test", "r" );
  那么,我们获取了函数名fopen,发现他是被注册的函数,所以我们得到fopen的函数指针,假设是fptr.所以这条语句的执行是这样的:
  push 0x123243     ; "test"的地址
  push 0x894982     ; "r"的地址
  call fptr         ; 调用系统的fopen函数
  ...

我写了一个汇编代码,为了在liunx下顺利的移植代码,使用了nasm(我原来是使用masm)。:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;nasm -fcoff call.asm -o outfile;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

[bits 32]                      ;使用32位模式的处理器[section .text]

%define WIN32%ifdef  WIN32%define _funptr _asm_funptr    ;保存函数指针%define _argtab _asm_argtab    ;参数列表%define _argtye _asm_argtye    ;参数类型列表%define _argnum _asm_argnum    ;参数个数%define _call    _asm_call%else%define _funptr  asm_funptr%define _argtab  asm_argtab%define _argtye  asm_argtye%define _argnum  asm_argnum%define _call     asm_call%endif

extern _funptrextern _argtabextern _argtyeextern _argnumglobal _call

_call:xor  edx, edxxor  ecx, ecxmov  ebx, [_argnum]cmp  ebx, 0jz   endbeg:cmp  dword[_argtye + ecx], 1jz   ftpush dword[_argtab+ecx]add  edx,4jmp  feft:fld  dword [_argtab+ecx]sub  esp,8fstp qword [esp]add  edx,8fe:add  ecx, 8sub  ebx, 1jnz  begend:mov  [_argnum], edxmov  eax, [_funptr]call eaxadd  esp, [_argnum]ret

转载于:https://www.cnblogs.com/linxr/archive/2012/03/15/2398635.html

C语言解释器的实现--让脚本跑起来(六)相关推荐

  1. 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言

    用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言 用c语言手搓一个60 ...

  2. 脚本编程语言python语言-python算的上脚本语言吗

    脚本语言泛指单用作简单編程任务如shell scripts.脚本语言是一种介乎于 HTML 和诸如 JAVA . Visual Basic . C++ 等编程语言之间的一种特殊的语言,尽管它更接近后者 ...

  3. java 脚本语言交互_Java学习笔记--脚本语言支持API

    Java语言的动态性之脚本语言支持API 随着Java平台的流行,很多的脚本语言(scripting language)都可以运行在Java虚拟机啊上,其中比较流行的有JavaScript.JRuby ...

  4. html支持的脚本语言,能不能让日志内容在支持html语言的同时支持一下脚本语言,拜托!拜托!...

    日志里经常引用一个网站的歌,他们的歌曲网址有部分常改变,比如说dm1.hting.com/ht//部分,没准哪天就变成dm1.hting.com/as//. 所以我想在内容模板里声明一个变量.比如说用 ...

  5. 自制计算机语言,3个步骤实现简单语言解释器(自制简易编程语言)

    前段时间,我用 javascript 重新编写了一个 16 位的虚拟机,用它实现了一个定制的 CPU 架构和汇编语言,汇编器和调试器.虽然从头编一个语言可以完全实现自己的自定义目标,但过程却及其复杂. ...

  6. python语言+selenium自动化,编写脚本调用Chrome、Firefox浏览器打开百度网站

    python语言+selenium自动化,编写脚本调用Chrome.Firefox浏览器打开百度网站 目标:初始化一个webdriver实例对象driver,通过webdriver.Chrome()和 ...

  7. 最新易语言调用大漠插件制作脚本入门教程

    最新易语言调用大漠插件制作脚本入门教程 这是田野学院的一套零基础视频.学习做辅助脚本入门还是不错的. https://pan.baidu.com/s/1BWd2_kIjL6OLE7q-VcDVlw 提 ...

  8. C语言/C++基础之火车快跑

    C语言/C++基础之火车快跑 程序之美 前言 主体 运行效果 代码示例 代码分析 结束语 程序之美 前言 火车 作者:[土耳其]贾希特·塔朗吉 译者:余光中 朗读:月汐 去什么地方呢 这么晚了 美丽的 ...

  9. 我用c语言把何同学的代码跑起来了

    我用c语言把何同学的代码跑起来了 原版代码 代码分析 代码实现 代码执行结果 免责声明:仅供娱乐,只是展示这段代码在理论上是可行的. 原版代码 首先,先来看下视频中何同学的这两段代码: 代码分析 首先 ...

  10. C语言/C++常见习题问答集锦(七十六)之玫瑰花寄语

    C语言/C++常见习题问答集锦(七十六)之玫瑰花寄语 程序之美 在古希腊神话中,玫瑰花集爱与美于一身,既是美神的化身,又溶进了爱神的血液.可以说,在世界范围内,玫瑰是用来表达爱情的通用语言,形成了永不 ...

最新文章

  1. 数字图像处理笔记二 - 图片缩放(最近邻插值(Nearest Neighbor interpolation))
  2. 项目实战12.1—企业级监控工具应用实战-zabbix安装与基础操作
  3. linux配置caffe环境,最全caffe安装踩坑记录(Anaconda,nvidia-docker,linux编译)
  4. datetime数据类型_系统数据类型
  5. Apache 2.2 虚拟主机配置(本人推荐的)
  6. 计算机二级办公室软件应用选择题,计算机二级,办公软件高级应用技术有没有选择题和判断题的...
  7. Trie树实现[ java ]
  8. left join on用法_MySQL 多表查询 quot;Joinquot;+“case when”语句总结
  9. java ognl表达式_java -------ognl表达式入门
  10. java获取汉字的拼音首字母_java获取汉字的拼音首字母
  11. java类、抽象类、接口的继承规则
  12. PostgreSQL数据库从入门到精通
  13. Camel可视化操作(结合Gooflow)
  14. 博弈论——拍卖会(Auctions)
  15. ubifs代码解析之ubi_attach_mtd_dev--个人学习
  16. 导数公式、导数运算法则、复合函数求导、幂指函数求导
  17. 5.学城项目 支付宝支付
  18. win10开启hdr功能屏幕泛白如何解决?
  19. 遭遇Trojan.DL.Win32.Autorun.yuz,Trojan.Win32.Inject.gh,Trojan.Win32.Agent.zsq等
  20. dvi转vga接口图及相关接法

热门文章

  1. 【2017 United Kingdom and Ireland Programming Contest (UKIEPC 2017)】Knightsbridge Rises【最大流+路径输出】
  2. t14m4t:一款功能强大的自动化暴力破解工具
  3. 617.合并二叉树(力扣leetcode) 博主可答疑该问题
  4. c语言如何获取错误码,错误码列表
  5. linux openfire mysql_Linux系统安装openfire及其如何后台运行openfire
  6. Python之旅Day14 JQuery部分
  7. android 模仿今日头条ViewPager+TabLayout
  8. ORACLE在线切换undo表空间
  9. 关于Javascript表单验证
  10. 什么是Ajax? (转载于疯狂客的BLOG)