想象你走进一家餐厅点菜:连锁大厨的工具包里只认自家写法的菜谱,而街角私厨则按自己的习惯记菜。汇编器就是这样的“翻译官”,它把人类可读的汇编代码翻译成机器能执行的二进制指令。GNU Assembler(GAS)是标配在 GCC 工具链里的“连锁大厨”,默认讲一口 AT&T 方言;NASM 则像一位独立私厨,坚持 Intel 语法的“大白话”。同一条指令,两位大厨的记法完全不同,但端上桌的“菜”——机器码——却一模一样。读懂他们的差异,你就能在任何平台上灵活选择工具,写出既高效又不出错的底层代码。

岗位关联

对 AI 从业者而言,理解两种汇编器的语法差异,是读懂编译器生成代码、调试底层推理引擎、以及做性能优化的基本功。掌握了它,你就能在纷繁的工具链中自如切换,不再被“语法不通”卡住。

目录
  1. 可视化演示
  2. 汇编器角色认知
  3. AT&T与Intel语法
  4. 伪指令与宏机制
  5. 段与内存组织
  6. 目标文件格式
  7. 调试与符号瘦身
  8. 实战抉择策略
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示AT&T与Intel语法差异、两遍扫描流水线及工具链生态对比,支持扫描阶段调节。

本节配有交互动效(进入视口后自动加载,约 3KB)。

AT&T vs Intel语法对比 红色GAS使用AT&T语法:寄存器带%前缀,立即数带$前缀,源在前目的在后 青色NASM使用Intel语法:无前缀,目的在前源在后,更接近人类阅读习惯 movl %eax, %ebx vs mov ebx, eax 两种语法在操作数顺序和前缀上有显著差异

汇编器角色认知

走进厨房,连锁餐厅的“总配菜台”早已由中央厨房统一配好——这就是 GAS 在 GCC 世界里的身份:它不是独立工具,而是随 GCC 编译器一起发布的标准配件。当你敲下 gcc -c 时,GCC 会把 C 语言翻译成汇编,再顺手交给 GAS 翻译成机器码,全程无需你操心。而 NASM 就像一间独立私厨,自己采购、自己调味、自己装盘,它不属于任何大厂体系,可以单独下载、单独使用,输出的目标文件格式也由你说了算。

“翻译官”的工作其实很简单:读入一段汇编文本,查表对照指令表,输出对应二进制。但这位翻译官是否靠谱,直接影响最终机器码的正确性。GAS 与 NASM 都遵守同一份 x86 指令集手册,因此同一段逻辑最终生成的机器码是等价的,区别只在于“记菜谱的方式”不同——GAS 默认用 AT&T 方言,NASM 用 Intel 方言。

从工程实践看,选择哪位翻译官取决于你站在哪个舞台上:如果你已在 GCC 生态中写 C/C++,GAS 是零成本选项;如果你在写操作系统引导或跨平台裸机代码,NASM 的格式灵活性和可移植性会让你事半功倍。

回到比喻:翻译官在整条流水线上只是“中厨”一环。理解它的角色边界,你就知道为什么做性能分析时该去看汇编,而写链接脚本时则要去找链接器。

汇编语言源文件→汇编器目标文件→链接器可执行文件\text{汇编语言源文件} \xrightarrow{\text{汇编器}} \text{目标文件} \xrightarrow{\text{链接器}} \text{可执行文件}
一条完整流水线:汇编器只负责把汇编代码翻成目标文件,真正的“装盘上桌”由链接器完成。
  1. 汇编语言源文件这是你手写的 .asm 或 .s 文件,里面是给人读的指令文本。
    源文件=指令+伪指令+数据定义\text{源文件} = \text{指令} + \text{伪指令} + \text{数据定义}
  2. 汇编器GAS 或 NASM 读取文本,查表翻译成二进制机器码。
    目标文件=机器码+重定位表+符号表\text{目标文件} = \text{机器码} + \text{重定位表} + \text{符号表}
  3. 链接器把多个目标文件和库合并,解析符号引用,生成最终可执行文件。
    可执行文件=∑目标文件+地址回填\text{可执行文件} = \sum \text{目标文件} + \text{地址回填}

AT&T与Intel语法

