Red/System编译器实现分析(2)
在开始讲解如何生成机器代码之前,我们先认识一些重要的数据结构: -- 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 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 '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][ ; 算法细节就不细说了。 ; 好吧,算我偷懒 ;-) ]
- 将变量放入符号表。此时符号表内容为 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]] ] ;-- 占位空指针开始于第二个字节处
到目前为止,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>

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











PHP 개발에서 PHP의 CURL 라이브러리를 사용하여 JSON 데이터를 보내면 종종 외부 API와 상호 작용해야합니다. 일반적인 방법 중 하나는 컬 라이브러리를 사용하여 게시물을 보내는 것입니다 ...

Docker 환경을 사용할 때 Docker 환경에 Extensions를 설치하기 위해 PECL을 사용하여 오류의 원인 및 솔루션. 종종 일부 두통이 발생합니다 ...

Linux 터미널에서 Python 사용 ...

이 기사는 데비안 시스템에서 PostgresQL 데이터베이스를 모니터링하는 다양한 방법과 도구를 소개하여 데이터베이스 성능 모니터링을 완전히 파악할 수 있도록 도와줍니다. 1. PostgreSQL을 사용하여 빌드 인 모니터링보기 PostgreSQL 자체는 데이터베이스 활동 모니터링 활동을위한 여러보기를 제공합니다. PG_STAT_REPLICATION : 특히 스트림 복제 클러스터에 적합한 복제 상태를 모니터링합니다. PG_STAT_DATABASE : 데이터베이스 크기, 트랜잭션 커밋/롤백 시간 및 기타 주요 지표와 같은 데이터베이스 통계를 제공합니다. 2. 로그 분석 도구 PGBADG를 사용하십시오

DebianHadoop 클러스터의 성능을 향상 시키려면 하드웨어, 소프트웨어, 리소스 관리 및 성능 튜닝에서 시작해야합니다. 다음은 몇 가지 주요 최적화 전략 및 제안입니다. 1. 하드웨어 및 시스템 구성 선택 하드웨어 구성을 선택하십시오. 하드웨어 구성을 선택하십시오. 실제 애플리케이션 시나리오에 따라 적절한 CPU, 메모리 및 스토리지 장치를 선택하십시오. SSD 가속 I/O : I/O 작동 속도를 향상시키기 위해 가능한 한 SSD (Solid State Hard Drive)를 사용하십시오. 메모리 확장 : 더 큰 데이터 처리 및 작업에 대처하기 위해 Namenode 및 Datanode 노드에 충분한 메모리를 할당합니다. 2. 소프트웨어 구성 최적화 Hadoop 구성 파일 조정 : Core-Site.xml : HDFS 기본 파일 시스템 구성

Go Language에서 메시지 대기열을 구현하기 위해 Redisstream을 사용하는 문제는 Go Language와 Redis를 사용하는 것입니다 ...

Console.log 출력의 차이의 근본 원인에 대한 심층적 인 논의. 이 기사에서는 Console.log 함수의 출력 결과의 차이점을 코드에서 분석하고 그에 따른 이유를 설명합니다. � ...
