如何阻止 icc 消除从内联汇编中调用的函数

Iva*_*anq 7 c optimization icc

背景

我正在制作一个需要同时运行多个任务的应用程序。我不能使用线程等,因为应用程序应该在没有任何操作系统的情况下工作(即直接从引导扇区)。使用 x86 任务看起来有点矫枉过正(在逻辑和性能方面)。因此,我决定自己实现一个任务切换实用程序。我会保存处理器状态,调用任务代码,然后恢复以前的状态。所以我必须从内联汇编中进行调用。

问题

下面是一些示例代码:

#include <stdio.h>

void func() {
    printf("Hello, world!\n");
}

void (*funcptr)();

int main() {
    funcptr = func;
    asm(
        "call *%0;"
        :
        :"r"(funcptr)
    );
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它在 icc 下完美编译,没有选项,gcc 和 clang 并产生“Hello,world!” 运行时。但是,如果我用 编译它icc main.c -ipo,它会出现段错误。

我反汇编了生成的代码icc main.c并得到以下内容:

0000000000401220 <main>:
  401220:   55                      push   %rbp
  401221:   48 89 e5                mov    %rsp,%rbp
  401224:   48 83 e4 80             and    $0xffffffffffffff80,%rsp
  401228:   48 81 ec 80 00 00 00    sub    $0x80,%rsp
  40122f:   bf 03 00 00 00          mov    $0x3,%edi
  401234:   33 f6                   xor    %esi,%esi
  401236:   e8 45 00 00 00          callq  401280 <__intel_new_feature_proc_init>
  40123b:   0f ae 1c 24             stmxcsr (%rsp)
  40123f:   48 c7 05 f6 78 00 00    movq   $0x401270,0x78f6(%rip)        # 408b40 <funcptr>
  401246:   70 12 40 00 
  40124a:   b8 70 12 40 00          mov    $0x401270,%eax
  40124f:   81 0c 24 40 80 00 00    orl    $0x8040,(%rsp)
  401256:   0f ae 14 24             ldmxcsr (%rsp)
  40125a:   ff d0                   callq  *%rax
  40125c:   33 c0                   xor    %eax,%eax
  40125e:   48 89 ec                mov    %rbp,%rsp
  401261:   5d                      pop    %rbp
  401262:   c3                      retq   
  401263:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  401268:   0f 1f 84 00 00 00 00    nopl   0x0(%rax,%rax,1)
  40126f:   00 

0000000000401270 <func>:
  401270:   bf 04 40 40 00          mov    $0x404004,%edi
  401275:   e9 e6 fd ff ff          jmpq   401060 <puts@plt>
  40127a:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
Run Code Online (Sandbox Code Playgroud)

另一方面,icc main.c -ipo产量:

0000000000401210 <main>:
  401210:   55                      push   %rbp
  401211:   48 89 e5                mov    %rsp,%rbp
  401214:   48 83 e4 80             and    $0xffffffffffffff80,%rsp
  401218:   48 81 ec 80 00 00 00    sub    $0x80,%rsp
  40121f:   bf 03 00 00 00          mov    $0x3,%edi
  401224:   33 f6                   xor    %esi,%esi
  401226:   e8 25 00 00 00          callq  401250 <__intel_new_feature_proc_init>
  40122b:   0f ae 1c 24             stmxcsr (%rsp)
  40122f:   81 0c 24 40 80 00 00    orl    $0x8040,(%rsp)
  401236:   48 8b 05 cb 2d 00 00    mov    0x2dcb(%rip),%rax        # 404008 <funcptr_2.dp.0>
  40123d:   0f ae 14 24             ldmxcsr (%rsp)
  401241:   ff d0                   callq  *%rax
  401243:   33 c0                   xor    %eax,%eax
  401245:   48 89 ec                mov    %rbp,%rsp
  401248:   5d                      pop    %rbp
  401249:   c3                      retq   
  40124a:   66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
Run Code Online (Sandbox Code Playgroud)

因此,虽然-ipo没有删除funcptr变量(参见地址 401236),但它确实删除了赋值。我猜 icc 注意到它func不是从 C 代码中调用的,因此可以安全地删除它,因此funcptr允许包含垃圾。但是,它没有注意到我是func通过程序集间接调用的。

我试过的

  1. 替换"r"(funcptr)"r"(func)作品,但我无法对特定功能进行硬编码(参见背景)。
  2. 调用funcptr和/或func在内联汇编块之前和/或之后没有帮助,因为 icc 只是 inlines printf("Hello, world!\n");
  3. 我无法摆脱内联汇编,因为我必须在调用前后进行低级寄存器、标志和堆栈操作。
  4. 使 funcptr 不稳定会产生以下警告,但仍然存在段错误:
a value of type "void (*)()" cannot be assigned to an entity of type "volatile void (*)()"
Run Code Online (Sandbox Code Playgroud)
  1. 添加volatile到几乎所有其他单词也无济于事。
  2. 移动func和/或funcptr到其他源文件,然后将它们链接在一起并没有帮助。
  3. 将内联汇编移动到单独的函数不起作用。

我做错了什么还是icc错误?如果是前者,我该如何修复代码?如果是后者,是否有任何解决方法,我应该报告错误吗?

$ icc --version
icc (ICC) 19.1.0.166 20191121
Copyright (C) 1985-2019 Intel Corporation.  All rights reserved.
Run Code Online (Sandbox Code Playgroud)