我正在学习AT& T x86汇编语言。我正在尝试编写一个取整数n的汇编程序,然后返回结果(n / 2 + n / 3 + n / 4)。这就是我所做的:
.text
.global _start
_start:
pushl $24
call profit
movl %eax, %ebx
movl $1, %eax
int $0x80
profit:
popl %ebx
popl %eax
mov $0, %esi
movl $4, %ebp
div %ebp
addl %eax, %esi
movl %ecx, %eax
movl $3, %ebp
div %ebp
addl %eax, %esi
movl %ecx, %eax
movl $2, %ebp
div %ebp
addl %eax, %esi
movl %esi, %eax
cmpl %ecx, %esi
jg end
pushl %ebx
ret
end:
mov %ecx, %eax
ret
问题是我遇到了分段错误。问题在哪里?
答案 0 :(得分:8)
我认为代码在这里失败了:
_start:
pushl $24
call profit
movl %eax, %ebx
movl $1, %eax
int $0x80
profit:
popl %ebx
popl %eax
所以,你push $24
(4个字节)然后是call profit
,它会推动eip
并跳转到profit
。然后,将eip
的值转换为ebx
,将值$24
转换为eax
。
然后,最后,如果jg end
分支到end:
,则堆栈将不会保留有效的返回地址,ret
将失败。你可能也需要pushl %ebx
。
cmpl %ecx, %esi
jg end
pushl %ebx
ret
end:
mov %ecx, %eax
; `pushl %ebx` is needed here!
ret
答案 1 :(得分:2)
ecx
而没有明确地初始化它(我不确定Linux在进程开始时是否会保证ecx
的状态 - 如果没有,它看起来像0
按规则)jg end
跳转时,返回地址不再在堆栈上,因此ret
会将控制转移到某个垃圾地址。答案 2 :(得分:2)
您似乎没有正确执行函数调用。您需要阅读并理解x86 ABI(32-bit,64-bit),特别是“调用约定”部分。
此外,这不是您的直接问题,但是:不要写_start
,写main
就好像这是一个C程序。当你开始做一些更复杂的事情时,你会希望C库可用,这意味着你必须让它自己初始化。相关地,不进行自己的系统调用;调用C库中的包装器。这将使您免受内核界面中的低级更改,确保errno
可用,等等。
答案 3 :(得分:2)
您的问题是您从堆栈中弹出退货地址,当您分支到结束时,您不会将其恢复。快速解决方法是在其中添加push %ebx
。
您应该做的是修改您的程序,以便正确使用调用约定。在Linux中,调用函数应该从堆栈中清除参数,因此您的过程应该将它们保留在原来的位置。
而不是这样做以获取参数然后稍后恢复返回地址
popl %ebx
popl %eax
您应该这样做,并将返回地址和参数保留在
movl 4(%esp), %eax
并删除将返回地址推回堆栈的代码。然后你应该添加
subl $4, %esp
调用过程后从堆栈中删除参数。如果您希望能够从其他语言调用汇编过程,请务必正确遵循此约定。
答案 4 :(得分:1)
在你看到利润之前,我认为你有一个单一的pushl,然后利润做的第一件事是做两个popl指令。我希望这会弹出你压入堆栈的值以及返回代码,这样你的ret就不会工作了。
推送和弹出应该是相同的次数。
调用将返回地址压入堆栈。