拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Universal Ctags Asm 解析器完全指南:从 define 归属到 C 预处理器宏展开

Universal Ctags Asm 解析器完全指南:从 define 归属到 C 预处理器宏展开
  • 开发工具
  • CLI

【免费下载链接】ctags

A maintained ctags implementation

项目地址:https://gitcode.com/gh_mirrors/ct/ctags
点击查看免费下载

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):

字母名称说明默认启用
ddefine由equ/=/set等定义的符号是
llabel标签(含:结尾的行、.proc、.label等)是
mmacro由.macro定义的宏是
ttype结构体(.struct)与记录(.record)是
zparameter宏的参数否

其中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=1

5.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

项目地址:https://gitcode.com/gh_mirrors/ct/ctags
点击查看免费下载
上一篇:突破性B站视频内容解析工具:如何实现智能语音转文字的极致体验
下一篇:Bilibili视频转文字终极方案:高效提取视频内容,一键生成文本稿

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

返回列表