两种方言最让初学者头疼的差异,是操作数的书写顺序。AT&T 方言像英文中的“SVO 倒装句”——先说动作的来源,再说动作的去向:movl %eax, %ebx 意思是“把 eax 的值搬去 ebx”,源在左、目标在右。而 Intel 方言更像中文的“把字句”——目标放前面,来源放后面:mov ebx, eax 还是同样的搬运操作,但书写顺序完全不同。顺序一颠倒,初读时极容易把数据搬错方向。

除了语序,两位大厨“标注”的方式也不同。在 AT&T 里,寄存器要加 % 前缀(%eax),立即数要加 $ 前缀($5),这样一眼就能分辨谁是地址、谁是数值;而在 Intel 里,eax 就是寄存器,5 就是立即数,简洁直白。指令宽度也不是靠名字区分:GAS 在指令尾部加 b/w/l/q 后缀表示字节/字/双字/四字,NASM 则根据寄存器名天然判断宽度。

同样一句“把 5 搬入 eax”,GAS 写成 movl $5, %eax,NASM 写成 mov eax, 5。两者阅读手感完全不同,但翻出来的机器码都是 B8 05 00 00 00。掌握这套“方言对照表”,你在看 GCC 生成的 .s 文件时就不再发懵,也能顺畅读懂各类反汇编工具的 Intel 风格输出。

现在再回头读开头的点菜比喻:同样的菜,私厨写的简化菜单和连锁大厨的标准化表格内容一致,只是次序和标注不同。你只需要记住“先后顺序”这一把钥匙,就能在两套菜单之间自由切换。

AT&T: movl源, 目标Intel: mov目标, 源\text{AT\&T: } \text{movl} \quad \text{源, 目标} \qquad \text{Intel: } \text{mov} \quad \text{目标, 源}
两行写法表示同一操作,核心口诀:“AT&T 先讲从哪来,Intel 先讲到哪去”。
  1. AT&T 操作数顺序源操作数写在前面,目标操作数写在后面,传递方向从左到右。
    movl $5⏟源, %eax⏟目标\text{movl} \ \underbrace{\$5}_{\text{源}} , \ \underbrace{\%eax}_{\text{目标}}
  2. Intel 操作数顺序目标操作数写在前面,源操作数写在后面,传递方向从右到左。
    mov eax⏟目标, 5⏟源\text{mov} \ \underbrace{eax}_{\text{目标}} , \ \underbrace{5}_{\text{源}}
  3. 宽度后缀AT&T 用 b/w/l/q 指定操作宽度,Intel 靠寄存器名推断。
    movb→8bit,movw→16bit,movl→32bit,movq→64bit\text{movb} \rightarrow 8\text{bit},\quad \text{movw} \rightarrow 16\text{bit},\quad \text{movl} \rightarrow 32\text{bit},\quad \text{movq} \rightarrow 64\text{bit}

伪指令与宏机制

真正的老厨子不会每次做菜都从头切配,而是提前配好“调味包”——汇编里的宏就是这种预制的代码片段。GAS 和 NASM 都有宏能力,只是“包装方式”不同。GAS 用 .macro 和 .endm 包裹宏体,参数以 \arg 形式引用;NASM 则用 %macro 和 %endmacro 包裹,参数写成 %1、%2。同样是定义一个“输出字符”的宏,GAS 写起来像标准公文,NASM 则更像随手记的便签。

伪指令是写给汇编器看的“管理指令”,不产生机器码。GAS 的 .equ 定义常量,NASM 的 equ 干同样的活;GAS 的 .if/.endif 做条件汇编,NASM 则用 %if/%endif。这些机制的价值在于:你可以写一份代码,通过条件编译适配不同的平台或配置,就像同一张菜谱,根据是川菜馆还是粤菜馆自动调整辣度。

以实际场景为例,写启动代码时常需要判断是 32 位还是 64 位环境。用条件汇编,你可以在一个 .asm 文件里同时维护两套代码分支:

这就像你把常用口味的“糖醋汁”比例写在小纸条上,每次做菜直接照抄,省时又不易出错。合理使用宏,能让复杂汇编项目保持整洁可维护。

