我正在学习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
问题是我得到分段错误。 哪里有问题?
我认为这里的代码失败了:
_start: pushl $24 call profit movl %eax, %ebx movl $1, %eax int $0x80 profit: popl %ebx popl %eax
所以,你push $24
(4字节),然后call profit
,推动profit
跳跃profit
。 然后你把eip
的值和$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
你似乎没有正确地进行函数调用。 您需要阅读并理解x86 ABI( 32位 , 64位 ),特别是“调用约定”部分。
另外,这不是你的直接问题,但是:不要写_start
,写main
,就好像这是一个C程序。 当你开始做更复杂的事情时,你会希望C库可用,这意味着你必须让它自己初始化。 相关的, 不要做你自己的系统调用; 调用C库中的包装器。 这样可以隔离内核接口中的低级更改,确保errno
可用,等等。
ecx
(我不确定Linux在进程启动的时候是否能保证ecx
的状态 – 如果不是按规则在实践中看起来是0
) jg end
时接受jg end
跳转时,返回地址不再在堆栈上,所以ret
会将控制权转移给某个垃圾地址。 在你称之为利润之前,它看起来像你有一个单一的推动力,然后利润做的第一件事就是做两个指令。 我希望这会弹出你推入堆栈的价值以及返回代码,以便您的ret不起作用。
推和pop应该是相同的次数。
调用将返回地址推入堆栈。
你的问题是,你弹出的堆栈返回地址,当你分支结束时,不要恢复。 一个快速的解决办法是在那里添加push %ebx
。
你应该做的是修改你的过程,所以它正确地使用调用约定。 在Linux中,调用函数需要清除栈中的参数,所以你的过程应该把它们留在原来的位置。
而不是这样做得到的参数,然后恢复返回地址
popl %ebx popl %eax
你应该这样做,并留下他们的地址和参数
movl 4(%esp), %eax
并摆脱将返回地址推回栈中的代码。 你应该添加
subl $4, %esp
之后调用该过程从堆栈中删除参数。 如果您希望能够从其他语言中调用您的汇编程序,则正确地遵循此惯例很重要。