需要帮助验证汇编代码

3
我有一个装配体,编译没有问题,但在还原时出现分段故障。能否有人进行验证。这是针对x86_64架构的。
save_context:

mov     %rdi,%rax           /* Get our context pointer */
                            /* Don't need to save A */
mov     %rbx, 16(%rax)      /* Save B */
mov     %r12, 24(%rax)      /* Save r12 */
mov     %r13, 32(%rax)      /* Save r13 (8*3+16)*/
mov     %r14, 40(%rax)      /* Save r13 */
mov     %r15, 48(%rax)      /* Save r13 */

mov     %rbp, 56(%rax)      /* Save frame pointer */
mov     %rsp, 64(%rax)      /* Save stack pointer */

mov     (%rsp), %rdx        /* Fetch our return address */
mov     %rdx, (%rax)        /* Save our return address */

xor     %rax,%rax           /* Construct return code of 1 */
inc     %rax

ret

恢复的过程大概是这样的

restore_context:

mov     %rdi,%rax       /* Get our context pointer */       

mov     64(%rax), %rsp  /* Restore stack pointer */ 

mov     (%rax), %rdx    /* Fetch our return address */  
mov     %rdx, (%rsp)    

mov     16(%rax),%rbx   /* Restore B */
mov     24(%rax), %r12  /* Restore r12 */   
mov     32(%rax), %r13  /* Restore r13 */   
mov     40(%rax), %r14  /* Restore r14 */
mov     48(%rax), %r15  /* Restore r15 */
mov     56(%rax), %rbp  /* Restore frame pointer */

xor     %rax,%rax       /* Return 0 */
ret

当我使用gdb调试函数时,出现了如下提示信息“在 segmentation fault 后面”。
   0x0000000000424c4c <+0>:     mov    %rdi,%rax
   0x0000000000424c4f <+3>:     mov    0x18(%rax),%rsp
   0x0000000000424c53 <+7>:     mov    (%rax),%rbx
=> 0x0000000000424c56 <+10>:    mov    %rbx,(%rsp)
   0x0000000000424c5a <+14>:    mov    0x10(%rax),%rbx
   0x0000000000424c5e <+18>:    mov    0x20(%rax),%rbp
   0x0000000000424c62 <+22>:    mov    0x28(%rax),%r12
   0x0000000000424c66 <+26>:    mov    0x30(%rax),%r13
   0x0000000000424c6a <+30>:    mov    0x38(%rax),%r14
   0x0000000000424c6e <+34>:    mov    0x40(%rax),%r15
   0x0000000000424c72 <+38>:    xor    %rax,%rax
   0x0000000000424c75 <+41>:    retq   
   0x0000000000424c76 <+42>:    nop
   0x0000000000424c77 <+43>:    nop

反汇编 save_context

  0x0000000000424c1c <+0>:     mov    %rdi,%rax
  0x0000000000424c1f <+3>:     mov    %rbx,0x10(%rax)
  0x0000000000424c23 <+7>:     mov    %rsp,0x18(%rax)
  0x0000000000424c27 <+11>:    mov    %rbp,0x20(%rax)
  0x0000000000424c2b <+15>:    mov    %r12,0x28(%rax)
  0x0000000000424c2f <+19>:    mov    %r13,0x30(%rax)
  0x0000000000424c33 <+23>:    mov    %r14,0x38(%rax)
  0x0000000000424c37 <+27>:    mov    %r15,0x40(%rax)
  0x0000000000424c3b <+31>:    mov    (%rsp),%rdx
  0x0000000000424c3f <+35>:    mov    %rdx,(%rax)
  0x0000000000424c42 <+38>:    xor    %rax,%rax
  0x0000000000424c45 <+41>:    inc    %rax
  0x0000000000424c48 <+44>:    retq   
  0x0000000000424c49 <+45>:    nopl   (%rax)

有关上下文的更多信息