.macro func [args]⇒调用处展开为宏体代码\text{.macro} \ \text{func} \ [\text{args}] \quad \Rightarrow \quad \text{调用处展开为宏体代码}
宏的本质是“源码级复制粘贴”:编译前先把宏调用展开成完整代码,再交给汇编器翻译。
  1. 宏定义声明一个带名字的代码模板,像厨师配好调味包。
    .macro print_char char\text{.macro} \ \text{print\_char} \ \text{char}
  2. 参数引用GAS 用 \char,NASM 用 %1 引用传入的参数。
    mov $′A′\text{mov} \ \$'A' %\text{?}\ \Rightarrow \ \text{格式化输出}\ \%eax
  3. 宏展开调用 print_char 时,汇编器把宏体代码原样插入调用位置。
    宏展开=宏体代码中参数全部替换\text{宏展开} = \text{宏体代码中参数全部替换}

段与内存组织

餐厅有前厅、后厨和仓库的划分,汇编程序的内存布局同样被“段”划分出不同功能区。.text 段(代码段)存放指令,相当于后厨里贴着的操作步骤;.data 段存放有初值的全局变量,相当于仓库里备好的半成品;.bss 段存放未初始化或零值的数据,相当于空置的冰柜——不占用文件体积,运行时才分配。GAS 和 NASM 都支持这几类段,但段名和语法略有差异:GAS 用 .section .text,NASM 则写 section .text,一个多一个 . 前缀,风格截然不同。

段的作用不只是分区存储。操作系统加载程序时,会根据段属性设置内存权限:代码段只读可执行,数据段可读写。这使得缓冲溢出攻击难以下手,也是现代安全基线的一部分。而“段基址 + 偏移”的组合寻址,在 16 位实模式下是硬规定,在 32/64 位保护模式下则演变成平坦内存模型,但概念上依然保留着“区域 + 偏移”的心智模型。

写嵌入式程序时,你甚至可以用链接脚本或段指令把特定代码放到指定内存地址,比如把中断向量表固定放在 0x0000。熟练操控段组织,意味着你对程序的内存蓝图有了绝对掌控权。

理解段的划分,就像知道了餐厅每一层楼的用途:该放砧板的放砧板,该摆桌子的摆桌子。程序跑得稳不稳,很大程度上取决于段安排得是否合理。

物理地址=段基址×16+偏移地址\text{物理地址} = \text{段基址} \times 16 + \text{偏移地址}
实模式下访问一个地址,需要“基址 + 偏移”两个部件合成,像用“街区号 + 门牌号”定位一栋房子。
  1. 段基址段的起始位置,必须是 16 的倍数,相当于街区号。
    段基址=段寄存器值×16\text{段基址} = \text{段寄存器值} \times 16
  2. 偏移地址段内距离段首的字节数,相当于门牌号。
    偏移地址=指令/数据距段首的距离\text{偏移地址} = \text{指令/数据距段首的距离}
  3. 物理地址合成两者相加即得内存真实地址。
    物理地址=段基址+偏移地址\text{物理地址} = \text{段基址} + \text{偏移地址}

目标文件格式

大厨做完菜,得讲究用什么盘子装——这就是目标文件格式的职责。GAS 在 Linux 平台上默认产出 ELF 格式目标文件,这是 Linux 世界的“标准餐盘”,里面不仅有机器码,还携带符号表、重定位表、调试信息等“配菜”。NASM 则更像一个“万能打包师傅”,通过 -f 参数可以指定输出格式:-f elf 提供给 Linux 链接器,-f bin 直接生成裸二进制,-f win64 则适配 Windows 的 PE 格式。选对格式,链接器才认你这道菜。

ELF 文件的结构像一本精装书:开头是文件头(目录),接着是节区表(章节列表),中间是各个 section(正文内容)。链接器按节区表把不同目标文件的同名节合并、重定位,最终生成可执行文件。NASM 的 -f bin 则跳过所有包装,直接输出内存镜像——常用于操作系统引导扇区,因为它只需要“纯代码”,不需要任何元数据。

你做嵌入式开发时,bin 格式是烧写 Flash 的标配;你做用户态 Linux 应用时,elf 格式是绕不开的主场。理解这些格式的差异,就像懂得同一道菜该用深盘还是浅碟:用错了,上不了桌。

选对“盘子”,菜才能顺利上桌。理解了 ELF 的解剖学,你就理解了 Linux 下一切程序的生活真相。

