【问题标题】:Writing assembly in C++ without using other variables在不使用其他变量的情况下用 C++ 编写程序集
【发布时间】:2017-03-14 08:11:10
【问题描述】:

我正在编写一个包含一些 asm 指令的简单 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++

也欢迎使用非编译器方法

【问题讨论】:

  • 这很糟糕,非常糟糕。此代码假定堆栈上的变量位于特定位置。您应该使用GCC extended assembly template 并将变量作为约束传递给模板,以免依赖硬编码的位置。同样,由于您没有使用任何约束,因此您正在破坏可用于其他目的的 EAXEDXGCC 不对您的内联汇编器做任何假设(与 MASM 不同)。
  • 我会问一个问题。尽管在代码函数callOne 中没有局部变量,但您拥有的汇编程序假定堆栈上有局部变量(即来自RBP 的负位移)。这是该函数的完整代码 sn-p 还是还有更多?这是什么操作系统(Windows/Linux/BSD?)。为什么不能为此创建 C++ 代码?你在做漏洞利用吗?
  • @MichaelPetch 我更新了这个问题。 Linux,这是我不再使用代码的完整功能。
  • 如果这是完整的代码,请问您使用的汇编代码来自哪里?似乎它可能是从其他代码中提取的(反汇编?)并插入。我这样说是因为无论该代码来自何处,它最初都是具有许多局部变量的函数的一部分(似乎其中有 4 个来自目测如果涉及数组,则代码或可能更少)
  • " 我不关心结果,而关心代码是否运行" = 但这是一回事。访问无效内存会使它崩溃,所以让它“不运行”,但如果地址意外有效,它会以垃圾结果运行。我看不出这对你有什么不同。无论哪种情况,您发布的这段代码都需要先进行大量重写才能在 64b C++ 项目中有意义。你基本上可以直接删除它(因为它对代码的 C++ 部分没有做任何重要的事情),并且代码将比现在运行得更好,而无需一些流氓 asm 指令修改一些随机内存。

标签: c++ assembly x86 g++ x86-64


【解决方案1】:

作为一种非编译器方法:您可以使用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 哎呀。也许有一些类似的说明。
  • @Syntax_Error pusha 在 64b 模式下为“未定义”,只需 push/pop 您修改的所有单个寄存器执行相同操作,实际上只是其中两个 [push raxpush rdx。 ..你的代码...pop rdxpop rax]。或learn 如何将该程序集块标记为“破坏”raxrdx(我自己不知道,因为我不使用内联汇编,所以没有我的例子)。 (这当然希望你确实想要修改[rbp-16][rbp-8][rbp-4],它们是一些局部函数变量)
  • 想一想,您必须将这些局部变量的使用/修改也告诉 C++ 编译器,所以您可能“做错了”,内联汇编很难正确处理。要么将其作为独立函数执行,因此它将遵循调用 ABI,或者您将必须学习内联汇编的所有细节和扩展定义以解释编译器,该字符串内部发生了什么(编译器不知道该字符串是什么组装正在做),即。它需要什么可用以及它确实修改了什么。最终只需用 C++ 重写它,这很容易 + 更好的代码。
  • @Ped7g :如果遵循 Linux 64 位 System V ABI(可能不是这种情况,因为 OP 当然没有提到什么操作系统)我会小心推动和弹出模板,因为您可能还必须考虑到红色区域可能正在发挥作用。
  • @Syntax_Error 哦,正如迈克尔警告的那样,这更加棘手,甚至我的“修复”是错误的,并且只会在偶然情况下起作用。因此,在 64b 中,要么学习如何做所有内联 asm 标记的东西来解释编译器该块的作用,要么创建独立的 asm 函数,修改为遵循您的 ABI,并将其称为独立的。
【解决方案2】:

内联汇编在很大程度上是特定于编译器的,大部分工作是编译器的细微差别,是的,您可以告诉编译器避免与 copmiled 代码使用的其他寄存器冲突和/或您可以与高级语言变量交互并提取那些进入内联汇编。但它非常特定于编译器。如果您想要汇编中的整个函数,那么只需制作一个汇编函数并避免编译器问题。一个简单的起点是在 C/C++ 中对函数进行原型设计,将其编译为 asm 或反汇编,然后将其用作 asm 函数的骨架。

【讨论】:

    【解决方案3】:

    asm() as defined in the standard 实际上只是一种使各个编译器能够将其实现的解决方案“插入”到语言中的方法。从标准的角度来看,asm() 中的任何内容都是“这里是龙”,没有进一步说明。

    内联汇编的一个实现定义的解决方案是GCC's Extended ASM 语句(不太巧合地遵循asm() 形式),它允许(实际上,需要...)您指定哪些变量输入,哪些变量输出,哪些变量被破坏,从而允许编译器——否则它不知道你的 ASM 源代码做了什么——做“正确的事情”缓存值、优化等。

    OSDev Wiki 对 GCC 的内联汇编语法有一些更容易理解的介绍,包括几个(与操作系统内核相关的)examples


    Microsoft 有自己的 Inline Assembly 风格(而且,很典型,asm() 配合得很好,但坚持使用 __asm(),并且确实还坚持让您保存/恢复任何手动注册。

    【讨论】:

    • MASM 更加宽容(相对而言),因为在 32 位代码中(内联汇编在 64 位中不可用)您不需要保留 EAX、EBX 、ECX、EDX、ESI 或 EDI 寄存器跨 asm 块。您可以尽情使用它们。这就是 MASM 对内联汇编器的优化做得更差的原因之一。当然,内联汇编器应该谨慎使用(或根本不使用),如果可以使用编译器内在函数将是首选(或者只是在外部汇编器对象中编写函数)
    • 当我说 MASM 时,我指的是 MSVC/MSVC++
    猜你喜欢
    • 2021-01-08
    • 2011-02-08
    • 2010-11-04
    • 2010-10-08
    • 1970-01-01
    • 2015-08-24
    • 1970-01-01
    • 2019-01-29
    • 2019-05-14
    相关资源
    最近更新 更多