在开始讲解如何生成机器代码之前,我们先认识一些重要的数据结构: -- job ; 每个文件对应一个job对象,该对象会在整个流程各个步骤间传递。 job-class: context [ format: ;-- PE | ELF | Mach-o type: ;-- exe | obj | lib | dll target: ;-- CPU identifi
在开始讲解如何生成机器代码之前,我们先认识一些重要的数据结构:
-- job ; 每个文件对应一个job对象,该对象会在整个流程各个步骤间传递。 job-class: context [ format: ;-- 'PE | 'ELF | 'Mach-o type: ;-- 'exe | 'obj | 'lib | 'dll target: ;-- CPU identifier divs: ;-- code/data divs flags: ;-- global flags sub-system: ;-- target environment (GUI | console) symbols: ;-- symbols table buffer: none ] -- globals ; 全局名字空间 -- locals ; 局部名字空间,比如函数内部 locals: none globals: make hash! 40 ;-- [name [type]] -- code-buf ; 存放代码,对应PE文件的代码节,二进制格式存放 -- data-buf ; 存放全局变量,对应PE文件的数据节,二进制格式存放 -- symbols ; 这个就是符号表了,emitter和job引用同一个symbols table code-buf: make binary! 10'000 data-buf: make binary! 10'000 symbols: make hash! 200 ;-- [name [type address [relocs]] ...]上篇文章讲到函数 comp-expression,那就继续吧。
comp-expression expr ;将expr展开,comp-expression [a: 1] comp-expression: func [tree /local name value][ ; tree? 没错,程序的结构本质上是一棵树 switch/default type?/word tree/1 [ set-word! [ name: to-word tree/1 ; name: a value: either block? tree/2 [ ; value: 1 comp-expression tree/2 'last ][ tree/2 ] add-symbol name value ; 将变量 a 放入符号表 ... emitter/target/emit-store name value ; 生成机器码 ] ... ][...] ]看看在函数 add-symbol 中做了些什么?
; add-symbol 'a 1 add-symbol: func [name [word!] value /local type new ctx][ ctx: any [locals globals] ; 在全局名字空间里,ctx: globals unless find ctx name [ type: case [ ; type: integer! ... 'else [type?/word value] ; value: 1 ] append ctx new: reduce [name compose [(type)]] ; append ctx [a [integer!]] if ctx = globals [emitter/set-global new value] ; 跟进函数 emitter/set-global ] ] ; set-global [a [integer!]] 1 set-global: func [spec [block!] value /local type base][ either 'struct! = type: spec/2/1 [ ; spec/2/1: integer! ... ][ base: tail data-buf store-global value select datatypes type ; 最后一个函数了,坚持住! ] spec: reduce [spec/1 reduce ['global (index? base) - 1 make block! 5]] ;-- zero-based ; spec最终的结果是什么? ; 因为 a 是第一个变量,所以开始于 data-buf 的第 0 个字节处 ; spec: [a [global 0 []] append symbols new-line spec yes spec ] datatypes: to-hash [ int8! 1 signed int16! 2 signed int32! 4 signed integer! 4 signed ; select datatypes type "type" 为 integer! int64! 8 signed ... ] ; store-global 1 4 ; 这函数的职责是将数据存放到 data-buf 中。 ; 比如一个整数值为:0x08040201 (十六进制表示) ; 存放在内存中有两种形式:little-endian 和 big-endian ; 存放成哪种形式是由系统架构决定的,x86使用的是little-endian ; 所以要按照如下形式存放:0x01020408 store-global: func [value size /local ptr][ ; 算法细节就不细说了。 ; 好吧,算我偷懒 ;-) ]函数 add-symbol 执行结束,做的事情还不少呢。总结一下:
- 将变量放入符号表。此时符号表内容为 symbols: [ [a [global 0 []] ]
- 将变量放入全局名字空间。此时 globals: [ [a [integer!]] ]
- 将变量 a 的值 1 存入 data-buf。此时 data-buf: #{01000000}
可以看出 add-symbol 并不是一个’好‘函数,一个’好‘的函数职责应该是单一的。不过这是正常的,每个程序员在快速实现软件功能的阶段,都或多或少会写一些这样的代码。但一个优秀的程序员会在以后的迭代中不断改善,去掉这些坏味道。
函数add-symbol返回后,看看comp-expression,只剩下一行代码了,:- ) 这一行代码目的的机器码生成。
emitter/target/emit-store name value ; emit-store 'a 1 ; 目前只实现了IA32目标代码的生成 ; target: do %targets/IA32.r ; 函数 emit-store 在文件 IA32.r 中 emit-store: func [name [word!] value [integer! word! string! struct!] /local spec][ ... switch type?/word value [ integer! [ emit-variable name #{C705} ;-- gcode: MOV [name], value ; (32-bit only!!!) #{C745} ;-- lcode: MOV [ebp+n], value ; (32-bit only!!!) emit to-bin32 value ] ... ] ] emit-variable: func [ name [word!] gcode [binary!] lcode [binary! block!] /local offset ][ ... ;-- global variable case emit gcode emit-reloc-addr emitter/symbols/:name ; emit-reloc-addr [a [global 0 []] ] emit-reloc-addr: func [spec [block!]][ append spec/3 emitter/tail-ptr ;-- 注意这里保存重定位的地址 emit void-ptr ;-- emit void addr #{00000000}, reloc later ... ] emit: func [bin [binary! char! block!]][ append emitter/code-buf bin ]
emitter部分的代码本身不复杂,但要看懂需要有一定的x86汇编语言编程基础。汇编指令对应的机器指令可参考《英特尔? 64 和 IA-32 架构开发人员手册》。结果如下
; 将 1 存放到内存地址 00000000 处。 ; 目前不确定数据段(data-buf)中的变量 a 相对于exe文件开头的位置 ; 这个位置要到最后生成exe文件时,才能确定。 ; 所以使用空指针占位 ; code-buf中内容,注意值 1 按照little-endian格式存放 #{C7050000000001000000} ;-- MOV [00000000], 1 ; 符号表更新,加入了重定位的地址 ; 也就是占位空指针的起始位置,zero-based symbols: [ [a [global 0 [2]] ] ;-- 占位空指针开始于第二个字节处编译器是直接将代码翻译成机器码的,没有像编译原理教程上所说的先生成中间代码,再把中间代码翻译成机器码。直接生成机器码的好处是能够以最快速度的实现编译器,缺点是没法进行有力的优化。不知道大家发现没有,其实我们生产的这一段代码就是多余的。 ; -) 现阶段Red/System的目的是功能的完成,性能不是考虑的重点,所以没有使用中间代码。一但Red完成,使用Red重写Red/System的时候会引入中间代码,从而可以进行各种优化,使Red/System编译生成的程序达到C语言级别的速度。
到目前为止,Compiling部分已经完成。经典的编译原理课程一般到这里为止。接下来的一步称为Linking,也就是将我们的编译结果按照操作系统要求的格式拼装成文件,以便操作系统执行。Windows上使用的是 PE Format (Specification下载), Linux上使用的是
ELF Format (Specification下载)。网络上很多分析 PE 文件格式的文章,基本上都是在Microsoft公开 PE 文件格式之前,大牛们通过逆向工程得到的成果。这里向前辈们表示敬意!现在Microsoft已经公开的详细的文档,强烈建议阅读官方文档。
数据和代码都在data-buf和code-buf中准备好了,拼装成的PE文件格式如下:
+-------------------+ | DOS-stub | +-------------------+ | file-header | +-------------------+ | optional header | <p>当所有文件头(DOS-stub,file-header,optional header和div headers)都生成好以后,code div和data div的相对于文件起始处的偏移地址也就确定了。这时可以将原来预留在code-buf中的占位空指针替换为数据段中变量实际的地址,这个地址是相对于文件起始处的偏移量。函数’resolve-data-refs‘用于完成这个工作。要完成这项工作需要三个结构 data-buf, code-buf 和 symbols。</p> <p>结构 optional header 中包含一个成员 AddressOfEntryPoint,是程序的入口点地址。当Windows系统加载可执行文件的时候,会读取 AddressOfEntryPoint 中的内容,然后跳转的这个地址,开始运行程序。因为我们的代码放在div 1,所以我们把 AddressOfEntryPoint 设置成div 1的地址。</p> <p>整个编译的过程完成了,是不是比想象中的要简单。: -) 当然了,之所以简单是因为我们的编译的程序几乎什么都没做。先对流程有一个总体的认识,能增加深入下去的信心。接下来会讲解稍复杂的部分:控制结构(if, while)以及函数。敬请期待!</p>

