【问题标题】:Why is gcc not writing to memory correctly when I use __asm__为什么当我使用 __asm__ 时 gcc 无法正确写入内存
【发布时间】:2016-10-22 08:36:36
【问题描述】:

我有一段代码调用了 BIOS 文本输出函数,INT 0x10, AH=0x13

static void __stdcall print_raw(uint8_t row, uint8_t col, uint8_t attr,
                               uint16_t length, char const *text)
{
    __asm__ __volatile__ (
        "xchgw %%bp,%%si\n\t"
        "int $0x10\n\t"
        "xchgw %%bp,%%si\n\t"
        :
        : "d" (row | (col << 8)), "c" (length), "b" (attr), "a" (0x13 << 8), "S" (text)
    );
}

我还有一个可以在屏幕上打印数字的功能:

void print_int(uint32_t n)
{
    char buf[12];
    char *p;
    p = buf + 12;
    do {
        *(--p) = '0' + (n % 10);
        n /= 10;
    } while (p > buf && n != 0);

    print_raw(1, 0, 0x0F, (buf + 12) - p, p);
}

我花了很多时间试图弄清楚为什么屏幕上什么都没有出现。我深入研究并查看了为print_int 生成的代码:

    .globl  print_int
    .type   print_int, @function
print_int:
.LFB7:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    $10, %ecx
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    pushl   %esi
    pushl   %ebx
    .cfi_offset 6, -12
    .cfi_offset 3, -16
    leal    -8(%ebp), %esi
    leal    -20(%ebp), %ebx
    subl    $16, %esp
    movl    8(%ebp), %eax
.L4:
    xorl    %edx, %edx
    decl    %esi
    divl    %ecx
    testl   %eax, %eax
    je  .L6
    cmpl    %ebx, %esi
    ja  .L4
.L6:
    leal    -8(%ebp), %ecx
    movl    $4864, %eax
    movb    $15, %bl
    movl    $1, %edx
    subl    %esi, %ecx
#APP
# 201 "bootsect.c" 1
    xchgw %bp,%si
    int $0x10
    xchgw %bp,%si

# 0 "" 2
#NO_APP
    addl    $16, %esp
    popl    %ebx
    .cfi_restore 3
    popl    %esi
    .cfi_restore 6
    popl    %ebp
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret

如果您仔细查看L4 的循环,您会发现它从未将任何内容存储到buf。它省略了指令!它只是将其划分,从不将任何字符存储到缓冲区中。

【问题讨论】:

  • 最重要的是,您不能直接从 32 位保护模式代码中调用 BIOS 函数。您只能从 16 位实模式代码或虚拟 8086 模式调用它们。
  • @AlexeyFrunze 这是在实模式下运行。
  • 是否有扩展段限制?
  • 我想知道当编译器生成跳转表(用于 switch 语句)时会发生什么。代码一定是崩溃了。
  • @AlexeyFrunze 不,它有效。链接器将所有内容解析为足够低的地址以使其有意义。这一切都必须在 64KB 以内(除非您使用内联汇编并短暂更改段寄存器)。 .code16gcc 指令告诉汇编器相应地自动使用 addr16 和 data16 前缀以使代码在 16 位模式下工作。这只是引导代码,它不需要做太多事情。您需要一个自定义链接器脚本来实现这些低地址。

标签: gcc inline-assembly compiler-bug


【解决方案1】:

当使用__asm__ 语句时,优化器可能会导致这种错误代码。你需要非常小心你的约束。在这种情况下,编译器没有“看到”我正在通过esi 中的指针访问内存,正如"S" (text) 输入约束中所指定的那样。

解决方法是在__asm__语句的clobber部分添加一个"memory"clobber:

__asm__ __volatile__ (
    "xchgw %%bp,%%si\n\t"
    "int $0x10\n\t"
    "xchgw %%bp,%%si\n\t"
    :
    : "d" (row | (col << 8)), "c" (length), "b" (attr), "a" (0x13 << 8), "S" (text)
    : "memory"
);

这告诉编译器您依赖于内存值,并且您可能会更改内存值,因此在汇编语句执行之前确保内存是最新的,并确保不依赖任何内存应该是偏执的它可能缓存在寄存器中的值。有必要防止编译器在我的代码中省略存储到buf

【讨论】:

  • 编译我有点惊讶。您同时使用 ("S") 和破坏 esi,这通常会产生错误。而且由于si 在这里实际上似乎没有改变,我不确定你为什么要破坏它。更重要的是,应该可以使用local register variables 设置bp 并为自己节省一个寄存器和2条指令。 FWIW。
  • @DavidWohlferd 啊,好消息,我猜这是因为 Doug 的代码是 16 位汇编,编译器没有意识到“S”和“esi”本质上指向同一个东西......
  • @DavidWohlferd 是的,我想我可以删除“esi”clobber,我补充说,当我试图弄清楚它为什么不起作用时,它最终意外地停留在那里。跨度>
  • 至于 2 xchgw,我希望您可以执行类似 register char *foo asm ("bp") = text; __asm__ __volatile__ ("int $0x10" : : "d" (row | (col &lt;&lt; 8)), "c" (length), "b" (attr), "a" (0x13 &lt;&lt; 8), "r" (foo) : "memory"); 的操作。将您的 asm 保持在最低限度往往会产生更高效的代码和更少的错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
  • 2010-10-11
  • 2019-11-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多