NASM用寄存器值填充数组报错:expression is not simple or relocatable
问题描述
我正在编写一个简单的NASM x86-64汇编程序,希望根据输入的long型索引i(取值0至15),获取对应64位寄存器(rax至r15)的值。我的实现思路是:在.data段分配一个存储所有寄存器值的数组,通过索引i读取对应位置的值。编写的代码如下:
SECTION .data ; array containing all our registers regs dq rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15 SECTION .text global getReg getReg: ; rdi is the first argument we got, and the registers are 8 bytes each, ; so the desired index is our argument multiplied by 8 imul rax, rdi, 8 ; move the quadword at index rax of regs array into register rax (for returning) mov rax, [regs + rax] ret
执行编译命令nasm -f elf64 getReg.s -o getReg.o时,第3行出现16次“expression is not simple or relocatable”错误。作为汇编新手,我无法理解该错误的含义及成因,希望获得问题解决帮助,以及代码中的其他错误排查建议和NASM通用最佳实践指导。
错误原因分析
dq rax, rbx...这种写法完全错误——.data段只能存储编译期确定的静态常量,而寄存器是运行时才会有具体值的动态实体,汇编器根本无法在编译阶段把寄存器的“值”写入.data段,所以抛出“expression is not simple or relocatable”错误,意思是这个表达式无法被汇编器解析为可重定位的静态内容。
正确实现方案
要实现“通过索引获取寄存器值”的功能,必须在运行时动态选择寄存器读取,最常用且易读的方式是用跳转表(对应C语言的switch-case逻辑):
SECTION .text global getReg getReg: ; 先检查索引是否在0-15范围内,避免越界 cmp rdi, 15 ja invalid_index ; i>15时跳转到错误处理 ; 计算跳转表偏移:索引*8(每个跳转地址占8字节) lea rax, [rel reg_jump_table] mov rax, [rax + rdi*8] jmp rax ; 跳转表:存储每个寄存器对应的读取函数地址 reg_jump_table: dq get_rax, get_rbx, get_rcx, get_rdx dq get_rsi, get_rdi, get_rbp, get_rsp dq get_r8, get_r9, get_r10, get_r11 dq get_r12, get_r13, get_r14, get_r15 ; 每个寄存器的读取逻辑:把目标寄存器的值放到rax(返回寄存器) get_rax: mov rax, rax; ret get_rbx: mov rax, rbx; ret get_rcx: mov rax, rcx; ret get_rdx: mov rax, rdx; ret get_rsi: mov rax, rsi; ret get_rdi: mov rax, rdi; ret get_rbp: mov rax, rbp; ret get_rsp: mov rax, rsp; ret get_r8: mov rax, r8; ret get_r9: mov rax, r9; ret get_r10: mov rax, r10; ret get_r11: mov rax, r11; ret get_r12: mov rax, r12; ret get_r13: mov rax, r13; ret get_r14: mov rax, r14; ret get_r15: mov rax, r15; ret ; 索引越界的错误处理:返回0(可根据需求修改) invalid_index: xor rax, rax ret
原代码其他错误排查
- 逻辑根本性错误:哪怕.data段的写法能编译,数组里存的也只是汇编器对寄存器的符号标记,绝不是运行时寄存器的实际值,完全达不到需求。
- 返回值逻辑问题:原代码里
imul rax, rdi,8会直接覆盖rax的初始值,但rax是x86-64调用约定的返回寄存器——不过原逻辑本身错误,这点影响相对次要。
NASM通用最佳实践
- 区分静态数据和动态值:.data/.rodata段只能放编译期确定的常量(数值、字符串、跳转表地址等),不能放运行时才存在的寄存器值、栈变量值等动态内容。
- 严格遵守调用约定:x86-64 System V调用约定中,rdi/rsi/rdx/rcx/r8/r9是参数寄存器,rax是返回寄存器,rbx/rbp/r12-r15是非易失性寄存器(函数调用前要保存,调用后要恢复)。
- 必须加边界检查:任何涉及索引、指针的操作都要检查范围,避免越界访问导致崩溃或未定义行为。
- 使用相对寻址:用
lea rax, [rel label]而非直接lea rax, [label],生成位置无关代码(PIC),兼容动态链接场景。 - 注释关键逻辑:汇编代码可读性差,要给跳转逻辑、寄存器用途、特殊指令添加清晰注释。
- 分步测试:先写小片段代码验证单个功能(比如先实现读取rax的逻辑),再逐步扩展,避免一次性写大量代码难以调试。
内容的提问来源于stack exchange,提问作者Robbie
相关产品推荐
相关产品推荐

