news 2026/9/20 23:35:43

.NET CoreCLR ilasm 解析器生成指南:使用 Bison 从 asmparse.y 生成 asmparse.cpp

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
.NET CoreCLR ilasm 解析器生成指南:使用 Bison 从 asmparse.y 生成 asmparse.cpp
  • 语言运行时
  • 标准库
  • JIT编译
  • 编译器

【免费下载链接】runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.

项目地址:https://gitcode.com/GitHub_Trending/runtime6/runtime
点击查看免费下载

导读

本文介绍 .NET CoreCLR 中 IL 汇编器(ilasm)的语法解析器(Parser)生成流程:如何在 Windows、Unix 以及 Docker 环境中使用 Bison 从 BNF 语法文件asmparse.y生成 C++ 解析器prebuilt/asmparse.cpp,并说明配套的 GrammarExtractor 工具如何从同一语法文件提取面向文档与工具链的 BNF 文法。读完本文,你将掌握 ilasm 解析器的完整再生成方法、语法文件的核心结构、预生成产物在构建系统中的作用,以及如何在容器环境中快速完成解析器重建。


一、背景:ilasm 的语法解析器从何而来

ilasm(IL Assembler)是 .NET CoreCLR 运行时的一部分,负责把 MSIL 汇编文本(.il 文件)汇编为托管程序集。它的核心语法定义存放在 src/coreclr/ilasm/asmparse.y(约 2000 行),这是一个标准的 Yacc(Yet Another Compiler-Compiler)语法文件,采用 BNF(Backus-Naur Form)文法描述 IL 汇编语言的词法单元与语法规则。

与很多使用 Yacc 的项目不同,ilasm并不在每次构建时调用 Bison,而是将生成的解析器源文件预先提交到仓库中:

  • src/coreclr/ilasm/prebuilt/asmparse.cpp:Bison 生成的 C++ 解析器主体;
  • src/coreclr/ilasm/prebuilt/asmparse.grammar:从同一语法文件提取的纯 BNF 文法文本。

构建系统直接编译这份预生成文件。在 src/coreclr/ilasm/CMakeLists.txt 中可以看到ILASM_SOURCES明确列出了prebuilt/asmparse.cpp作为编译单元,它与assem.cppwriter.cppmethod.cppasmman.cppmain.cppassembler.cppportable_pdb.cpp一起组成 ilasm 可执行文件。这意味着:任何对 IL 语法的修改,都必须先修改asmparse.y,再重新运行 Bison 生成新的asmparse.cpp并提交,解析器生成是"离线"完成的。

正因如此,src/coreclr/ilasm/README.md 作为本目录的入口文档,其核心价值就是回答一个问题:如何在不同平台上正确地再生成asmparse.cpp


二、环境准备:各平台所需的 Bison 工具

生成解析器需要 Yacc/Bison 兼容工具,各平台获取方式如下:

平台工具获取方式
Windowswin_bison(Win flex-bison)https://github.com/lexxmark/winflexbison
Unix / Linux / macOSyacc(GNU Bison)https://www.gnu.org/software/bison
  • Windows 侧推荐使用 Win flex-bison 项目提供的win_bison可执行文件,它是以 Windows 原生方式移植的 Bison/flex 工具集;
  • 其他平台使用 GNU 官方发布的 Bison,安装后以yacc命令调用(Bison 兼容传统的 Yacc 命令行接口)。

说明:Bison 是 Yacc 的 GNU 实现,向下兼容传统 Yacc 语法,因此asmparse.y既可以被 Bison 处理,也可以被yacc命令调用。仓库中的调用方式统一使用yacc名称。


三、核心操作:再生成 asmparse.cpp

在任意平台上,生成命令的要点都是:输入asmparse.y,指定输出文件为prebuilt/asmparse.cpp,确保生成结果直接落入预构建目录。

3.1 Unix / Linux / macOS

yacc asmparse.y -o prebuilt/asmparse.cpp

即调用系统 Bison 生成的yacc命令,读取 src/coreclr/ilasm/asmparse.y,把生成的 C++ 解析器写入 src/coreclr/ilasm/prebuilt/asmparse.cpp。

3.2 Windows

win_bison asmparse.y -o prebuilt\asmparse.cpp

在 Windows 上使用 winflexbison 提供的win_bison,注意输出路径使用 Windows 风格反斜杠分隔符prebuilt\asmparse.cpp。命令必须在src/coreclr/ilasm目录下执行,以便相对路径asmparse.yprebuilt/asmparse.cpp正确解析。