ELF=ELF头+程序头表+节区+节头表\text{ELF} = \text{ELF头} + \text{程序头表} + \text{节区} + \text{节头表}
ELF 分成四个大块:文件头统领全局,程序头表描述加载方式,节区存放真实内容,节头表索引各节位置。
  1. ELF头固定 52 或 64 字节,记录文件类型、架构、入口地址。
    e_entry=程序入口虚拟地址\text{e\_entry} = \text{程序入口虚拟地址}
  2. 程序头表告诉加载器哪些节映射到内存的什么位置、什么权限。
    offset→vaddr,filesz→memsz\text{offset} \rightarrow \text{vaddr}, \quad \text{filesz} \rightarrow \text{memsz}
  3. 节区数据.text、.data、.bss 等节的实际内容。
    节区=机器码+只读数据+可写数据\text{节区} = \text{机器码} + \text{只读数据} + \text{可写数据}
  4. 节头表描述每个节的名称、偏移、大小,供链接和调试使用。
    sh_offset=节区在文件中的偏移\text{sh\_offset} = \text{节区在文件中的偏移}

调试与符号瘦身

菜品做完,质检员要试菜——对应到开发中就是各类检查工具。objdump 像一把“透视镜”,能把目标文件反汇编成汇编代码,让你亲眼核对机器码是否为心中所想;readelf 则像“内部员工手册”,详细列出 ELF 文件里的每一个节区、符号和重定位项。当你怀疑编译器优化出了问题,或想确认某个全局变量有没有被链接进最终产物,objdump 和 readelf 是首选工具。

符号表相当于菜品的“配料标签”,记录着每个函数名、变量名的地址。它在调试时不可或缺,但也给逆向工程师提供了便利线索。于是 strip 命令应运而生——它删除符号表和调试信息,让程序体积变小、逆向难度提高。发布正式版本时,strip 是常规操作;但做崩溃分析时,没有符号表就像丢了菜名的菜单,只剩一堆原材料,分析难度陡增。

在排查 AI 推理引擎性能瓶颈时,objdump 能帮你确认 SIMD 向量化指令是否生成正确,nm 能查符号是否重复定义。这就像质检员拿着放大镜逐行检查后厨的每个细节。

用好这些“质检工具”,你就能像经验丰富的老饕一样,一眼看出菜品的火候和用料——让底层问题无处遁形。

重定位=符号地址+修正量\text{重定位} = \text{符号地址} + \text{修正量}
链接时,引用外部符号的指令需要“填坑”:把符号的实际地址加上偏移写回指令中。
  1. 符号函数名或变量名对应的抽象地址占位符。
    symbol=name+value+section index\text{symbol} = \text{name} + \text{value} + \text{section index}
  2. 重定位表项记录哪条指令的哪个位置需要回填地址。
    r_offset=需要修改的位置\text{r\_offset} = \text{需要修改的位置}
  3. 地址回填链接器计算符号最终地址后写入指令。
    最终值=符号值+addend\text{最终值} = \text{符号值} + \text{addend}

实战抉择策略

走到点菜环节:到底该选哪位翻译官?答案取决于你所在的项目环境。如果你已经在 GCC 的工具链里写 C/C++,GAS 是零成本默认选择——它和 GCC 的配合天衣无缝,内联汇编语法一致,语言模型一脉相承。如果你的项目需要跨多个平台、多个汇编器共存,或者你在写引导程序、操作系统内核这类“从零开始”的代码,NASM 的简单语法和强大格式控制会让你更顺手。

还有一种常见情况:一个项目里既有 GCC 生成的 AT&T 汇编,又有别人提供的 Intel 语法汇编。此时你有两条路:一是用 GAS 的 .intel_syntax 指令,让 GAS 也能读懂 Intel 风格;二是用 objconv 等工具做语法转换。理解两种语法的映射关系,你就掌握了“翻译官之间的翻译官”。

选择本身没有绝对的对错,只有是否匹配场景。就像选餐厅:证件齐全的连锁店稳定可靠,风味独特的私房菜更能满足刁钻口味。成熟的底层开发者,往往两种语法都熟稔于心,根据情况灵活切换。

说到底,工具是死的,人是活的。把两种“翻译官”都装进口袋,你就拥有了在任何底层战场上自由切换的底气。

