C语言解释器的实现--让脚本跑起来(六)
目录:
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语言解释器的实现--让脚本跑起来(六)相关推荐
- 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言
用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言 用c语言手搓一个600行的类c语言解释器: 给编程初学者的解释器教程(1)- 目标和前言 用c语言手搓一个60 ...
- 脚本编程语言python语言-python算的上脚本语言吗
脚本语言泛指单用作简单編程任务如shell scripts.脚本语言是一种介乎于 HTML 和诸如 JAVA . Visual Basic . C++ 等编程语言之间的一种特殊的语言,尽管它更接近后者 ...
- java 脚本语言交互_Java学习笔记--脚本语言支持API
Java语言的动态性之脚本语言支持API 随着Java平台的流行,很多的脚本语言(scripting language)都可以运行在Java虚拟机啊上,其中比较流行的有JavaScript.JRuby ...
- html支持的脚本语言,能不能让日志内容在支持html语言的同时支持一下脚本语言,拜托!拜托!...
日志里经常引用一个网站的歌,他们的歌曲网址有部分常改变,比如说dm1.hting.com/ht//部分,没准哪天就变成dm1.hting.com/as//. 所以我想在内容模板里声明一个变量.比如说用 ...
- 自制计算机语言,3个步骤实现简单语言解释器(自制简易编程语言)
前段时间,我用 javascript 重新编写了一个 16 位的虚拟机,用它实现了一个定制的 CPU 架构和汇编语言,汇编器和调试器.虽然从头编一个语言可以完全实现自己的自定义目标,但过程却及其复杂. ...
- python语言+selenium自动化,编写脚本调用Chrome、Firefox浏览器打开百度网站
python语言+selenium自动化,编写脚本调用Chrome.Firefox浏览器打开百度网站 目标:初始化一个webdriver实例对象driver,通过webdriver.Chrome()和 ...
- 最新易语言调用大漠插件制作脚本入门教程
最新易语言调用大漠插件制作脚本入门教程 这是田野学院的一套零基础视频.学习做辅助脚本入门还是不错的. https://pan.baidu.com/s/1BWd2_kIjL6OLE7q-VcDVlw 提 ...
- C语言/C++基础之火车快跑
C语言/C++基础之火车快跑 程序之美 前言 主体 运行效果 代码示例 代码分析 结束语 程序之美 前言 火车 作者:[土耳其]贾希特·塔朗吉 译者:余光中 朗读:月汐 去什么地方呢 这么晚了 美丽的 ...
- 我用c语言把何同学的代码跑起来了
我用c语言把何同学的代码跑起来了 原版代码 代码分析 代码实现 代码执行结果 免责声明:仅供娱乐,只是展示这段代码在理论上是可行的. 原版代码 首先,先来看下视频中何同学的这两段代码: 代码分析 首先 ...
- C语言/C++常见习题问答集锦(七十六)之玫瑰花寄语
C语言/C++常见习题问答集锦(七十六)之玫瑰花寄语 程序之美 在古希腊神话中,玫瑰花集爱与美于一身,既是美神的化身,又溶进了爱神的血液.可以说,在世界范围内,玫瑰是用来表达爱情的通用语言,形成了永不 ...
最新文章
- 数字图像处理笔记二 - 图片缩放(最近邻插值(Nearest Neighbor interpolation))
- 项目实战12.1—企业级监控工具应用实战-zabbix安装与基础操作
- linux配置caffe环境,最全caffe安装踩坑记录(Anaconda,nvidia-docker,linux编译)
- datetime数据类型_系统数据类型
- Apache 2.2 虚拟主机配置(本人推荐的)
- 计算机二级办公室软件应用选择题,计算机二级,办公软件高级应用技术有没有选择题和判断题的...
- Trie树实现[ java ]
- left join on用法_MySQL 多表查询 quot;Joinquot;+“case when”语句总结
- java ognl表达式_java -------ognl表达式入门
- java获取汉字的拼音首字母_java获取汉字的拼音首字母
- java类、抽象类、接口的继承规则
- PostgreSQL数据库从入门到精通
- Camel可视化操作(结合Gooflow)
- 博弈论——拍卖会(Auctions)
- ubifs代码解析之ubi_attach_mtd_dev--个人学习
- 导数公式、导数运算法则、复合函数求导、幂指函数求导
- 5.学城项目 支付宝支付
- win10开启hdr功能屏幕泛白如何解决?
- 遭遇Trojan.DL.Win32.Autorun.yuz,Trojan.Win32.Inject.gh,Trojan.Win32.Agent.zsq等
- dvi转vga接口图及相关接法
热门文章
- 【2017 United Kingdom and Ireland Programming Contest (UKIEPC 2017)】Knightsbridge Rises【最大流+路径输出】
- t14m4t:一款功能强大的自动化暴力破解工具
- 617.合并二叉树(力扣leetcode) 博主可答疑该问题
- c语言如何获取错误码,错误码列表
- linux openfire mysql_Linux系统安装openfire及其如何后台运行openfire
- Python之旅Day14 JQuery部分
- android 模仿今日头条ViewPager+TabLayout
- ORACLE在线切换undo表空间
- 关于Javascript表单验证
- 什么是Ajax? (转载于疯狂客的BLOG)