汇编 - 如何在汇编中将一个常数乘/除以另一个常数?
Posted
技术标签:
【中文标题】汇编 - 如何在汇编中将一个常数乘/除以另一个常数?【英文标题】:Assembly - How to multiply/divide a constant by another constant in assembly? 【发布时间】:2018-04-02 19:20:48 【问题描述】:所以,我有一个汇编函数,它在 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
【问题讨论】:
真正的答案在Application_binary_interface (ABI) 中。如果您没有 ABI,那么我建议将您使用的每个寄存器都推入堆栈,并在返回之前将其弹出。 错误的可能原因是您没有遵循标准调用约定。特别是,您销毁了ebx
,它是一个被调用者保存的寄存器,因此您的调用者很可能希望它保持不变。
A 和 B 似乎是常量,我猜 i 是一个变量,但您对它们都使用完全相同的语法。我认为它正在尝试从内存地址 50 和地址 5 加载值,这是行不通的。我建议使用 $A 和 $B。
如果我把 $50 和 $5 放在常量的声明中,编译失败,我认为 $A 给出了常量的内存地址。
您将常量声明为内存地址,因此您应该使用$A
和$B
。你试过了吗?为了能够使用A
,您需要类似:A: .int 50
。
【参考方案1】:
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
将地址作为立即数一样。)
【讨论】:
【参考方案2】:感谢您的所有帮助,伙计们,我设法使用以下代码进行了练习:
.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
保存$B
作为idiv 除数。
所以你的整个函数可以是 mov $A, %eax
; cdq
; mov $B, %ecx
; idiv %ecx
; imul n, %eax
。 (或者将n
加载到ecx
或edx
然后inc %ecx
如果它实际上是您需要的n+1
)。您不需要使用单操作数形式,除非您想在 edx:eax
中返回完整的 64 位产品。 2 操作数形式更快,并且只写入一个寄存器。)以上是关于汇编 - 如何在汇编中将一个常数乘/除以另一个常数?的主要内容,如果未能解决你的问题,请参考以下文章