MySQL值得学习,因为它是强大的开源数据库管理系统,适用于数据存储、管理和分析。1)MySQL是关系型数据库,使用SQL操作数据,适合结构化数据管理。2)SQL语言是与MySQL交互的关键,支持CRUD操作。3)MySQL的工作原理包括客户端/服务器架构、存储引擎和查询优化器。4)基本用法包括创建数据库和表,高级用法涉及使用JOIN连接表。5)常见错误包括语法错误和权限问题,调试技巧包括检查语法和使用EXPLAIN命令。6)性能优化涉及使用索引、优化SQL语句和定期维护数据库。

MySQL适合初学者学习数据库技能。1.安装MySQL服务器和客户端工具。2.理解基本SQL查询,如SELECT。3.掌握数据操作:创建表、插入、更新、删除数据。4.学习高级技巧:子查询和窗口函数。5.调试和优化:检查语法、使用索引、避免SELECT*,并使用LIMIT。

MySQL通过表结构和SQL查询高效管理结构化数据,并通过外键实现表间关系。1.创建表时定义数据格式和类型。2.使用外键建立表间关系。3.通过索引和查询优化提高性能。4.定期备份和监控数据库确保数据安全和性能优化。

MySQL是一个开源的关系型数据库管理系统,广泛应用于Web开发。它的关键特性包括:1.支持多种存储引擎,如InnoDB和MyISAM,适用于不同场景;2.提供主从复制功能,利于负载均衡和数据备份;3.通过查询优化和索引使用提高查询效率。