3.3 Docker 一键生成(无需本机安装 Bison)

对于不想在宿主机安装 Bison 的开发者,README 提供了基于 Alpine Linux 的容器化方案:

$ cd runtime # run a throw-away-after-exit container with --rm $ docker run --rm -v$(pwd):/runtime -w /runtime/src/coreclr/ilasm alpine \ sh -c 'apk add bison && yacc asmparse.y -o prebuilt/asmparse.cpp'

逐项拆解这条命令:

  • --rm:容器退出后立即删除,不保留中间层,避免残留垃圾容器;
  • -v$(pwd):/runtime:把当前目录(仓库根目录runtime)挂载到容器内的/runtime
  • -w /runtime/src/coreclr/ilasm:设置容器工作目录为 ilasm 源码目录,使asmparse.y相对路径可直接使用;
  • alpine:使用轻量级 Alpine Linux 镜像(体积小、启动快);
  • sh -c 'apk add bison && yacc asmparse.y -o prebuilt/asmparse.cpp':先通过 Alpine 包管理器安装 Bison,再执行与 Unix 平台完全相同的生成命令。

由于工作目录被挂载为宿主机的 ilasm 目录,容器内生成的prebuilt/asmparse.cpp直接落盘到宿主机仓库中,容器销毁不影响产物。这是三种方式中最干净、最可复现的做法,适合 CI 或没有本地工具链的环境。


四、语法文件 asmparse.y 的内部结构

要理解生成流程,有必要了解被处理的输入文件 asmparse.y 的组织方式。它分为标准 Yacc 的三个区段:

4.1 声明区(Prologue + %union/%token)

  • 文件头部(%{ ... %}之间)通过#include "ilasmpch.h"引入 ilasm 预编译头,并#include "grammar_before.cpp"引入解析器辅助代码;
  • grammar_before.cpp 中定义了解析器所需的全局状态与工具函数,包括:
    • YYMAXDEPTH 0x80000栈深度上限、FAIL_UNLESS断言宏;
    • 全局解析指针PASM/PASMM,指向汇编器与清单对象;
    • 词法回调函数指针(PFN_NEXTCHARPFN_SYM等),通过SetFunctionPtrs()从解析环境注入;
    • 预处理指令支持(DefineVar/UndefVar/FindVarDef/IsVarDefined),对应 IL 源文件中的#define/#ifdef等编译控制指令。
  • %union定义了语法符号的值类型联合体,覆盖属性标志(类、方法、字段、事件、属性、PInvoke 映射等)、整型/浮点/字符串字面量、BinStr二进制串、IL 指令操作码、mdToken元数据令牌等;
  • %token声明了全部词法单元,例如IDDOTTEDNAMEQSTRINGINT32_VFLOAT64,以及按类别分组的 IL 指令令牌INSTR_NONEINSTR_VARINSTR_IINSTR_BRTARGETINSTR_METHODINSTR_FIELDINSTR_SWITCH等;
  • 汇编器伪指令(assembler directives)也以令牌形式声明,如_CLASS_METHOD_FIELD_TRY_MAXSTACK_LOCALS_ENTRYPOINT_CUSTOM_PERMISSION_ASSEMBLY等,语法规则中它们以.class.method.try.maxstack等形态出现。

4.2 规则区(%start decls 之后)

%%分隔后进入语法规则。入口为decls,最顶层decl规则定义了 IL 文件的模块级声明集合(asmparse.y 第 189-228 行):

decls : /* EMPTY */ | decls decl ; decl : classHead '{' classDecls '}' { PASM->EndClass(); } | nameSpaceHead '{' decls '}' { PASM->EndNameSpace(); } | methodHead methodDecls '}' { ... PASM->EndMethod(); } | fieldDecl | dataDecl | vtableDecl | vtfixupDecl | extSourceSpec | fileDecl | assemblyHead '{' assemblyDecls '}' { PASMM->EndAssembly(); } | assemblyRefHead '{' assemblyRefDecls '}' { PASMM->EndAssembly(); } | exptypeHead '{' exptypeDecls '}' { PASMM->EndComType(); } | manifestResHead '{' manifestResDecls '}' { PASMM->EndManifestRes(); } | moduleHead | secDecl | customAttrDecl | _SUBSYSTEM int32 { PASM->m_dwSubsystem = $2; } | _CORFLAGS int32 { PASM->m_dwComImageFlags = $2; } | _FILE ALIGNMENT_ int32 { PASM->m_dwFileAlignment = $3; ... } | _IMAGEBASE int64 { PASM->m_stBaseAddress = (ULONGLONG)(*($2)); ... } | _STACKRESERVE int64 { PASM->m_stSizeOfStackReserve = ...; } | languageDecl | typedefDecl | compControl | _TYPELIST '{' classNameSeq '}' | _MSCORLIB { PASM->m_fIsMscorlib = TRUE; } ;

