- 开发工具
- CLI
【免费下载链接】ctags
A maintained ctags implementation
Universal Ctags 的 Asm(汇编语言)解析器负责为各种汇编方言生成标签(tags),但它与经典 Exuberant Ctags 的处理方式有本质差异:它以 CPreProcessor 元解析器为基础,专门针对 GNU as(Gas)等现代汇编器做了扩展。本文基于仓库文档 docs/parser-asm.rst 与 man/ctags-lang-asm.7.rst.in,结合 parsers/asm.c 源码与 Units/parser-asm.r 测试用例,系统讲解 Asm 解析器的标签种类、#define归属差异、四个可调参数,以及 C 预处理器宏展开这一独有能力,帮助你针对不同汇编器方言正确配置并利用该解析器。
一、设计定位:与 Exuberant Ctags 的关键差异
1.1#define与 C 风格注释交给 CPreProcessor 处理
原版 Exuberant Ctags 的汇编解析器会自己处理#define预处理指令和 C 风格注释。而 Universal Ctags 的 Asm 解析器将这两件事委托给了CPreProcessor 元解析器(见 parsers/asm.c 中对x-cpreprocessor.h的引用以及 parsers/cpreprocessor.c)。
由此产生一个容易被忽视的行为差异:用#define定义的语言对象,其标签归属为 CPreProcessor 语言,而不是 Asm 语言。文档 docs/parser-asm.rst 用一组对比命令说明了这一点:
$ cat input.S #define S 1 $ e-ctags --fields=+l -o - input.S S input.S /^#define S 1$/;" d language:Asm $ u-ctags --fields=+l -o - input.S S input.S /^#define S /;" d language:CPreProcessor file:同样是#define S 1,e-ctags(Exuberant Ctags)把它标记为 Asm 语言的define标签(d),而u-ctags(Universal Ctags)把它标记为 CPreProcessor 语言的define标签。这一差异在编写依赖标签结果的脚本或使用--languages过滤时尤为重要。
1.2 从源码看“define”的两种来源
从 parsers/asm.c 的 kind 表可以看到 Asm 语言自身仍保留了define(d)这一标签种类,其来源是汇编器自己的定义指令,而非#define:
equ/equiv/eqv、=、:=、set等运算符对应K_DEFINE(见OpKinds表,parsers/asm.c);- 形如
DB、DB.L、DCB.W(68000 系)的“定义类运算符”也会产生define标签(见isDefineOperator,parsers/asm.c)。
因此#define与.equ虽然都输出d类标签,但前者属于 CPreProcessor、后者属于 Asm,二者泾渭分明。
二、标签种类(kinds):label、macro、type、parameter
Asm 解析器定义了 5 种标签(parsers/asm.c):
| 字母 | 名称 | 说明 | 默认启用 |
|---|---|---|---|
d | define | 由equ/=/set等定义的符号 | 是 |
l | label | 标签(含:结尾的行、.proc、.label等) | 是 |
m | macro | 由.macro定义的宏 | 是 |
t | type | 结构体(.struct)与记录(.record) | 是 |
z | parameter | 宏的参数 | 否 |
其中parameter(z)默认关闭,需要时用--kinds-Asm=+z开启。测试 Units/parser-asm.r/macro-params.d/expected.tags 展示了启用后的效果:
plus1 input.asm /^ .macro plus1 p, p1$/;" m signature:p p1 end:7 p input.asm /^ .macro plus1 p, p1$/;" z macro:plus1 nth:0 p1 input.asm /^ .macro plus1 p, p1$/;" z macro:plus1 nth:1可以看到每个宏参数标签都带有macro:plus1作用域与nth:0序号;若参数带:req或:vararg后缀,还会输出properties:req、properties:vararg字段(对应源码中的F_PROPERTIES字段定义,parsers/asm.c)。
macro标签支持用end:字段记录宏体结束行。例如 Units/parser-asm.r/gas.s.d/expected.tags 中的.macro sum from=0, to=5会生成sum ... m line:1 end:6,宏体跨越第 1~6 行。
三、文件匹配:扩展名、模式与选择器
Asm 解析器通过三种途径识别汇编源文件(parsers/asm.c):
- 扩展名:
asm、ASM、s、S; - 模式(pattern):
*.A51、*.29[kK]、*.[68][68][kKsSxX](如 68000/68k)、*.[xX][68][68]; - 选择器:
selectByArrowOfR(R 语言文件中的汇编内嵌代码,见selectors数组)。
同时可用--map-Asm=+.{ext}追加映射,例如:
$ u-ctags ... --map-Asm=+.asm --map-Asm=+.s --map-Asm=+.S ...对应 man 页的 SYNOPSIS 部分(man/ctags-lang-asm.7.rst.in)。
四、四个可调参数:适配不同汇编器方言
汇编语言的语法高度依赖汇编器实现与目标 CPU。例如 NASM 中;是行注释起始符,而 i386 的 Gas 中;是语句分隔符。为弥合这些差异,Asm 解析器提供了 4 个参数(源码定义见 parsers/asm.c),统一通过--param-Asm.{参数}={值}设置,可用--list-params=Asm查看全部可用参数。
4.1runCPreProcessor:是否先跑 C 预处理器
- 默认值:
true。CPreProcessor 会先于 Asm 解析器处理输入(对应findAsmTags中调用findAsmTagsCommon(useCPreProcessor)的逻辑,parsers/asm.c)。 - 启用后的三个主要效果:
- 剥离以
//起始的行注释; - 剥离
/* ... */注释块; - 提取
#define宏为标签(归属 CPreProcessor 语言)。
- 剥离以
- 关闭方式:
$ u-ctags ... --param-Asm.runCPreProcessor=false ...(man 页中该参数写作runCPreProcessor,示例命令行亦常见useCPreProcessor写法,二者同指。)
4.2commentCharsAtBOL:行首注释字符
- 默认值:
;*@(源码常量DEFAULT_COMMENT_CHARS_BOL,parsers/asm.c)。 - 含义:
BOL即 "beginning of line",这些字符只有在行首才作为注释起始符。若runCPreProcessor=true,//也会被当作注释忽略。 - 示例(改用
!与>作为行首注释符):
$ u-ctags ... --param-Asm.commentCharsAtBOL='!>' ...4.3commentCharsInMOL:行中注释字符
- 默认值:空(无)。
- 含义:
MOL即 "middle of line",这些字符在行内任何位置都起始注释。例如 CRIS 的 Gas 中;可在行中任意位置开始注释。 - 用法:
$ u-ctags ... --param-Asm.commentCharsInMOL=';' ...4.4extraLinesepChars:额外行分隔符
- 默认值:空。解析器以行为单位处理输入,默认只把
\n当作行分隔。 - 用途:AArch64 的 Gas 允许用
;作为语句分隔符,此时需要把它加入行分隔符集合,否则同一物理行内的多条语句无法被分别解析:
$ u-ctags ... --param-Asm.extraLinesepChars=';' ...相关测试见 Units/parser-asm.r/gas-parameterized-linesep.d 及其 no-cpp 变体。
五、独有能力:展开 C 预处理器宏
这是 Universal Ctags Asm 解析器相对 Exuberant Ctags 的显著增强:在解析前展开 C 预处理器宏,从而正确识别由宏调用生成的标签。注意这里的宏特指 C 预处理器宏(#define),而非汇编器自身的.macro。
5.1 启用宏展开的必要选项
展开功能需要同时满足以下选项(man 页明确指出):
--param-Asm.runCPreProcessor=true --fields=+{signature} --fields-CPreProcessor=+{macrodef}- 只展开命令行用
-D定义的宏时,以上即可; - 若要同时展开当前输入文件内
#define的宏,再加:
--param-CPreProcessor._expand=15.2 一个完整的宏展开示例
man 页给出了自带的输入文件input.S与输出output.tags:
#define ENTRY(LABEL) .global LABEL ;\ LABEL ENTRY(main): nop使用命令:
$ u-ctags --options=NONE -o - \ --param-Asm.useCPreProcessor=1 \ --param-CPreProcessor._expand=1 \ --fields=+{signature} \ --fields-CPreProcessor=+{macrodef} \ --param-Asm.extraLinesepChars=; \ input.S输出(tags 格式):
ENTRY input.S /^#define ENTRY(/;" d file: signature:(LABEL) macrodef:.global LABEL ;LABEL main input.S /^ENTRY(main):$/;" l main input.S /^ENTRY(main):$/;" s要点:
#define ENTRY(LABEL) ...被提取为 CPreProcessor 的define标签,signature:(LABEL)记录形参,macrodef:记录宏体;- 宏调用
ENTRY(main):展开后生成了main的l(label)标签; main同时还有一个s类标签——这是ENTRY宏体内.global LABEL展开出来的符号(.global/.globl指令会生成 LdScript 语言的 foreign 标签,见后文);- 由于宏体依赖
;作为语句分隔符,命令中必须用--param-Asm.extraLinesepChars=;与输入假设保持一致(man 页明确说明了这一点)。
这一场景在仓库测试 Units/parser-asm.r/asm-cpp-macro-expansion.d/input.S 中被进一步放大——它复刻了 Linux 内核风格的SYM_ENTRY/SYM_START/SYM_CODE_START宏链,最终正确提取出asm_exc_nmi标签。展开器对带字符串实参(ENTRY2(loop0, "STRING"))、字符实参('c')、嵌套括号、多行实参等情况均有处理,相关实现位于 parsers/asm.c(collectCppMacroArguments/expandCppMacro),以及readLineViaCpp(parsers/asm.c)中对宏展开后行缓冲的截断与重读逻辑。
5.3 已知限制
man 页明确列出两点局限(man/ctags-lang-asm.7.rst.in):
- 解析器无法展开当前输入文件之外定义的宏(例如头文件中定义的宏);
- 展开时不考虑
#undef。
此外,尽管解析器会展开宏,但它不会自动从展开结果中按你的期望提取所有语言对象——宏展开要真正生效,还需配合 5.1 中的参数及汇编器方言参数(如extraLinesepChars)一起调整。
六、.section与 LdScript 语言的协作
Asm 解析器对链接脚本相关指令做了特殊处理:.global/.globl产生的符号、.section指定的输入段,会被标记为LdScript 语言的标签,而非 Asm 标签。这通过解析器的依赖声明实现(parsers/asm.c):
static parserDependency dependencies [] = { { DEPTYPE_FOREIGNER, "LdScript", &Lang_ldscript }, };对应的生成逻辑在makeTagForLdScript(parsers/asm.c):
.global symbol生成 LdScript 的symbolkind 标签;.section NAME生成 LdScript 的inputSectionkind 标签,并带destinationrole。
这是文档“VERSIONS”一节描述的变更结果:早期 Asm 语言自身的sectionkind(含placementrole)已被删除,改为委托给 LdScript 的inputSection/destination。测试 Units/parser-asm.r/gas-section.d 覆盖了该行为。
七、版本说明与延伸阅读
- VERSIONS 变更:自 0.0 以来,
sectionkind 被删除,.section指令改以destinationrole 的inputSectionkind(LdScript 语言)标记,见 man/ctags-lang-asm.7.rst.in。 - 进一步探索:
- 解析器源码:parsers/asm.c;
- 行为测试:Units/parser-asm.r(含宏展开、Gas 参数化注释/行分隔、
#define归属、宏参数提取等数十个用例); - CPreProcessor 元解析器:parsers/cpreprocessor.c;
- man 手册:man/ctags-lang-asm.7.rst.in,以及通用手册 man/ctags.1.rst.in(关于
-D定义宏与--param-的通用说明); - 解析器总览:docs/parsers.rst。
结语
Universal Ctags 的 Asm 解析器以“CPreProcessor 预处理 + 方言参数可调 + 宏展开”三件套,解决了汇编标签提取中两大痛点:#define归属的语义差异,以及不同汇编器(NASM、Gas、CRIS、AArch64……)语法不一致导致的解析失真。实际使用时,先确认目标汇编器的注释符与行分隔符,再通过--param-Asm.*与--fields-*系列选项做针对性配置,即可在多种汇编方言上获得稳定可用的标签输出。
- 开发工具
- CLI
【免费下载链接】ctags
A maintained ctags implementation
相关推荐
Universal Ctags 的汇编语言(Asm)解析器完全指南:Gas 语法适配、行注释配置与 C 预处理器宏展开
Universal Ctags 的汇编语言(Asm)解析器完全指南:Gas 语法适配、行注释配置与 C 预处理器宏展开 导读 ctags lang asm.7.
开发工具CLIctags 新 C/C++ 解析器完全指南:properties 字段、宏展开与不兼容变更详解
ctags 新 C/C++ 解析器完全指南:properties 字段、宏展开与不兼容变更详解 本篇技术指南以 universal ctags 仓库官方文档 d
开发工具CLIUniversal Ctags Optlib 解析器定义语言(ctags-optlib)完全指南
Universal Ctags Optlib 解析器定义语言(ctags optlib)完全指南 导读 本文以 ctags optlib.7 手册 https:
开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考