我知道你可以使用GCC选项将其转换为汇编,但这并没有教会我任何关于这个过程的东西。有没有关于如何做到这一点的指南?我在MacOS上。我的教授没有说任何关于在Windows上编写汇编代码与在macOS或其他。
int main()
{
int x, y, result=0;
//read x
printf("Please enter x: ");
scanf("%d", &x);
//read y
printf("Please enter y: ");
scanf("%d", &y);
if (y<0)
{
x=0-x;
y=0-y;
}
for (int counter=0; counter<y; counter++)
{
result+=x;
}
printf("x*y = %d\n",result);
return 0;
}
这是我迄今为止最好的尝试:
.section .data
input_x_prompt: .asciz "Please enter x: "
input_y_prompt: .asciz "Please enter y: "
input_spec: .asciz "%d"
result_prompt: .asciz "x*y = %d\n"
.section .bss
x: .space 4
y: .space 4
result: .space 4
.section .text
.global main
main:
# Display "Please enter x: "
ldr x0, =input_x_prompt
bl printf
# Read x
ldr x0, =x
ldr x1, =input_spec
bl scanf
# Display "Please enter y: "
ldr x0, =input_y_prompt
bl printf
# Read y
ldr x0, =y
ldr x1, =input_spec
bl scanf
# Check if y is negative and negate both x and y
ldr x1, [y]
cmp x1, 0
blt negate_x_and_y
calculate_product:
# Initialize result to 0
mov x2, 0
# Initialize counter to 0
mov x3, 0
product_loop:
# Compare counter with y
cmp x3, x1
bge print_result
# Add x to the result
ldr x4, [x]
add x2, x2, x4
# Increment counter
add x3, x3, 1
# Repeat the loop
b product_loop
print_result:
# Display the result
ldr x0, =result_prompt
ldr x1, [x2]
bl printf
exit:
mov x0, 0
mov x8, 93
svc 0
ret
negate_x_and_y:
# Negate x
ldr x4, [x]
neg x4, x4
str x4, [x]
# Negate y
ldr x4, [y]
neg x4, x4
str x4, [y]
b calculate_product
请看上面我尝试的代码。
1条答案
按热度按时间von4xj4u1#
好吧,让我们一步一步地修复你的组件。
让我们先把它编译一下。把它扔到
cc
上,我们得到了这样的结果:这实际上是两种错误,每种都重复了几次:
1.部分指令。
.section .data
无效。你可以写.data
或.section __DATA,__data
。.text
(__TEXT,__text
)和.bss
(__DATA,__bss
)也是如此。通过在llvm/lib/MC/MCParser/DarwinAsmParser.cpp
in the LLVM source tree中搜索DarwinAsmParser::parseSectionDirective
可以找到节别名的完整列表。1.使用
[]
删除全局变量。不是这样的[]
用于解引用寄存器。指令集允许从ldr x1, y
等PC相对加载,但macOS工具链仅在加载的标签与执行加载的指令位于同一节时才允许。由于您从中加载的标签x
和y
位于数据中,因此不能在此处使用。但这只允许你加载,但你也想存储,所以你需要生成标签的地址。您可以通过使用adrp xN, label@PAGE
生成标签的4K页面,然后使用add xN, xN, label@PAGEOFF
,或者直接将label@PAGEOFF
插入到ldr
/str
指令中:一旦修复了这个问题,我们就会得到一些链接器错误:
第一个错误已经暗示了解决方案:在达尔文上,C函数的程序集名称需要以下划线作为前缀,因此
_main
、_printf
和_scanf
。一旦修复,我们得到一个新的链接器错误:
这实际上是两个错误之一。
首先,标签 * 没有对齐到任何东西。由于
ldr
和str
存储的偏移量按加载值的大小缩放,因此8字节加载只能从8字节对齐的地址加载。我们可以通过将.balign 8
放在.bss
和x:
之间来解决这个问题。但是第二,8字节是不正确的!我们的全局变量只有
.space 4
。因此,我们实际上需要调整程序集以使用w1
,w4
等。而不是x1
、x4
等。然后我们应该使用.balign 4
。有了这个固定的,它现在编译。但它还不起作用,我们立即得到一个分割错误。这是因为
ldr xN, =...
在达尔文上不起作用,这是由于协同设计的原因。参见How to load data by label on Apple Silicon (ARM64)。一旦修复了这个问题,代码中唯一剩下的
=
应该在一个字符串中。但代码还是崩溃了。在这一点上,它的时间来看看一些编译器输出。
我已经把你上面的C代码移到了
main()
之外,以匹配程序集的功能,并给它一个volatile
修饰符,以防止编译器优化任何访问。用
-S -O3
编译它,然后去掉一些.loh
和.cfi*
指令以及一些自动生成的注解,我们得到了这样的结果:你会注意到的一件事是,它使用了堆栈。不仅仅是建立一个堆栈框架,不仅仅是溢出寄存器,而是在某些函数调用之前主动向堆栈写入内容。具体而言:varargs。
printf
和scanf
的原型如下:确切的ABI有一堆边缘情况(see here for a more comprehensive answer on the topic),但在这种情况下,可以说命名参数进入寄存器,而varargs进入堆栈,每个填充为8个字节。
所以你的格式字符串放在
x0
中,scanf的指针和printf的整数都放在[sp]
中。这也意味着您需要保留一些堆栈空间。在_main
的开头添加sub sp, sp, 0x10
就足够了,只需need to make sure that the stack is always aligned to 16 bytes。在
print_result
中,您还可以使用ldr x1, [x2]
,它...我不知道这是怎么回事?x2
已经是整数结果了,所以我用str w2, [sp]
替换了它。修复所有这些使代码真正工作:
但最后还是会崩溃这是因为您使用的是Linux系统调用ABI。有关(不稳定!)arm 64 XNU syscall ABI,参见this answer,但在此只需说明syscall编号位于
x16
中,而exit
的syscall编号为1:就这样,它干净地离开了。