SQL用于与MySQL数据库交互,实现数据的增、删、改、查及数据库设计。1)SQL通过SELECT、INSERT、UPDATE、DELETE语句进行数据操作;2)使用CREATE、ALTER、DROP语句进行数据库设计和管理;3)复杂查询和数据分析通过SQL实现,提升业务决策效率。

MySQL的基本操作包括创建数据库、表格,及使用SQL进行数据的CRUD操作。1.创建数据库:CREATEDATABASEmy_first_db;2.创建表格:CREATETABLEbooks(idINTAUTO_INCREMENTPRIMARYKEY,titleVARCHAR(100)NOTNULL,authorVARCHAR(100)NOTNULL,published_yearINT);3.插入数据:INSERTINTObooks(title,author,published_year)VA

MySQL在Web应用中的主要作用是存储和管理数据。1.MySQL高效处理用户信息、产品目录和交易记录等数据。2.通过SQL查询,开发者能从数据库提取信息生成动态内容。3.MySQL基于客户端-服务器模型工作,确保查询速度可接受。

构建MySQL数据库的步骤包括:1.创建数据库和表,2.插入数据,3.进行查询。首先,使用CREATEDATABASE和CREATETABLE语句创建数据库和表,然后用INSERTINTO语句插入数据,最后用SELECT语句查询数据。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

SublimeText3汉化版
中文版,非常好用

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

Dreamweaver CS6
视觉化网页开发工具

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

禅工作室 13.0.1
功能强大的PHP集成开发环境