【问题标题】:error: unsupported size for integer register错误:整数寄存器的大小不受支持
【发布时间】:2020-06-23 18:58:59
【问题描述】:

我在 Windows 上使用 i686 gcc。当我使用单独的 asm 语句构建代码时,它起作用了。但是,当我尝试将它组合成一个语句时,它不会生成并给我一个error: unsupported size for integer register

这是我的代码

u8 lstatus;
u8 lsectors_read;
u8 data_buffer;

void operate(u8 opcode, u8 sector_size, u8 track, u8 sector, u8 head, u8 drive, u8* buffer, u8* status, u8* sectors_read)
{
    asm volatile("mov %3, %%ah;\n"
                "mov %4, %%al;\n"
                "mov %5, %%ch;\n"
                "mov %6, %%cl;\n"
                "mov %7, %%dh;\n"
                "mov %8, %%dl;\n"
                "int $0x13;\n"
                "mov %%ah, %0;\n"
                "mov %%al, %1;\n"
                "mov %%es:(%%bx), %2;\n"
                : "=r"(lstatus), "=r"(lsectors_read), "=r"(buffer)
                : "r"(opcode), "r"(sector_size), "r"(track), "r"(sector), "r"(head), "r"(drive)
                :);
    status = &lstatus;
    sectors_read = &lsectors_read;
    buffer = &data_buffer;
}

【问题讨论】:

  • 这是 16 位代码。你用什么选项来编译它?也许指针宽度是个问题?如果删除一些操作数,哪一个会导致编译错误?此外,您忘记了 "memory" clobber,在将寄存器中的指针传递到取消引用它们的内联 asm 语句时,这通常是安全所必需的。另外,为什么全局变量作为临时变量?你为什么要在函数结束之前给按值函数 args 赋值?调用者不会看到status=lstatus 的任何效果,这不是C 的工作方式。你的意思是*status = lstatus吗?
  • 如果您打算尝试将 GCC 与 16 位代码一起使用,我可能会建议您考虑使用分叉的 ia16-gcc elf 交叉编译器。这是一项正在进行的工作,但它处理 16 位代码比常规 GCC 编译器要好得多。甚至还有一个 lib86 库,允许您通过函数进行 BIOS 中断调用(尽管它似乎缺乏使用 32 位寄存器的能力,而这是少数 BIOS 服务所必需的)
  • @mike 我在你之前的问题中向你展示了这个任务的正确代码是什么样的。你为什么要回到这个糟糕透顶的实现?
  • @mike 请注意,BIOS 调用只是让es:bx 保持不变。您需要在es:bx 中提供缓冲区地址,BIOS 会将数据加载到该地址。当调用返回时地址仍然存在,这就是为什么一些资源记录调用返回缓冲区地址的原因。但是,这通常适用于除ax 之外的所有寄存器。
  • @PeterCordes Gcc 要求ssesds 指向同一个内存,因此无需在内联程序集中设置es

标签: c assembly gcc x86-16 inline-assembly


【解决方案1】:

错误信息有点误导。这似乎是因为 GCC 用完了 8 位寄存器。

