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上表现不佳,但保存了多条指令