保存上下文(context) context = {4243415,0,0,4242944,140737488348624,0,0,140737488348368,140737488348312,0}

恢复上下文(new_context) new_context= {4249788,0,0,0,0,0,6719200,6719184,0,0}

恢复上下文后失败。我尝试了save_context然后restore_context。那样可以。只是想确认一下64位的上下文和新上下文是否有什么问题?!?!

以下是32位版本

save_context:

movl    4(%esp),%eax        /* Get our context pointer */
                            /* Don't need to save A */
movl    %ebx, 12(%eax)      /* Save B */
movl    %esi, 16(%eax)      /* Save SI */
movl    %edi, 20(%eax)      /* Save DI */
movl    %ebp, 24(%eax)      /* Save frame pointer */
movl    %esp, 28(%eax)      /* Save stack pointer */

movl    0(%esp), %edx       /* Fetch our return address */
movl    %edx,  0(%eax)      /* Save our return address */

xorl    %eax,%eax           /* Construct return code of 1 */
incl    %eax
    ret

还原上下文:

restore_context:
movl    4(%esp),%eax        /* Get our context pointer */

movl    28(%eax), %esp      /* Restore stack pointer */
movl    0(%eax),%edx        /* Get our return address */
movl    %edx, 0(%esp)       /* Put it on the stack in the right
                            spot. */

movl    12(%eax),%ebx       /* Restore B */

movl    16(%eax), %esi      /* Restore SI */
movl    20(%eax), %edi      /* Restore DI */
movl    24(%eax), %ebp      /* Restore frame pointer */

xorl    %eax,%eax       /* Return 0 */
    ret

有什么办法可以解决这个问题吗?

1
返回地址不在8(%rbp),你还没有设置rbp指向任何地方(除非它是由调用者隐式设置的)。 - Jester
1
返回地址在(%rsp)。如果您使用传统的函数序言push %rbp; mov %rsp, %rbp,那么相同的位置也可以被称为8(%rbp) - Jester
2
是的,看起来应该可以工作。 - Jester
1
反汇编不匹配源代码,你从0x18(%rdx)加载了rsp。请确保它是正确的。 - Jester
1
new_context 当然应该来自之前的 save_context,你确定你没有破坏它吗?你能否发布完整的代码? - Jester
显示剩余16条评论
1个回答

4

首先,我将保存和恢复放在一起(并打乱恢复指令),以便我可以看到偏移是否有误。从这个角度来看,它看起来很好。

save_context:                restore_context:

mov     %rdi,%rax            mov     %rdi,%rax

mov     %rbx, 16(%rax)       mov     16(%rax),%rbx
mov     %r12, 24(%rax)       mov     24(%rax), %r12
mov     %r13, 32(%rax)       mov     32(%rax), %r13
mov     %r14, 64(%rax)       mov     64(%rax), %r14
mov     %r15, 48(%rax)       mov     48(%rax), %r15

mov     %rbp, 56(%rax)       mov     56(%rax), %rbp
mov     %rsp, 64(%rax)       mov     64(%rax), %rsp

mov     %rdx, (%rsp)         mov     (%rsp), %rdx
mov     %rdx, (%rax)         mov     (%rax), %rdx

xor     %rax,%rax            xor     %rax,%rax
inc     %rax

ret                          ret

到目前为止一切都很好。

然而,现在我想看一下所说的偏移量:

16
24
32
64   <-- why 64 here?
48
56
64   <-- Oops 64 again?

我认为这就是罪魁祸首。您在同一个地址保存了两个寄存器。

奇怪的是,在gdb输出中它看起来是正确的。所以我猜你没有展示给我们你的原始代码。

为避免这样的问题,通常最好使用define指令定义不同的偏移量来定义您的结构。

