如何在NASM 64位模式下正确实现地址寻址(编译器行为异常)
我明白你遇到的麻烦了——进入长模式后,内核只有映射到32位地址才能正常运行,根源就是NASM默认把内存操作数的地址编译成了32位值,没法正确访问你指定的0x100000000以上的地址。下面来一步步解决这个问题:
问题根源
当你使用[org 0x100000000]和[bits 64]时,符号l1的地址已经超出了32位有符号整数的范围(32位有符号数最大只能到0x7FFFFFFF)。NASM在bin输出格式下,默认会对内存操作数优先使用32位有符号位移的RIP相对寻址或32位绝对地址编码,这就导致它无法正确表示你的64位地址,只能截断成32位,最终内核找不到正确的内存位置。
而mov rbx, l1能正常工作,是因为这条指令是把立即数加载到寄存器,NASM对64位立即数会自动生成完整的64位编码,不会做截断。
解决方案
这里有三种可行的方法,你可以根据自己的内核设计选择:
方法1:显式指定64位绝对地址
使用NASM的abs关键字强制编译器为内存操作数生成64位绝对地址编码,修改你的代码如下:
[org 0x100000000] [bits 64] ; 强制使用64位绝对地址访问 mov rax, [abs l1] ; 原有能工作的代码保留 mov rbx, l1 mov rax, [abs l1] jmp $ l1: dq 0x1234
编译后这条指令会生成类似48 8b 04 25 00 00 00 10的编码,直接访问64位的绝对地址。
方法2:设置默认寻址方式为绝对寻址
在代码开头添加default abs,让NASM默认对所有内存操作数使用绝对地址,当地址超过32位时自动生成64位编码:
[org 0x100000000] [bits 64] default abs ; 全局设置默认使用绝对寻址 mov rax, [l1] ; 现在会自动生成64位绝对地址 mov rbx, l1 mov rax, [l1] jmp $ l1: dq 0x1234
这种方法不需要修改每一条内存操作指令,适合大量使用绝对地址的代码。
方法3:使用RIP相对寻址(推荐的x86-64实践)
如果你的内核支持位置无关代码(PIC),或者可以通过RIP相对寻址来访问符号,那么可以去掉org指令,改用rel关键字显式指定RIP相对寻址:
[bits 64] default rel ; 全局设置默认使用RIP相对寻址 mov rax, [l1] ; 自动使用RIP相对的64位位移 mov rbx, l1 mov rax, [l1] jmp $ l1: dq 0x1234
这种方式更符合x86-64的指令优化原则,生成的代码也更紧凑,不过需要确保你的内核加载逻辑支持位置无关的运行方式。
验证方法
编译后可以用反汇编工具确认指令是否正确,比如使用ndisasm -b 64 test.bin查看输出,检查mov rax, [l1]对应的指令编码是否包含完整的64位地址。
内容的提问来源于stack exchange,提问作者user1237916231

