【问题标题】:How to overwrite the return address of the assembler stack with an address given by C++?如何用 C++ 给出的地址覆盖汇编程序堆栈的返回地址?
【发布时间】:2019-05-18 17:57:44
【问题描述】:

我有一个函数switchContext(void*& from, void*& to)。它接收两个堆栈指针并应更改进程的上下文。因此,如果我有一个协程 A 并且它使用特定函数 resume(),协程 B 将继续工作。 目前我无法让我的代码正常工作。

我正在使用 Nasm 和 GCC 进行编译。

汇编程序:(switchContext(void*& from, void*& to)):

switchContext:

    ; epb = esp
    mov     ebp, esp

    ; save registers
    push    ebp
    push    ebx
    push    esi
    push    edi

    ; from <= returnadress
    ; eax <= returnadress
    mov     eax, [ebp+16] 
    mov     edx, ebp
    add     edx, 20 ; make edx point to 'from'
    ; overwrite 'from' with returnadress
    mov     [edx], eax

    ; what to do now: returnadress <= to
    ; eax <= to
    mov     eax, [ebp+24]
    mov     edx, ebp
    add     edx, 16 ; make edx point to returnadress
    ; overwrite returnadress with 'to'
    mov     [edx], eax

    pop     edi ; RSA = esp + 12
    pop     esi ; RSA = esp + 8
    pop     ebx ; RSA = esp + 4
    pop     ebp ; RSA = esp + 0

    ; use new returnadress to jump to 'to'
    ret     

这是对应的 C++ 类:

extern "C" {
    void switchContext(void*& from, void*& to);
}

class Coroutine {
public:

    const char* name;
    Coroutine(void* tos = 0)
    {
        setup(tos);
    }

    void resume(Coroutine* next)
    {

        switchContext(this->sp, next->sp);
    }

    virtual void body() = 0;
    virtual void exit() = 0;

private:
    static void startup(Coroutine* obj) {
        obj->body();
        obj->exit();
    };

    void setup(void* tos) {
        if (tos == 0) {
            unsigned temp_stack[1024];
            this->sp = &temp_stack;
            return;
        }

        this->sp = &tos;
        return;
    };

    void* sp;
};

目前我的程序只是崩溃了。但它只能通过用 'to' 覆盖汇编器中的返回地址来实现。

我在这个过程中哪里出错了?

【问题讨论】:

  • 为了完成这个,你能否提供一个小测试程序,使用这个类来设置几个协程。对于发现此问题的任何人来说,这至少可以使其成为minimal reproducible example
  • 我会为你不使用内联汇编并决定更直接的事情而鼓掌。

标签: c++ assembly x86 coroutine context-switch


【解决方案1】:

您的mov ebp,esp 放错地方了。应该是在寄存器保存之后。

您没有取消引用引用。从汇编程序的角度来看,C++ 引用只是一个指针,因此您的参数是void **。由于您希望将返回地址保存/加载到所指向的地址,因此您需要一个额外的间接来将值保存到所指向的地址。

与此问题无关:您使用edx 进行的一些地址计算可以压缩为更少的指令。您也可以不使用 ebp 并使用基于 esp 的偏移量。

【讨论】:

  • 你是完全正确的。这些是崩溃的错误。
  • 但是这个过程仍然没有改变上下文。它只是在第一次调用resume() 后停止。
  • @IPodFan 您的setup 错误。 &amp;temp_stack&amp;tosswitchContext 所需的堆栈值不对应。另一个汇编函数(获取这个初始值)会有所帮助。
  • 我可能遗漏了一些东西。您确定不必保存 EDI 或 ESI 吗?尽管函数本身不使用这些寄存器,但我认为所有非易失性(被调用者保留)寄存器(EBP、ESP、EBX、EDI 和 ESI)都必须在协程切换中保留。我的印象是,那些被推送的特定寄存器的预期用途是确保调用者上下文总是可以正确恢复。易失性寄存器(EAX、ECX、EDX)无关紧要,因为它们不会在switchContext 返回时保留其值。
  • 我在 OP 的代码 unsigned temp_stack[1024]; this-&gt;sp = &amp;temp_stack; 中也对此表示担忧。这是在setup 内部完成的,因此该数组被创建为堆栈上的临时数组,当setup 返回时将超出范围。这几乎会在以后被破坏。所以保存指向它的指针不会削减它。要么将数组放入类中,要么动态分配数组(在免费存储中)——后者是我的偏好。
猜你喜欢
  • 2015-01-28
  • 1970-01-01
  • 2021-12-02
  • 2014-04-21
  • 2020-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多