不使用其他变量在C++中编写汇编代码

6
我正在编写一个简单的C++程序,其中包含一些汇编指令。
void call(){
    __asm__("mov    -0x10(%rbp),%eax;"
            "add    $0x10,%eax;"
            "mov    %eax,%edx;"
            "shr    $0x1f,%edx;"
            "add    %edx,%eax;"
            "sar    %eax;"
            "add    %eax,-0x4(%rbp);"
            "mov    $0x4c,%eax;"
            "mov    -0x8(%rbp),%eax;"
            "add    %eax,%eax;"
            "sub    -0x4(%rbp),%eax;"
            "add    %eax,-0xc(%rbp);");
}

然而,根据程序执行行为我发现,这段 asm 代码所操作的寄存器实际上也被函数中其他变量使用了。

有没有一种方法可以调用编译器来隔离 asm 标签中使用的寄存器,确保它们不会受到影响?

操作系统:Linux 编译器:G++

非编译器方法也可接受。


4
这很糟糕,非常糟糕。这段代码假定栈上的变量在特定位置。您应该使用_GCC_扩展汇编模板,并将变量作为约束传递给模板,以不依赖硬编码位置。此外,由于您没有使用任何约束条件,因此会破坏_EAX_和_EDX_,这些寄存器可能用于其他目的。_GCC_不对您的内联汇编做出任何假设(不像_MASM_)。 - Michael Petch
我想问一个问题。你所拥有的汇编器假定栈上有本地变量(即从_RBP_负位移),尽管在代码函数callOne中没有本地变量。这是该函数的完整代码片段还是还有更多内容?这是哪个操作系统(Windows/Linux/BSD)?为什么不能为此创建C++代码?你正在开发漏洞吗? - Michael Petch
@MichaelPetch 我更新了问题。Linux,这是我正在使用的完整函数没有更多的代码。 - Syntax_Error
1
如果这是完整的代码,我可以问一下你使用的汇编器代码是从哪里来的吗?看起来它可能是从其他代码中提取出来(反汇编?)然后放进去的。我这么说是因为无论这段代码来自哪里,它最初都是一个包含多个局部变量的函数的一部分(从代码上看似乎有4个局部变量,或者如果涉及到数组可能会更少)。 - Michael Petch
1
“我不关心结果,而是代码是否能运行。”= 但这是一样的。访问无效内存会导致崩溃,因此使其“不运行”,但如果地址意外有效,则将以垃圾结果运行。我不明白你觉得这有什么不同。在任何情况下,你发布的代码都需要进行大量重写,以便在64位C++项目中有些意义。基本上,你可以将其删除(因为它对于代码的C++部分没有做出任何重要贡献),并且代码将比现在更好地运行,而不会有一些流氓asm指令修改某些随机内存。 - Ped7g
显示剩余4条评论
3个回答

2
作为非编译器方法: 你可以使用 pusha/popa 指令,在执行你自己的汇编之前将所有寄存器推入栈中,并在执行之后将它们全部弹出。
代码示例:
void callOne(){
    __asm__(
        "pusha;"
        "mov    -0x10(%rbp),%eax;"
        "add    $0x10,%eax;"
        "mov    %eax,%edx;"
        "shr    $0x1f,%edx;"
        "add    %edx,%eax;"
        "sar    %eax;"
        "add    %eax,-0x4(%rbp);"
        "mov    $0x4c,%eax;"
        "mov    -0x8(%rbp),%eax;"
        "add    %eax,%eax;"
        "sub    -0x4(%rbp),%eax;"
        "add    %eax,-0xc(%rbp);"
        "popa;");
}

@Syntax_Error 哎呀,可能有一些类似的指令。 - alexeykuzmin0
@Syntax_Error 在64位模式下,“pusha”是“未定义”的,要做同样的事情,只需push/pop所有你修改的单个寄存器,实际上只有两个[ push rax push rdx ... 你的代码 ... pop rdx pop rax ]。或者学习如何标记该汇编块为“clobbering”raxrdx(我自己不知道,因为我不使用内联汇编,所以没有我的示例)。 (当然,这需要你确实想要修改[rbp-16][rbp-8][rbp-4],这些是一些本地函数变量) - Ped7g
1
思考一下,你必须告诉C++编译器关于这些本地变量的使用/修改,所以你可能是“做错了”,内联汇编非常棘手。要么将其作为独立函数执行,以便遵循调用ABI,要么您将不得不学习内联汇编的所有细节和扩展定义,以解释编译器,字符串内部发生了什么(编译器不知道汇编字符串在做什么),即它需要哪些可用内容以及它会修改什么。最终只需在C++中重写即可,这很容易且代码更好。 - Ped7g
2
@Ped7g:如果遵循Linux 64位System V ABI(可能不是这种情况,因为当然OP没有提到操作系统),我会小心地在模板中推送和弹出,因为您还必须考虑红区可能正在运行。 - Michael Petch
1
@Syntax_Error 哦,正如Michael所警告的那样,这更加棘手,即使我的“修复”是错误的,只能偶然起作用。因此,在64b中,要么学习如何进行所有内联汇编标记的操作来解释编译器该块的功能,要么创建独立的汇编函数,修改以遵循您的ABI,并将其作为独立的调用。 - Ped7g

1

内联汇编在不同编译器中具有较大的差异,大部分工作都是编译器细节。是的,你可以告诉编译器避免与其他编译代码使用的寄存器冲突,或者你可以与高级语言变量交互并将其引入内联汇编中。但这非常依赖于编译器。如果你想要一个完整的汇编函数,那么就制作一个汇编函数并避免编译器问题。一个简单的起点是在C/C++中原型化函数,将其编译为汇编代码或反汇编,然后将其用作汇编函数的框架。


1
asm()按标准定义实际上只是一种使各个编译器将其实现方案“插入”到语言中的方法。从标准的角度来看,asm()内的任何内容都是“龙穴”,没有进一步的规定。
一个内联汇编的实现方案GCC的扩展ASM语句(不那么巧合地遵循asm()形式),允许(事实上,需要...)您指定哪些变量进入、哪些变量退出以及哪些变量被破坏,使得编译器——否则不知道您的ASM源代码做了什么——对缓存值、优化等“正确处理”。

OSDev Wiki提供了一个相对易懂的介绍GCC内联汇编语法的入门指南,其中包括一些(与操作系统内核相关的)示例


微软有其自己的内联汇编(并且,相当典型的是,不支持asm(),而是坚持使用__asm(),并且还要求您手动保存/恢复任何寄存器。

MASM 相对而言更加宽容,因为在 32 位代码中(64 位不支持内联汇编),您不需要在 asm 块中保留 EAX、EBX、ECX、EDX、ESI 或 EDI 寄存器。您可以尽情使用它们。这也是为什么 MASM 在内联汇编优化方面做得更差的原因之一。当然,应该谨慎地使用内联汇编(或者根本不使用),如果可以使用编译器内置函数,则应首选使用它们(或者只需将函数编写为外部汇编器对象)。 - Michael Petch
1
当我说MASM时,我的意思是MSVC / MSVC ++。 - Michael Petch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接