x86 16位汇编:如何修正可执行二进制加载后的地址指向问题?
实模式动态加载二进制的绝对地址重定位问题
问题描述
我有一个引导加载程序将内核加载到0x0500,内核初始化后会加载并调用一个二进制文件。该二进制代码如下:
BITS 16 ORG 0x0000 START_ADDR dw init ; 内核调用的入口地址 times 16-($-$$) db 0 ; 保留字段 init: mov ah, 0x04 ; 内核打印字符串功能 mov si, [STR_TO_PRINT] int 0x20 ; 调用内核GPU驱动 jmp short main main: jmp short main STR_TO_PRINT db "Hello", 0 times 512-($-$$) db 0 ; 填充到扇区大小
内核会将该二进制加载到动态内存位置(如0x4000、0x7C00),通过「加载地址+START_ADDR」计算执行入口,跳转后代码能正常运行,但STR_TO_PRINT的引用是文件内偏移(如0x0020)而非实际内存地址(如0x7C20),导致无法正确读取字符串。需要修正这个问题,使用NASM汇编器,运行在16位实模式。
解决方案
方法1:使用NASM相对寻址语法(最简单)
利用NASM的rel关键字让汇编器生成相对当前指令的地址,自动适配加载位置:
BITS 16 ORG 0x0000 START_ADDR dw init times 16-($-$$) db 0 init: mov ah, 0x04 lea si, [rel STR_TO_PRINT] ; 生成相对地址,自动计算实际内存位置 int 0x20 jmp short main main: jmp short main STR_TO_PRINT db "Hello", 0 times 512-($-$$) db 0
原理:[rel symbol]会让汇编器计算符号相对于当前指令的偏移量,运行时CPU会根据当前IP值加上偏移得到实际内存地址,完全不依赖加载基址的硬编码,适合位置无关代码(PIC)场景。
方法2:通过寄存器传递加载基址(兼容性好)
如果需要兼容旧版NASM或无法使用rel语法,可以让内核在调用入口前将加载基址存入某个寄存器(比如BX),然后在二进制中用基址加上符号偏移得到实际地址:
BITS 16 ORG 0x0000 START_ADDR dw init times 16-($-$$) db 0 init: ; 内核调用init前需将二进制的加载基址写入BX mov ah, 0x04 mov si, bx ; 加载基址 add si, STR_TO_PRINT - $$ ; 加上符号在文件内的偏移量 int 0x20 jmp short main main: jmp short main STR_TO_PRINT db "Hello", 0 times 512-($-$$) db 0
原理:$$代表当前段的起始地址(这里ORG=0,所以$$=0),STR_TO_PRINT - $$就是符号在二进制文件内的偏移,加上BX中的加载基址后,SI就指向了字符串的实际内存地址。
方法3:重定位表(适合复杂二进制)
如果二进制包含大量绝对地址引用,可以在文件末尾添加重定位表,记录所有需要修正的地址偏移。内核加载二进制后,遍历重定位表,用「加载基址-预期基址」的差值修正每个地址。不过对于仅单个字符串的简单场景,前两种方法更高效。
内容的提问来源于stack exchange,提问作者ja0
相关产品推荐
相关产品推荐