选择依据=工具链生态+平台目标+格式需求\text{选择依据} = \text{工具链生态} + \text{平台目标} + \text{格式需求}
三个权重最高的决策因素:跟谁走、去哪跑、最后装成什么。
  1. 工具链生态是否已深度使用 GCC 系列工具链。
    GCC 生态⇒GAS\text{GCC 生态} \Rightarrow \text{GAS}
  2. 平台目标是否有跨 Windows/Linux/嵌入式多平台需求。
    跨平台⇒NASM\text{跨平台} \Rightarrow \text{NASM}
  3. 格式需求是否需要 bin 裸格式或特殊目标文件格式。
    裸机引导⇒−f bin\text{裸机引导} \Rightarrow -f \ \text{bin}

AI 应用场景

场景1:编译器后端的汇编代码生成验证

阶段:模型编译与部署优化

解决的问题:AI 编译器(如 TVM、MLIR)在将深度学习模型翻译成底层指令时,通常先生成汇编再交由汇编器处理。若生成的 AT&T 语法汇编与目标平台要求不符,会导致编译失败。理解 GAS 与 NASM 的语法映射,工程师能快速定位是编译器 bug 还是语法不兼容。

具体案例:某团队用 TVM 在 ARM 边缘设备部署模型,自定义算子生成的 .s 文件在汇编阶段报“invalid operand type”。工程师对照 AT&T 与 Intel 语法差异表,发现是操作数顺序写反导致,一小时内修复。

场景2:二进制安全分析中的反汇编语法切换

阶段:模型安全审计与对抗样本防御

解决的问题:分析恶意模型文件或底层运行时库时,需要调用反汇编工具查看代码逻辑。IDA Pro、Ghidra 等工具默认展示 Intel 语法,而 GCC 生成的二进制用 objdump 默认展示 AT&T 语法。理解两种语法的对应关系,才能在两种展示之间无缝切换。

具体案例:安全工程师在分析一个被植入后门的 ONNX 运行库时,先用 objdump 查看 .text 段,再用 Ghidra 交叉验证。因为两种工具语法风格不同,他靠“源/目标操作数互换 + 前缀规则”快速对齐了同一段代码,找出注入点。

场景3:自定义算子库的跨平台构建

阶段:算子开发与部署

解决的问题:用 SIMD 指令集手写高性能算子时,需要在不同平台选择不同汇编器。Linux 上默认 GAS,Windows 上常用 MASM/NASM。理解汇编器差异,开发者可编写宏层抽象,一套代码适配多平台构建。

具体案例:某推理引擎团队在 x86 平台用 NASM 写 AVX-512 算子,在 ARM 平台用 GAS 写 SVE 算子。通过统一对外接口和汇编宏包装,同一份 C++ 调用代码同时支持两套实现,性能提升 3 倍。

Python 演示

python55 行
#!/usr/bin/env python3"""Intel 语法与 AT&T 语法迷你转换工具箱""" REGS = {    "eax": "%eax", "ebx": "%ebx", "ecx": "%ecx", "edx": "%edx",} SIZE_SUFFIX = {    "mov": "l", "add": "l", "sub": "l", "cmp": "l",    "movb": "b", "movw": "w", "movq": "q",}  def intel_to_att(line):    """把 Intel 语法的一行汇编转成 AT&T 语法。     例如: "mov eax, 5" -> "movl $5, %eax"    """    line = line.strip()    if not line or line.startswith(";") or line.startswith("#"):        return ""    parts = line.replace(",", " ").split()    op = parts[0].lower()    suffix = SIZE_SUFFIX.get(op, "")    if suffix:        op = op[:-1] if op in ("movb", "movw", "movq") else op    ops = []    for i in range(1, len(parts)):        p = parts[i]        if p.lower() in REGS:            ops.append(REGS[p.lower()])        elif p.startswith("0x") or p.lstrip("-").isdigit():            ops.append("$" + p)        else:            ops.append(p)    if len(ops) == 2:        return "{op}{suffix} {src}, {dst}".format(            op=op, suffix=suffix, src=ops[1], dst=ops[0]        )    return "{op}{suffix} {arg}".format(op=op, suffix=suffix, arg=ops[0])  def main():    samples = [        "mov eax, 5",        "mov ebx, eax",        "add ecx, 10",        "mov edx, 0x100",    ]    for s in samples:        print("{:18s} -> {}".format(s, intel_to_att(s)))  if __name__ == "__main__":    main()
目的:演示 Intel 语法到 AT&T 语法的核心转换规则——操作数反转、寄存器加 %、立即数加 $、指令加宽度后缀。
  1. parts = line.replace(",", " ").split()把逗号统一替换为空格再拆词,避免解析差异。
  2. if p.lower() in REGS识别寄存器名并映射为 %eax 形式,这是 AT&T 的固定前缀。
  3. elif p.startswith("0x") or p.lstrip("-").isdigit()判断立即数并加 $ 前缀。
  4. return "{op}{suffix} {src}, {dst}"...反写操作数顺序,源在前、目标在后,这正是两种语法最核心的差异。
  5. samples = [...]四组典型用例覆盖了寄存器搬运、立即数赋值、十六进制常量三种场景。