有趣的是,如果您只是编辑模板以删除对最后 2 个操作数 (https://godbolt.org/z/oujNP7) 的引用,即使没有将它们从输入约束列表中删除,它也不会出现错误消息! (修剪你的asm 语句是一种有用的调试技术,可以找出 GCC 不喜欢它的哪一部分,而不用关心 asm 是否会做任何有用的事情。)

删除 2 个较早的操作数并更改数字表明 "r"(head), "r"(drive) 并不是一个特别的问题,只是所有内容的组合。

看起来 GCC 正在避免像 AH 这样的高 8 寄存器作为输入,而 x86-16 只有 4 个低 8 寄存器,但您有 6 个 u8 输入。 所以我认为 GCC 意味着它用完了它愿意使用的字节寄存器。

(3 个输出未声明为 early-clobber,因此允许它们与输入重叠。)


您可以通过使用"rm" 为GCC 提供选择内存输入的选项来解决此问题。 (x86-specific constraints"Q" 这样被允许选择一个高 8 寄存器不会有帮助,除非你要求它选择 正确 一个让编译器发出一个 mov你。)这可能会让你的代码编译,但结果会完全被破坏。

您重新引入了与以前基本相同的错误:没有告诉编译器您编写了哪些寄存器,例如,您的 mov %4, %%al 将在您实际读取该操作数之前覆盖 GCC 选择作为输入的寄存器之一。

在您使用的所有寄存器上声明 clobbers 将导致没有足够的寄存器来保存所有输入变量。 (除非您允许内存源操作数。)这可能有效,但效率非常低:如果您的 asm 模板字符串以 mov 开头或结尾,那么您几乎总是做错了。

此外,除了您使用内联汇编的方式之外,还有其他严重的错误。您不提供指向缓冲区的输入指针。 int $0x13 不会为您分配新缓冲区,它需要 ES:BX 中的指针(它取消引用但未修改)。 GCC 要求 ES=DS=SS,因此您必须在调用 C 代码之前正确设置分段,而不是每次调用都必须这样做。

另外,即使在内联汇编之外的 C 术语中,您的函数也没有意义。 status = &lstatus; 修改函数 arg 的 value,而不是取消引用它来修改指向的输出变量。这些赋值写入的变量在函数结束时消失。但是全局临时变量确实必须更新,因为它们是全局的,并且其他一些函数可以看到它们的值。也许您的意思是像*status = lstatus; 这样的变量类型不同?

如果 C 的问题不明显(至少一旦被指出),您需要更多的 C 练习,然后才能尝试混合 C 和 asm,这需要您了解两者 很好,为了用准确的约束正确地向编译器描述你的 asm。


@fuz's answer to your previous question 中显示了实现此功能的良好且正确的方法。如果您想了解约束如何替换您的 mov 指令,请编译它并查看编译器生成的指令。有关指南和文档的链接,请参阅 https://stackoverflow.com/tags/inline-assembly/info。例如@fuz 没有 ES 设置的版本(因为 GCC 需要你在调用任何 C 之前已经完成):

typedef unsigned char u8;
typedef unsigned short u16;

// Note the different signature, and using the output args correctly.
void read(u8 sector_size, u8 track, u8 sector, u8 head, u8 drive,
    u8 *buffer, u8 *status, u8 *sectors_read)
{
    u16 result;

    asm volatile("int $0x13"
        : "=a"(result)
        : "a"(0x200|sector_size), "b"(buffer),
          "c"(track<<8|sector), "d"(head<<8|drive)
        : "memory" );  // memory clobber was missing from @fuz's version

    *status = result >> 8;
    *sectors_read = result >> 0;
}

编译如下,使用GCC10.1 -O2 -m16 on Godbolt:

read:
        pushl   %ebx
        movzbl  12(%esp), %ecx
        movzbl  16(%esp), %edx
        movzbl  24(%esp), %ebx      # load some stack args
        sall    $8, %ecx
        movzbl  8(%esp), %eax
        orl     %edx, %ecx          # shift and merge into CL,CH instead of writing partial regs
        movzbl  20(%esp), %edx
        orb     $2, %ah
        sall    $8, %edx
        orl     %ebx, %edx
        movl    28(%esp), %ebx     # the pointer arg
        int $0x13                  # from the inline asm statement
        movl    32(%esp), %edx     # load output pointer arg
        movl    %eax, %ecx
        shrw    $8, %cx
        movb    %cl, (%edx)
        movl    36(%esp), %edx
        movb    %al, (%edx)
        popl    %ebx
        ret

可能使用register u8 track asm("ch") 或其他东西让编译器只编写部分regs 而不是移位/或。


如果您不想了解约束的工作原理,请不要使用 GNU C 内联汇编。您可以改为编写从 C 调用的独立函数,这些函数根据编译器使用的调用约定接受 args(例如 gcc -mregparm=3,或者使用传统的低效调用约定只接受堆栈上的所有内容。)

您可以比 GCC 的上述代码生成做得更好,但请注意,内联 asm 可以优化到周围的代码并避免一些实际复制到内存以通过堆栈传递 args。

【讨论】:

  • 谢谢!我尝试了fiz的答案。不过,GCC 无法识别 cobbler 中的寄存器 es。
  • @mike:如果您使用的 GCC 就是这种情况,那么无论如何它肯定需要 ES=DS=SS,并且您应该使用评论时已经添加到此答案的版本。但说真的,当你还在犯像status = &amp;lstatus; 这样的纯C 初学者错误时,请考虑使用比16 位x86 asm 更简单且支持更好的东西。学习语言时犯错是正常的,但在学习时限制复杂性也是一个好主意。 GNU C inline asm 很难正确理解,你真的必须了解 asm C,而且也不利于学习。
  • 我在这个答案diskbios.h中有一个更高级的版本。它处理重试,返回进位标志,状态处理一些有问题的 BIOS,而不是传递大量参数,而是使用数据结构。 stackoverflow.com/a/52047408/3857942
  • @mike 如果我发布的代码不起作用,你为什么不告诉我?我假设 ia16-gcc 写了它,因为你没有说你使用的是哪个 gcc 版本。接受帮助是一条两条路。拒绝沟通,只会让自己变得更加困难,浪费很多人的时间。不要那样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-25
  • 2020-07-20
  • 2017-08-03
  • 1970-01-01
  • 1970-01-01
  • 2012-02-08
  • 2022-09-25
相关资源
最近更新 更多