You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 04:04:59