常见误区

误区GAS 是 AT&T 语法的代名词,所以 GAS 永远不认 Intel 写法。我想把 GAS 代码改成 Intel 风格,只能换汇编器,太麻烦了。

正解GAS 通过 .intel_syntax 伪指令完全可以切换为 Intel 语法。写 .intel_syntax noprefix 后,操作数顺序、寄存器前缀都变成 Intel 风格。只是默认是 AT&T,并不代表不支持另一种。就像连锁大厨平时用标准菜谱,但你说要家常口味,他也能做。

为何常见:大部分教材和教程只讲 GAS 的默认行为,没人提 .intel_syntax,于是初学者误把“默认”当成“唯一”。

误区NASM 编译完 .o 文件后,直接 `ld` 链接就能跑,不需要管格式。我在 NASM 里写了完整程序,链接却总报错。

正解NASM 默认输出 bin 裸格式,没有 ELF 重定位表和符号信息,ld 无法直接处理。必须在命令行加 -f elf 或 -f elf64 明确指定目标文件格式,链接器才能读懂。就像你炒好一道菜,得装进合适的餐盒才能外送。

为何常见:新手教程常直接给源码,省略编译命令细节,导致很多人不知道 -f 参数的关键作用。

误区写 `mov %ebx, %eax` 时,我以为是把 eax 的值搬进 ebx,因为传统语文习惯里“先读到谁”就以为谁是目标。

正解AT&T 语法永远“源在前,目标在后”,mov %ebx, %eax 的实际含义是把 ebx 的值复制到 eax。Intel 语法恰好相反,目标在左。用“从哪来到哪去”的箭头理解,就不会踩坑了。

为何常见:人脑默认从左到右是“动作的接受者”,而 AT&T 是“动作的发出者”在前,这种直觉反差导致高频出错。

误区宏和子程序都是复用代码,我随便用一个就行。我把每个宏都写成 call 子程序,程序跑得莫名奇慢。

正解宏是编译期展开的源码复制,每次调用都完整插入代码,会增加体积但减少调用开销;子程序是运行时跳转执行,代码只存一份但有调用和返回的额外开销。频繁执行的小段操作适合宏,大段低频逻辑适合子程序。

为何常见:从 C 语言来看宏和函数,认知惯性延续到汇编,忽略了汇编宏是纯文本替换这一本质。

面试题

Q1. GAS 默认采用哪种汇编语法?

单选题 · easy

  • A.Intel 语法
  • B.AT&T 语法← 正确答案
  • C.MASM 语法
  • D.ARM 语法

答案:B。GAS 是 GNU 工具链的标配汇编器,默认使用 AT&T 语法,操作数“源在前、目标在后”。Intel 语法需要显式用 .intel_syntax 指令切换。这种默认行为与 GCC 的代码生成风格一脉相承,像连锁大厨的默认菜谱。

Q2. NASM 编译时使用什么参数指定目标文件格式?

单选题 · easy

  • A.-o
  • B.-g
  • C.-f← 正确答案
  • D.-l

答案:C。-f(format)参数决定 NASM 输出格式,如 -f elf 用于 Linux 目标文件,-f bin 输出裸二进制。-o 指定输出文件名,-g 生成调试信息,-l 生成列表文件。记住“打包格式”归 -f 管。

Q3. 在 AT&T 语法中,立即数 5 的正确写法是?

