【问题标题】:Pointer to .text in real mode assembly在实模式汇编中指向 .text 的指针
【发布时间】:2015-10-05 17:54:17
【问题描述】:

我对实模式寻址一定有一些基本的误解。我正在尝试设置一个功能以在实模式下通过 BIOS 中断打印文本。我正在使用在 DOSBox 下执行的 .com 文件测试代码。 .text 部分结束于 0x1000(.com 文件中的 0x0F00)。所以假设我想打印该文本的第一个字母。

xor ebx, ebx
mov ecx, 1
mov ah, 10
mov al, ds:[0x1000]
int 0x10

这行得通,并打印出“H”,因为我没有想象力。但是我不希望它只打印出同一个字母。我想传入一个指针,并且我想在打印更多文本时增加该指针。在这个阶段,我很高兴能从寄存器中读取偏移量。所以我做了以下更改。

mov edx, 0x1000
mov al, ds:[edx]

并且没有字符被打印。我尝试过使用 esi 和 edi 寄存器,结果相同。使用 lea edx,byte ptr [0x1000] 产生相同的结果。更糟糕的是,尝试使用 16 位等效项(dx、si、di)会导致程序挂起。我尝试查看 .com 文件中的机器代码,但找不到任何明显错误的内容。

我正在使用自定义链接器脚本和 objcopy 调用使用 gcc 编译代码以生成 .com 文件。未链接任何库,目标架构为 386。

任何帮助将不胜感激。

编辑:完整列表。

directio.s

.intel_syntax
.global _printChar

_printChar:
    push ebp;
    mov ebp, esp;

    xor edx, edx;
    xor ebx, ebx;
    xor eax, eax;
    mov ecx, 1;

    mov ah, 10;
    mov edx, 0x1000;
    mov al, ds:[edx];
    int 0x10;

    mov esp, ebp
    pop ebp;
    ret;

dirTest.c

asm
(
    ".code16gcc;\n" \
    "call _dosmain;\n" \
    "mov ah, 0x4C;\n" \
    "int 0x21;\n"
);

#include "directio.h"

int dosmain(void)
{
    printChar("Hello World!");
    return 0;
}

com_mingw.ld

SECTIONS
{
    . = 0x0100;
    .text :
    {
        *(.text);
    }
    .data :
    {
        *(.data);
        *(.bss);
        *(.rodata);
    }
    _heap = ALIGN(4);
}

所有这些都使用以下命令行编译。

gcc -std=gnu99 -Os -nostdlib -m32 -masm=intel -march=i386 -ffreestanding -o dirTest.com -Wl,--nmagic,--script=com_mingw.ld dirTest.c directio.s

紧随其后

objcopy dirTest.com -O binary

【问题讨论】:

  • 出于好奇,您是否在代码开头设置了DS(数据段),例如mov ax, @data,后跟mov ds, ax?如果我们看到您的整个程序(如果它不是太长的话)会有所帮助。哦,我看到你使用的是 GCC,所以设置 DS 会有点不同。我还想看看您的链接器脚本以及您用于构建和链接的实际命令
  • 我已添加完整列表。目前,我正在 DOS 环境中测试所有内容。因此 DOS 将 cs 和 ds 设置为加载 .com 文件的偏移量。这些似乎设置正确,因为从 ds:[0x1000] 加载字符有效。代码从 0x100 开始。 .text 结束于 0x1000。
  • 为了确保这不是对段偏移量的奇怪限制,我尝试了以下方法: mov edx, cs add edx, 0x0100 mov ds, edx xor edx, edx 再次,鳕鱼有效mov al, ds:[0] 很好,但是 mov al, ds:[edx] 失败了。我不知道为什么这些不是一回事。
  • 是的,抱歉,来晚了,一开始没有注意到它是一个 COM 程序(所以是的,CS=DS=ES)
  • 令人困惑的事情。使用对 printChar 的调用将字符串传递到堆栈上。您不使用堆栈上传递的地址而不是硬编码 0x1000 的任何特殊原因?

标签: assembly mingw i386 real-mode


【解决方案1】:

_printChar 应该是一个 16 位的函数,所以不要把它组装成 32 位的。在 .s 文件的顶部添加 .code16gcc 并将 32 位寄存器更改为 16 位:

.code16gcc
.intel_syntax
.global _printChar

_printChar:
    push bp;
    mov bp, sp

    xor dx, dx
    xor bx, bx
    xor ax, ax
    mov cx, 1

    mov ah, 10
    mov dx, 0x1000
    mov al, ds:[dx]     ; ERROR! See comments.
    int 0x10

    mov sp, bp
    pop bp
    ret

现在,它应该(希望)工作。

【讨论】:

  • 编译器抱怨 ds:[dx],但是用 ds:[si] 替换它可以修复它,这可能更符合我使用寄存器的方式。所以我假设这与在 32 位和 16 位模式下使用不同的段寄存器有关?还是只是我应该在开始抛出 32 位代码之前切换到实模式?
  • @user5408560:哦,是的,对不起。没有基址寄存器选项[dx],只有[edx]。您的解决方案(更改为 [si])是最好的。
【解决方案2】:

自从我上次以.com 图像格式编写任何实模式代码以来,它一定是大约三年前了,但这里有一些观察:

  • 在编写.com 格式代码时,定义多个部分是不常见的。该格式只允许一个segment,最大初始图像大小为64Kb;通常,这可能包含逻辑部分,通常称为“CODE”和“DATA”,或“TEXT”和“DATA”,但它们必须组合成一个物理段;禁止任何“STACK”类的显式段。
  • MS-DOS 是 16 位操作系统,因此您必须使用 16 位寄存器(或其 8 位子寄存器)编写代码。
  • 16 位处理器不允许您使用除 BX、SI、DI 和 BP 之外的任何寄存器作为间接内存寻址的基址寄存器;您不能为此目的使用 DX、(和您一样)、CX 或 AX; (您可能可以使用 SP,但您通常不理会它,保留它作为堆栈指针的预期用途。
  • 当操作系统加载.com格式的进程映像时,它首先分配一个环境块,然后是它后面的程序段。然后用管理数据填充程序段的前 256 个字节(创建通常称为程序段前缀或 PSP),然后立即加载 .com 图像,从地址 0x100 开始,(您提到的 0x1000 只是程序段中的任意偏移量,在该偏移量处(可能)定义了您的数据,但它并没有什么神圣不可侵犯的地方——程序代码可以很容易地出现在那里。
  • 加载映像后,操作系统会将所有四个段寄存器设置为程序段的开头,并设置 CS:IP(通过执行 FAR JMP 或 FAR CALL)开始执行您的在适当的入口地址处进行处理,该入口地址总是位于程序段内的偏移量 0x100(以任何方式与您的 0x1000 数据地址相关) .

【讨论】:

  • 它似乎与模式有关,而不是操作系统,但是是的,32 位指令是罪魁祸首。是的,0x1000 偏移量是任意的。就在 .text 在这个特定的 .com 文件中结束的位置。我要使用的实际偏移量将作为函数参数在堆栈上传递。其余的符合我一直在使用的假设,但得到确认很有用。谢谢。
  • 那么,事实的准确性会吸引反对票吗?有点证实了我对 SO 普遍毫无价值的总体印象;我想我不会费心提供任何进一步的建议,或更正那些泛滥的错误信息
  • @KeithMarshall,我支持你。我从你的回答中学到了很多。这就是SO的优势。相信我,更多的人会欣赏你的努力/知识。
猜你喜欢
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多