否则,您能否更详细地描述一下您的上下文。这似乎不像您可以在操作系统中运行的代码,但您提到了gdb,因此听起来您可能正在Linux中运行。我能想到两种可能性:在恢复发生之前,您的上下文被覆盖了,保存后的堆栈发生了很多变化(我想象),当您恢复时,您会得到完全不同的堆栈,但是您将%esp恢复到现在对于堆栈来说是“随机数据”。所以ret应该工作(您恢复了那么多),但在那之后...谁知道!


从评论中的问题更新:

1)是的,我在Linux上运行,因为32位版本运行良好,我认为这个也会。我错误地假设了吗?

啊。堆栈可能被管理得不同,但是如果您自己调用函数,则这在此处不应该有影响。此外,您修改了rdx,这可能是不允许的?

2)我明白您所说的代码中某个位置上下文被覆盖的意思。所以我尝试将保存和恢复放在彼此之后。仍然会导致分段错误,这意味着我的汇编代码有问题。

实际上,您的代码应该类似于以下内容:

  MOV ..., %rdi
  CALL save
  TEST %rax
  JNE done
  [...do things, but no RET...]
  MOV ..., %rdi
  CALL restore
  ---not reached---
done:
  RET

当你从保存函数返回时,你保存的返回地址就在CALL save之后,这就是为什么你要将%rax设置为0或1,以便知道你是从保存还是从恢复返回。到目前为止,我没有看到任何问题。
唯一能想到的问题可能是%rdi在保存和恢复调用之间发生了变化,但如果你修改了代码只执行这两个调用,我想这不是问题所在。它是否在你的问题中标有=>的位置引发了SEGV错误?或者它会在之前的行上引发SEGV错误?
如何调试此类错误? 我建议使用stepi进行跟踪,并验证每条指令上确切发生你预期的事情。查看保存的堆栈指针是否与恢复中得到的相同,并验证返回地址是否正确。
GDB可以在每次运行命令时“打印”一组条目。因此,如果你这样做,然后输入任意数量的stepi,就应该在每个步骤中看到你的信息,并在出现问题时看到其异常。(例如,在%rdi处打印寄存器和数据?)

感谢您的输入。是的,我已经更改了源代码,忘记在这里进行更改。正如您指出的那样,GDB输出是最新的。有几个问题!!1)是的,我正在运行Linux,因为32位版本运行良好,我猜这个也会。我假设错了吗?2)我明白您所说的代码中上下文被覆盖的意思。所以我尝试在彼此之后立即放置保存和恢复。仍然会给我带来分段错误,这意味着我的汇编代码有问题。3)如何调试此类错误? - Curious Guy 007
(即打印%rdi处的寄存器和数据?)我尝试了一下,确实rdi的值发生了变化。但是在i386(32位)中也会发生这种情况:我改变了输入参数...所以在32位情况下,4(esp)会因为保存和恢复而发生变化。我的问题中有两个输入(上下文和新上下文)。那么为什么32位可以工作呢?我想切换到新的上下文,这可能吗? mov 4(esp) eax**/* * 切换到新的上下文。这不会返回。 */ cpu_restore_context (new_context); ** - Curious Guy 007
你没有展示给我们你的32位版本,但是如果rdi没有指向完全相同的位置,那么它如何以相同的方式恢复事物,因为你对“save”和“restore”使用了完全相同的偏移量?“mov %rsp, 64(%rax)”必须一一对应于“mov 64(%rax), %rsp”,对吗? - Alexis Wilke
1
实际上,如果4(%esp)从未更改,则在保存和恢复中,您在%eax中具有相同的指针。所以这就是问题所在。在64位版本中,%rdi需要保持不变。也许您可以在调用save之前将其推送到堆栈上,然后从8(%rsp)检索它,而不是使用%rdi - Alexis Wilke
1
如果在调用恢复函数时栈指针不同,那么它也不会起作用。如果在调用恢复函数时栈更负,则在堆栈上保存%rdi将毫无用处。所以我不太确定你如何做到这一点。特别是没有看到代码之间的更多内容。而且可能需要很多代码! - Alexis Wilke
显示剩余6条评论

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