单选题 · easy

  • A.5
  • B.$5← 正确答案
  • C.%5
  • D.#5

答案:B。AT&T 语法给立即数加 $ 前缀,写为 $5;寄存器加 % 前缀,写为 %eax。Intel 语法中两者都不加前缀。# 在 GAS 中用于注释,5 裸写会被当作内存地址。

Q4. 指令 movl $5, %eax 对应的 Intel 语法写法是?

单选题 · mid

  • A.mov eax, 5← 正确答案
  • B.mov 5, eax
  • C.movl eax, $5
  • D.mov $5, %eax

答案:A。Intel 语法操作数顺序相反且不加前缀,目标在前、源在后,所以 mov eax, 5 正确。选项 D 只是 AT&T 写法换皮,选项 B 方向反了,选项 C 混用了两种规则。

Q5. 关于 ELF 文件的四个组成部分,下列说法正确的是?

单选题 · mid

  • A.只有机器码和调试信息
  • B.只有文本和只读数据
  • C.包含 ELF 头、程序头表、节区和节头表← 正确答案
  • D.只包含符号表和重定位表

答案:C。ELF 标准结构包括 ELF 头、程序头表、节区数据和节头表。ELF 头描述总体信息,程序头表告知加载器映射方式,节区承载内容,节头表索引各节。选项 A/B/D 都只描述了一部分,以偏概全。

Q6. 以下哪些是 AT&T 语法的典型特征?

多选题 · mid

  • A.寄存器加 % 前缀← 正确答案
  • B.立即数加 $ 前缀← 正确答案
  • C.操作数目标在前
  • D.指令带 b/w/l/q 后缀← 正确答案

答案:ABD。AT&T 语法四件套:寄存器加 %、立即数加 $、操作数“源在前目标在后”、指令带宽度后缀。选项 C 把顺序说反了,Intel 才是目标在前。识别这三点,就能一眼判断代码属于哪种流派。

Q7. 在 GAS 中定义一个无参数宏,正确的伪指令组合是?

单选题 · mid

  • A.%macro / %endmacro
  • B..macro / .endm← 正确答案
  • C.macro / endmacro
  • D..macro / .macro_end

答案:B。GAS 的宏用 .macro 开始、.endm 结束,参数在宏体内用 \arg 引用。NASM 才用 %macro/%endmacro。不同汇编器的宏语法就像不同餐厅的定制菜流程,不能互换使用。

Q8. 关于 GAS 与 NASM 的差异,正确的说法有哪些?

多选题 · hard

  • A.GAS 必须依靠 GCC 才能运行
  • B.NASM 可通过 -f 指定多种输出格式← 正确答案
  • C.GAS 也支持 Intel 语法← 正确答案
  • D.NASM 只能在 Windows 上使用

答案:BC。GAS 虽随 GCC 分发但可独立运行,选项 A 错误;NASM 是跨平台工具,选项 D 错误。NASM 的 -f 参数支持 elf/bin/win64 等多种格式,GAS 通过 .intel_syntax 支持 Intel 语法,因此 B、C 正确。两个汇编器是“通用翻译官”,不是专属工具。

Q9. 使用 strip 命令处理 ELF 文件后,最直接的后果是?

单选题 · hard

  • A.程序运行更快
  • B.文件体积变小且符号表被删除← 正确答案
  • C.机器码被全部清除
  • D.自动启用调试模式

答案:B。strip 删除符号表和调试信息,使文件变小、逆向难度提高,但不改变机器码和运行速度。选项 A 错在体积小不等于运行快,选项 C 完全错误,选项 D 张冠李戴。发布正式版前 strip 是常规操作。

Q10. 在实模式下,物理地址由段基址和偏移合成,以下哪些说法正确?

多选题 · extreme

  • A.段基址必须乘以 16← 正确答案
  • B.偏移地址最大 64KB← 正确答案
  • C.物理地址等于段基址加偏移← 正确答案
  • D.段基址可以任意奇数

答案:ABC。实模式地址合成公式是“段值 × 16 + 偏移”,偏移宽度 16 位故最大 64KB,选项 A、B、C 正确。段值必须能对应 16 字节对齐的基址,实际段值可以是奇数,但乘以 16 后基址总是 16 的倍数,所谓“任意奇数”是段值而非基址,表述含糊故 D 错误。