Assembly retq过程中的装配分段故障

Assembly retq过程中的装配分段故障,assembly,segmentation-fault,stack,x86-64,Assembly,Segmentation Fault,Stack,X86 64,我有一些汇编代码,可以使用callq调用另一个。调用retq时,程序因分段错误而崩溃 .globl main main: # def main(): pushq %rbp # movq %rsp, %rbp # callq input # get input movq %rax, %r8 callq r8_digits_to_stack # pro

我有一些汇编代码,可以使用
callq
调用另一个。调用
retq
时,程序因分段错误而崩溃

    .globl  main
main:                   # def main():
    pushq   %rbp        #
    movq    %rsp, %rbp  #

    callq   input       # get input
    movq    %rax, %r8

    callq   r8_digits_to_stack
    # program is not getting here before the segmentation fault
    jmp     exit_0

# put the binary digits of r8 on the stack, last digit first (lowest)
# uses: rcx, rbx
r8_digits_to_stack:
    movq    %r8, %rax       # copy for popping digits off

    loop_digits_to_stack:
        cmpq    $0, %rax    # if our copy is zero, we're done!
        jle     return

        movq    %rax, %rcx  # make another copy to extract digit with
        andq    $1, %rcx    # get last digit
        pushq   %rcx        # push last digit to stack
        sarq    %rax        # knock off last digit for next loop
        jmp     loop_digits_to_stack

# return from wherever we were last called
return:
    retq

# exit with code 0
exit_0:
    movq    $0, %rax    # return 0
    popq    %rbp
    retq
其中,
input
是一个C函数,它将键盘输入返回到
%rax


我假设这可能与我正在操纵堆栈这一事实有关,是这样吗

我认为您的返回路径之一不会弹出rbp。只需省略

pushq   %rbp
movq    %rsp, %rbp

pop     %rbp
总共。gcc的默认值是
-fomit帧指针

或者修复您的非返回零路径,以同时弹出rbp


事实上,你被搞砸了,因为你的函数似乎被设计成把东西放在堆栈上,而永远不会把它拿走。如果您想发明自己的ABI,其中堆栈指针下方的空间可用于返回数组,这很有趣,但您必须跟踪它们的大小,以便可以将
rsp
调整回
ret
之前的返回地址

我建议不要将返回地址加载到寄存器中,并用
jmp*%rdx
或其他东西替换后面的
ret
。这将使现代CPU中的呼叫/返回地址预测逻辑失效,并导致与分支预测失误相同的暂停。(见附件)。CPU讨厌不匹配的呼叫/重试。我现在找不到一个特定的页面来链接

有关其他有用的资源,请参阅,包括有关函数通常如何使用args的ABI文档


您可以将返回地址复制到刚刚按下的数组下方,然后运行
ret
,以在修改%rsp后返回。但是,除非您需要从多个调用站点调用长函数,否则最好只将其内联到一个或两个调用站点中

如果它太大,无法在太多的呼叫站点进行内联,那么最好的选择是模拟
call
ret
,而不是使用
call
,并将返回地址复制到新位置。打电话的人

    put args in some registers
    lea   .ret_location(%rip), %rbx
    jmp   my_weird_helper_function
.ret_location:  # in NASM/YASM, labels starting with . are local labels, and don't show up in the object file.
         # GNU assembler might only treat symbols starting with .L that way.
    ...


my_weird_helper_function:
    use args, potentially modifying the stack
    jmp *%rbx   # return
你需要一个很好的理由来使用这样的东西。你必须用大量的评论来证明/解释它,因为这不是读者所期望的。首先,您将如何处理这个推到堆栈上的数组?你是想通过减去rsp和rbp或者别的什么来计算它的长度吗