每个规则后的{ ... }动作代码在归约时执行,直接调用汇编器对象(PASM)的方法或写入其字段,例如.subsystem.corflags.file alignment.imagebase.stackreserve等 PE 头选项正是在这里落到PASM的对应成员变量上,后续由 writer 输出到 PE 文件头。

4.3 辅助区(%%,语法规则后的用户代码)

规则区结束后可包含额外的 C++ 代码段,与声明区(grammar_before.cpp)对应,仓库中还有 grammar_after.cpp 存放规则之后的部分辅助实现。Bison 生成的asmparse.cpp会把声明区代码、规则表与这些辅助代码拼接在一起。


五、配套工具:GrammarExtractor 提取 BNF 文法

除了生成 C++ 解析器,仓库还提供一个名为 GrammarExtractor 的 .NET 工具,用于从asmparse.y提取纯 BNF 文法,输出给文档、分析工具与后续工具链使用。其使用说明见 GrammarExtractor/README.md:

cd runtime ./dotnet.sh run --project src/coreclr/ilasm/GrammarExtractor src/coreclr/ilasm/asmparse.y > src/coreclr/ilasm/prebuilt/asmparse.grammar

执行流程:

  1. 通过仓库根目录的 dotnet.sh 运行 GrammarExtractor 工程(src/coreclr/ilasm/GrammarExtractor/GrammarExtractor.csproj);
  2. 传入asmparse.y作为唯一命令行参数;
  3. 标准输出重定向到prebuilt/asmparse.grammar,生成可直接阅读的 BNF 文法文件。

5.1 提取原理:基于正则的规则重写

Program.cs 展示了提取逻辑,它并不真正解析 Yacc 文件,而是通过若干正则变换完成"语法到 BNF"的翻译:

  1. GetRegexExtractMarkers^(.*)%%(.*)%%):截取规则区(两个%%之间的内容),若找不到标记则报错退出;
  2. GetRegexRemoveTextInBraces\s*([^'])\{[^{}]*\}):反复迭代删除所有{ ... }动作代码块,直到不再变化(因为动作块可以嵌套);
  3. GetRegexKeywordIdentifiers\b([A-Z0-9_]+)_\b):把形如CLASS_METHOD_的关键字令牌改写为其小写字符串字面量'class''method'
  4. GetRegexAssemblerDirectives\b_([A-Z0-9]+)\b):把_CLASS_TRY等汇编器伪指令改写为带点号的小写形式'.class''.try'
  5. 特殊标点处理:ELLIPSIS'...'DCOLON'::'
  6. 输出固定的词法单元说明头(Lexical tokens/Auxiliary lexical tokens段),再拼接处理后的规则文本。

5.2 输出文件 asmparse.grammar

生成结果 prebuilt/asmparse.grammar 以 BNF 形式呈现 IL 文法,开头的词法单元清单完整描述了两类记号:

基本词法单元(Lexical tokens)

记号含义示例
IDC 风格字母数字标识符Hello_There2
DOTTEDNAME点分隔标识符序列System.Object
QSTRINGC 风格双引号字符串"hi\n"
SQSTRING单引号字符串'hi'
INT32C 风格 32 位整数235034230x34FFF
INT64C 风格 64 位整数-23534536362352340x34FFFFFFFFFF
FLOAT64C 风格浮点数-0.2323354.34233435.34E-5
INSTR_*各类 IL 指令参见opcode.def
HEXBYTE1-2 位十六进制数A2F0

辅助词法单元(Auxiliary lexical tokens)TYPEDEF_T(类型别名 TypeDef/TypeRef)、TYPEDEF_M(方法别名)、TYPEDEF_F(字段别名)、TYPEDEF_TS(TypeSpec 别名)、TYPEDEF_MR(MemberRef 别名)、TYPEDEF_CA(自定义特性别名)——这些是 ilasm 的.typedef ... as ...语法糖在解析器中的实现载体。

