装配 - 如何在装配中用常数乘以一个常数?
所以,我有一个汇编函数,它在C中调用。它编译并且没有给出警告,但是当我尝试运行它时,它给了我一个分段错误。我认为这是因为我不能将一个常量移动到一个寄存器中,但是使用mul/div命令它需要一个值在EAX寄存器中。 我如何在Assembly中乘两个常量?装配 - 如何在装配中用常数乘以一个常数?
下面的代码到目前为止...
.section .data
.global n
.equ A, 50
.equ B, 5
.section .text
.global loop_function
loop_function:
# prologue
pushl %ebp # save previous stack frame pointer
movl %esp, %ebp # the stack frame pointer for sum function
# beginning
movl i, %ebx # place i (declared in c) in ebx
movl A, %eax # place A in eax
movl B, %ecx # place B in ecx
jmp loop
loop:
movl $0, %edx # clean edx register
cdq
idivl %ecx # A/B, result in eax
imull %ebx # i * A/B, result in eax
incl %ebx
cmpl %ebx, n # if i <= n
jle loop # then jumps to loop
jmp end # else jumps to end
end:
# epilogue
movl %ebp, %esp # restore the previous stack pointer ("clear" the stack)
popl %ebp # restore the previous stack frame pointer
ret
GAS支持常量the *
operators for assemble-time multiplication。例如,mov $(5 * 50), %eax
汇编到与mov $250, %eax
完全相同的机器代码。其他运算符(如+ - /%)和位运算符也可用。我只是使用*
作为示例,但只要它们计算为单个数字(或链接器可以解析的符号的偏移量),就可以从编译时常量中构造任意表达式。
这适用于汇编常量,如.equ A, 50
或A = 50
。
.equ A, 50
.equ B, 5
aa = 3
bb = 7
.globl _start
_start: # machine code .intel_syntax disassembly
mov $(5 * 50), %eax # b8 fa 00 00 00 mov eax,0xfa # 250
mov $(aa * B), %ecx # b9 0f 00 00 00 mov ecx,0xf # 3*5 = 15
mov $A * B, %edx # ba fa 00 00 00 mov edx,0xfa # 250
注意,整个立即数只使用一个$
,而不是在每一个符号名称$
。例如,mov $(5 + $A), %eax
会尝试将名为$A
(加5)的符号地址放入%eax
中,以便为未定义的符号获取链接时错误。
mov $($A * $B), %eax
甚至没有组装:Error: invalid operands (*UND* and *UND* sections) for '*'
这是因为你想乘两个未知符号($A
和$B
)的地址,而不是你的汇编常数A
和B
。
在GAS中,all symbols have an associated section。当您使用.equ
或=
定义符号时,它是一个“绝对”符号(而不是.data
节或.text
节符号,就像您从标签A:
中获得的那样)。
汇编程序常量与用标号定义的符号并无太大区别。但是,除+
和-
之外,全部为assemble-time math operators require both args to be absolute, and the result is absolute。
您的代码似乎试图将常量放入寄存器中以在运行时将它们相乘。如果你坚持这样做,作为一个练习,
mov $A, %ecx # put symbol's value in ECX
imul $B, %ecx, %eax # EAX = A * B
mov A, %eax
是从符号的值的负载。即来自绝对地址50
的负载,这显然是段错误。使用调试器进行单步调试,并查看反汇编以了解发生了什么。
AT &对于即时常量,T语法使用$
,所以使用它来获取值。 (请记住,.equ
符号的行为与标签相同,例如您将如何使用$my_string
来获取地址作为即时贴。)
感谢您的帮助,伙计们,我能够做到用下面的代码练习:
.section .data
.global n
.global i
.equ A, 50
.equ B, 5
.section .text
.global loop_function
loop_function:
# prologue
pushl %ebp # save previous stack frame pointer
movl %esp, %ebp # the stack frame pointer for sum function
# beginning
movl i, %ecx # place i (declared in c) in ecx
movl $A, %eax # place A in eax
movl $B, %ebx # place B in ebx
movl $0, %edx # clean edx register
cdq
idivl %ebx # (A/B), result goes to eax
loop:
incl %ecx # increment i, which is in ecx
cmpl n, %ecx # if n > i
jg loop # then jumps to loop
end:
incl %ecx
imull %ecx # multiply i by (A/B), result in eax
# epilogue
movl %ebp, %esp # restore the previous stack pointer ("clear" the stack)
popl %ebp # restore the previous stack frame pointer
ret
您应该将'i'和'n'作为函数参数而不是全局变量。另外,'cdq'将'edx'设置为'eax',所以首先将'edx'归零是没有意义的。另外,我没有看到循环的重点。你可以用'mov n,%ecx'替换它。 (或者我想用额外的'inc',你正在做'%ecx =(n)+ 1') –
另外,你在不保存它的情况下'%ebx'。使用'%ecx'来保存idiv除数的'$ B'。 –
所以你的整个功能可能是'mov $ A,%eax'; 'cdq'; 'mov $ B,%ecx'; 'idiv%ecx'; 'imul n,%eax'。 (或者,如果它实际上是你需要的n + 1,则将'n'加载到'ecx'或'edx'中,然后加载'inc%ecx')。除非您想在'edx:eax'中返回完整的64位产品,否则不需要使用单操作数格式。 2操作数格式更快,只写入一个寄存器。) –
真正的答案是在[Application_binary_interface](https://en.wikipedia.org/ wiki/Application_binary_interface)(ABI)。如果您没有ABI,那么我建议您将每个使用的寄存器推入堆栈,并在返回之前将其弹出。 – user3386109
错误的可能原因是您没有遵循标准调用约定。特别是,你销毁了'ebx'这是一个被保存的被保存的寄存器,所以你的调用者可能会希望它没有改变。 – Jester
A和B似乎是常量,我猜测我是一个变量,但是对所有变量使用完全相同的语法。我认为它试图从内存地址50和地址5加载值,这不会起作用。我建议使用$ A和$ B。 – prl