有趣的是,尽管
push
必须修改rsp并进行存储,但在所有最近的cpu上,它每时钟有一个吞吐量。英特尔CPU有一个堆栈引擎,当rsp仅通过push/pop/call/ret进行更改时,堆栈操作不必等待在无序引擎中计算rsp。(将push/pop与
mov 4(%rsp)、%rax
混合使用,或任何导致插入额外UOP以将OOO引擎的rsp与堆栈引擎的偏移量同步的结果。)Intel/AMD CPU每个时钟只能执行一个存储,但Intel SnB和更高版本可以在每个时钟上弹出两次

因此,push/pop实际上并不是实现堆栈数据结构的糟糕方法,特别是在Intel上


此外,您的代码结构怪异
main()
被拆分为
r8位\u到\u堆栈
。这很好,但是你从来没有利用从一个块掉到另一个块的机会,所以它只需要在
main
中额外花费
jmp
,没有任何好处,而且可读性也有很大的缺点

让我们假设您的循环是
main
的一部分,因为我已经谈到了在函数返回时修改%rsp是多么奇怪

您的循环也可以更简单。在可能的情况下,用jcc将事情重新组织起来

避免使用上16个寄存器有一个小好处:使用经典寄存器的32位INSN不需要REX前缀字节。假设我们的起始值为%rax

digits_to_stack:
# put each bit of %rax into its own 8 byte element on the stack for maximum space-inefficiency

    movq   %rax, %rdx  # save a copy

    xor    %ecx, %ecx  # setcc is only available for byte operands, so zero %rcx

    # need a test at the top after transforming while() into do{}while
    test   %rax, %rax  # fewer insn bytes to test for zero this way
    jz  .Lend

    # Another option can be to jmp to the test at the end of the loop, to begin the first iteration there.

.align 16
.Lpush_loop:
    shr   $1, %rax   # shift the low bit into CF, set ZF based on the result
    setc  %cl       # set %cl to 0 or 1, based on the carry flag
    # movzbl %cl, %ecx  # zero-extend
    pushq %rcx
      #.Lfirst_iter_entry
      # test %rax, %rax   # not needed, flags still set from shr
    jnz  .Lpush_loop
.Lend:
这个版本仍然有点糟糕,因为在Intel P6/SnB CPU系列上,在写入较小部分后使用更宽的寄存器会导致速度减慢。(SnB前暂停,或SnB及更高版本的额外uop)。其他,包括AMD和Silvermont,不单独跟踪部分寄存器,因此写入%cl依赖于以前的值%rcx。(写入32位reg会将上32位置零,从而避免部分reg依赖性问题。)将
movzx
置零从字节扩展到长字节将完成Sandybridge隐式执行的操作,并在较旧的CPU上提供加速

这不会在英特尔的每次迭代中运行一个周期,但可能在AMD上运行
mov/和$1
不错,但是
会影响标志,使得仅基于
shr
设置标志进行循环变得更加困难


请注意,您的旧版本
sarq%rax
以符号位移位,而不一定是零,因此使用负输入时,您的旧版本将是一个inf循环(当堆栈空间用完时(push将尝试写入未映射的页))。

Oh,我正在尝试使用
return
从调用
r8位\u返回堆栈
exit\u 0
退出程序。这不是正确的方法吗?难道不可能让一个函数将内容放在堆栈上,然后让另一个函数稍后使用它吗?对不起,我知识不足,谢谢你的帮助@兰斯顿:好吧,我只是浏览了一下你的代码。是的,使用
ret
退出main(或者如果您正在编写不使用C启动文件的独立asm代码,则执行
exit
系统调用。(
gcc-nostartfiles
,或者只执行
as&&ld
)。但是,请参阅我的答案中关于如何使用堆栈的更新。我认为当您尝试在
r8位到堆栈的末尾
ret
时,堆栈指针没有指向返回地址。所以,不,这样做是不正常的。@Langston:更新了我的答案,进行了更多的代码检查并尝试优化循环。我的版本在英特尔pre-Sandybridge上表现不佳,但保存了多条指令