【问题标题】:setjmp and longjmp implementationsetjmp 和 longjmp 实现
【发布时间】:2017-09-07 10:48:54
【问题描述】:

基本上我的问题是我对 setjmp 和 longjmp 的实现 不起作用。我之所以以这种形式而不是在(代码审查)中提问的原因是我是汇编新手,我没有什么背景并且仍在学习但仍然不确定代码(请阅读到最后)。

首先我用三个不同的编译器在两个平台上执行了代码 这就是为什么我确定我在使用汇编程序时做错了什么的原因。

平台:mac OS 10.12.5 x86_64,ubuntu linux x86 编译器:Apple LLVM clang 8.0.0 x86_x64 ,clang 3.9.1 x86_x64 ,gcc 6.3 x86

我已经在所有平台上以 32 位模式编译了代码,因此在 linux 和 mac 上生成的机器代码是 32 位的。

我将在这里发布的代码是在 Apple clang 下编译的,没有使用 -m32 标志进行优化以生成 32 位机器码

 #include <cstdio>


typedef unsigned long jmp_buf[6];


int Setjmp(jmp_buf var){
     __asm__(
             "    mov -4(%ebp), %eax     # get pointer to jmp_buf, passed as argument on stack\n"
             "    mov    %ebx, (%eax)   # jmp_buf[0] = ebx\n"
             "    mov    %esi, 4(%eax)  # jmp_buf[1] = esi\n"
             "    mov    %edi, 8(%eax)  # jmp_buf[2] = edi\n"
             "    mov    %ebp, 12(%eax) # jmp_buf[3] = ebp\n"
             "    lea   4(%esp), %ecx     # get previous value of esp, before call\n"
             "    mov    %ecx, 16(%eax) # jmp_buf[4] = esp before call\n"
             "    mov  (%esp), %ecx     # get saved caller eip from top of stack\n"
             "    mov    %ecx, 20(%eax) #jmp_buf[5] = saved eip\n"
             "    xor    %eax, %eax     #eax = 0\n"
     );

    return 0;
}

void Longjmp(jmp_buf var,int m){
    __asm__("    mov  -4(%ebp),%edx # get pointer to jmp_buf, passed as argument 1 on stack\n"
            "    mov  -8(%ebp),%eax #get int val in eax, passed as argument 2 on stack\n"
            "    test    %eax,%eax # is int val == 0?\n"
            "    jnz 1f\n"
            "    inc     %eax      # if so, eax++\n"
            "1:\n"
            "    mov   (%edx),%ebx # ebx = jmp_buf[0]\n"
            "    mov  4(%edx),%esi # esi = jmp_buf[1]\n"
            "    mov  8(%edx),%edi #edi = jmp_buf[2]\n"
            "    mov 12(%edx),%ebp # ebp = jmp_buf[3]\n"
            "    mov 16(%edx),%ecx # ecx = jmp_buf[4]\n"
            "    mov     %ecx,%esp # esp = ecx\n"
            "    mov 20(%edx),%ecx # ecx = jmp_buf[5]\n"
            "    jmp *%ecx         # eip = ecx");
}



void fancy_func(jmp_buf env);

int main() {
    jmp_buf env;
    int ret = Setjmp(env);
    if (ret == 0) {
        puts("just returning from setjmp!");
        fancy_func(env);
    } else {
        puts("now returning from longjmp and exiting!");
    }

}

void fancy_func(jmp_buf env) {
    puts("doing fancy stuff");
    Longjmp(env, 1);
}

我正在关注本教程:http://vmresu.me/blog/2016/02/09/lets-understand-setjmp-slash-longjmp/

注意:我已经调试过源代码问题出在:

 jmp *%ecx

但我认为问题在于 setjmp 以及我存储上下文的方式 特别是那一行:

 lea   4(%esp), %ecx     # get previous value of esp, before call\n"

这也是我没有得到它的代码部分。

我还知道我的编译器生成的用于调用和清理 setjmp 和 longjmp 堆栈的代码以及在我的案例中使用的调用约定 (CDECL)。

非常感谢您的帮助。

【问题讨论】:

  • C++ 不是 C 不是 C++!这可能是你的问题。你为什么要使用它们?只是不要。在 C++ 中,使用异常、setjmp 等是一些比较有问题的 C 遗留问题(使用 C++ 时),在 C 中应该谨慎使用它们。
  • 哦,还有:阅读 How to Ask 并听从建议。
  • 你为什么要用内联汇编编程?堆栈布局在内联汇编中基本上是不可预测的,你不能只假设编译器推送基指针或类似的东西。
  • 我希望它们用于协程,这就是我标记 C 和 C++ 的原因
  • @jacky 那你为什么不在 libc 中使用setjmp 实现呢?

标签: c++ assembly x86 clang++


【解决方案1】:

这有很多问题。正如fuz所说,你不应该像这样使用内联汇编。使用单独的 asm 文件,或者至少使用约束,最好不要依赖特定的堆栈布局。

不管怎样,你的偏移量是错误的,参数是从ebp 偏移的,而不是负数,首先是8(%ebp)。你也弄错了返回地址,它在4(%esp),因为(%esp)是保存的ebp。此外,由于函数序言保存了ebp,因此您不会保存调用者的ebp,而是保存esp 的副本。

固定版本(仍然只能在 32 位模式下使用堆栈参数调用约定):

查看生成的 asm 以了解整个函数 on the Godbolt compiler explorer

// optimize("no-omit-frame-pointer") doesn't seem to work
// we still don't get a frame-point unless we force -O0 for the function with optimize(0)
__attribute__((noinline, noclone, returns_twice, optimize(0)))
int Setjmp(jmp_buf var){
    // relies on the compiler to make a stack-frame
    // because we're using inline asm inside a function instead of at global scope
     __asm__(
             "    mov 8(%ebp), %eax     # get pointer to jmp_buf, passed as argument on stack\n"
             "    mov    %ebx, (%eax)   # jmp_buf[0] = ebx\n"
             "    mov    %esi, 4(%eax)  # jmp_buf[1] = esi\n"
             "    mov    %edi, 8(%eax)  # jmp_buf[2] = edi\n"
             "    mov    (%ebp), %ecx\n"
             "    mov    %ecx, 12(%eax) # jmp_buf[3] = ebp\n"
             "    lea    8(%ebp), %ecx  # get previous value of esp, before call\n"
             "    mov    %ecx, 16(%eax) # jmp_buf[4] = esp before call\n"
             "    mov    4(%ebp), %ecx  # get saved caller eip from top of stack\n"
             "    mov    %ecx, 20(%eax) #jmp_buf[5] = saved eip\n"
             "    xor    %eax, %eax     #eax = 0\n"
     );

    return 0;
}

__attribute__((noinline, noclone, optimize(0)))
void Longjmp(jmp_buf var,int m){
    __asm__("    mov  8(%ebp),%edx # get pointer to jmp_buf, passed as argument 1 on stack\n"
            "    mov  12(%ebp),%eax #get int val in eax, passed as argument 2 on stack\n"
            "    test    %eax,%eax # is int val == 0?\n"
            "    jnz 1f\n"
            "    inc     %eax      # if so, eax++\n"
            "1:\n"
            "    mov   (%edx),%ebx # ebx = jmp_buf[0]\n"
            "    mov  4(%edx),%esi # esi = jmp_buf[1]\n"
            "    mov  8(%edx),%edi #edi = jmp_buf[2]\n"
            "    mov 12(%edx),%ebp # ebp = jmp_buf[3]\n"
            "    mov 16(%edx),%ecx # ecx = jmp_buf[4]\n"
            "    mov     %ecx,%esp # esp = ecx\n"
            "    mov 20(%edx),%ecx # ecx = jmp_buf[5]\n"
            "    jmp *%ecx         # eip = ecx");
}

如果您在全局范围内使用asm 语句,则无需使用__attribute__ 内容与编译器抗争,以确保它发出您期望的序言。您也可以跳过设置 EBP,这样您就可以直接获得调用者的 EBP。

asm(".globl SetJmp \n"
    "SetJmp:       \n\t"
    "   push   %ebp  \n\t"
    "   mov    %esp, %ebp  \n\t"

    "...  your current implementation    \n\t"

    "   xor    %eax,%eax   \n\t"
    "   pop    %ebp        \n\t"
    "   ret                \n\t"
 );

【讨论】:

  • 请注意,根据 OP 编译器配置,-fomit-frame-pointer 可能是也可能不是默认选项(但无法判断)。 __attribute__((naked)) 可能会有所帮助。实际上,我很惊讶编译器不会因为没有声明任何副作用而直接丢弃程序集,但唉,这可能只是缺乏优化。
  • @Jester 感谢代码工作正常的解决方案。看来我需要获取有关 clang 在不同情况下使用的堆栈布局的更多信息。关于内联汇编的事情,正如我所说,我不使用内联汇编,这是我第一次使用它,我认为这是实现功能的正确方法,因为正如我所说,我在汇编方面没有经验,再次感谢您的帮助
  • @jacky 除了最简单的情况外,堆栈布局几乎是不可预测的。如果可能,尽量避免内联汇编。它充满了陷阱,正确使用非常棘手,而且通常有更好的方法来实现您想要做的事情。
  • @fuz 如果没有输出操作数,则 GCC 假定 asm 语句是易变的并且具有未声明的副作用。
  • @fuz:同意,如果在 -O0 以外的任何地方编译,这会很糟糕。修复了__attribute__((noinline, optimize(0))),并添加了将asm 语句置于全局范围内的示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-19
相关资源
最近更新 更多