文法正文则从START : decls开始,逐条列出经过动作代码剥离后的纯净 BNF 规则,例如模块级声明、类声明、方法声明、自定义特性 blob、编译控制指令等,可直接作为 IL 语言规范的机器可读参考。


六、预生成产物的构建集成与注意事项

6.1 构建系统如何消费 asmparse.cpp

src/coreclr/ilasm/CMakeLists.txt 展示了生成产物在构建中的角色:

  • 编译单元明确包含prebuilt/asmparse.cpp(见ILASM_SOURCES);
  • Unix 平台上对该文件做了特殊编译处理(CMakeLists.txt 第 56-64 行):
    • 添加-Wno-register-Wno-array-bounds-Wno-unused-label以压制 Bison 生成代码在 Clang/GCC 下的告警;
    • 通过COMPILE_FLAGS "-O0"对该文件关闭优化——这是因为该预生成文件在优化下存在已知的 bad-codegen 问题(注释引用了 dotnet/runtime issue #4776);
  • Windows 上额外把头文件加入ILASM_SOURCES并定义FX_VER_INTERNALNAME_STR=ilasm.exe

这解释了为什么建议直接提交重新生成的asmparse.cpp:构建系统针对这份特定产物的行为是经过调优的,任何对语法的改动都应同步更新asmparse.yprebuilt/asmparse.cpp两份文件,保证源码与生成代码一致。

6.2 实操注意事项

  • 保持输出路径为prebuilt/asmparse.cpp:不要输出到临时目录后手动拷贝,三种平台的命令都已按此设计;
  • src/coreclr/ilasm目录执行asmparse.yprebuilt/的相对路径均以此目录为基准;Docker 方式通过-w参数保证这一点;
  • Windows 注意反斜杠-o prebuilt\asmparse.cpp使用 Windows 路径分隔符;
  • Docker 需要挂载卷:不要省略-v$(pwd):/runtime,否则生成的产物只存在于容器内,--rm退出后即丢失;
  • 语法变更要同步两份产物:修改 asmparse.y 后,应同时重新运行 Bison 生成asmparse.cpp、运行 GrammarExtractor 生成asmparse.grammar,保持三个文件一致。

七、总结

ilasm 的解析器生成是一条"人工触发 + 产物入库"的离线流程,核心链路为:

asmparse.y(Yacc/BNF 语法源) │ ├── Bison(yacc / win_bison)──► prebuilt/asmparse.cpp(C++ 解析器,编译进 ilasm) │ └── GrammarExtractor(dotnet run)──► prebuilt/asmparse.grammar(纯 BNF 文法文档)

无论是 Windows、Unix 还是 Docker 环境,生成命令都极为精简;掌握 src/coreclr/ilasm/README.md 中的这三条命令,配合对 asmparse.y 规则区结构与 CMakeLists.txt 编译集成的理解,即可安全地修改、再生成并验证 ilasm 的语法解析能力,也能借助asmparse.grammar深入研读 .NET IL 汇编语言的完整 BNF 定义。

  • 语言运行时
  • 标准库
  • JIT编译
  • 编译器

【免费下载链接】runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.

项目地址:https://gitcode.com/GitHub_Trending/runtime6/runtime
点击查看免费下载

相关推荐

上一篇:如何为旧款Mac安装最新macOS:OpenCore Legacy Patcher终极指南
下一篇:mcp-installer配置全攻略:修改claude_desktop_config.json文件的详细步骤

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 23:34:43

TiXL 节点详解:Combine3Images —— 用三张输入图像重组 RGBA 通道

音视频图形学桌面应用 【免费下载链接】t3 TiXL is an open source software to create realtime motion graphics. 项目地址: https://gitcode.com/GitHub_Trending/t3/t3 点击查看 免费下载 本篇技术指南围绕 TiXL(tooll3/t3 实时动态图形创作工具&am…

作者头像 李华
网站建设 2026/9/20 23:33:57

DevSecOps标准解读:从安全左移到流水线门禁落地实践

简介:DevSecOps标准解读PDF是一份面向网络安全、研发运维与合规管理从业者的标准科普资料,重点解决团队在落地DevOps时安全介入过晚的痛点。资源以安全左移为主线,系统梳理DevSecOps的起源、核心理念,以及计划、开发、构建、测试、…

作者头像 李华