汇编语言中的分割错误

我正在学习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可用,等等。

  1. 你不用明确的初始化它就可以使用ecx (我不确定Linux在进程启动的时候是否能保证ecx的状态 – 如果不是按规则在实践中看起来是0
  2. 当程序在程序jg end时接受jg end跳转时,返回地址不再在堆栈上,所以ret会将控制权转移给某个垃圾地址。

在你称之为利润之前,它看起来像你有一个单一的推动力,然后利润做的第一件事就是做两个指令。 我希望这会弹出你推入堆栈的价值以及返回代码,以便您的ret不起作用。

推和pop应该是相同的次数。

调用将返回地址推入堆栈。

你的问题是,你弹出的堆栈返回地址,当你分支结束时,不要恢复。 一个快速的解决办法是在那里添加push %ebx

你应该做的是修改你的过程,所以它正确地使用调用约定。 在Linux中,调用函数需要清除栈中的参数,所以你的过程应该把它们留在原来的位置。

而不是这样做得到的参数,然后恢复返回地址

 popl %ebx popl %eax 

你应该这样做,并留下他们的地址和参数

 movl 4(%esp), %eax 

并摆脱将返回地址推回栈中的代码。 你应该添加

 subl $4, %esp 

之后调用该过程从堆栈中删除参数。 如果您希望能够从其他语言中调用您的汇编程序,则正确地遵循此惯例很重要。