不幸的是,我的教授是有史以来最糟糕的教授之一,她制作的材料几乎没有任何材料可以对此提供任何帮助。我并不是在寻找直接答案,我只是想要教材,这样我就可以自己学习如何做到这一点。我知道您可以使用 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
请参阅上面我尝试的代码。
好吧,让我们一步步修复你的组件。
我们首先对其进行编译。按原样将其扔到
cc
会给我们这样的结果:
t.s:1:15: error: unexpected token in '.section' directive
.section .data
^
t.s:7:14: error: unexpected token in '.section' directive
.section .bss
^
t.s:12:15: error: unexpected token in '.section' directive
.section .text
^
t.s:35:14: error: invalid operand for instruction
ldr x1, [y]
^
t.s:52:14: error: invalid operand for instruction
ldr x4, [x]
^
t.s:75:14: error: invalid operand for instruction
ldr x4, [x]
^
t.s:77:14: error: invalid operand for instruction
str x4, [x]
^
t.s:80:14: error: invalid operand for instruction
ldr x4, [y]
^
t.s:82:14: error: invalid operand for instruction
str x4, [y]
这些实际上只是两种错误,每种错误都会重复几次:
部分指令。
.section .data
无效。您可以写 .data
或 .section __DATA,__data
。 .text
(__TEXT,__text
) 和 .bss
(__DATA,__bss
) 也是如此。通过在 LLVM 源代码树中的
DarwinAsmParser::parseSectionDirective
中搜索
llvm/lib/MC/MCParser/DarwinAsmParser.cpp
可以找到节别名的完整列表。
使用
[]
访问全局变量。事情不是这样的。 []
用于取消引用寄存器。该指令集允许从类似 ldr x1, y
等进行 PC 相关加载,但 macOS 工具链仅允许加载的标签与执行加载的指令位于同一部分中。由于您加载的标签 x
和 y
位于数据中,因此您不能在此处使用它。但这只允许您进行加载,但您还想做商店,因此您需要以任何方式生成标签的地址。您可以通过使用 adrp xN, label@PAGE
生成标签的 4K 页面,然后使用 add xN, xN, label@PAGEOFF
,或者将 label@PAGEOFF
直接插入到 ldr
/str
指令中来实现此目的:
adrp x1, y@PAGE
ldr x1, [x1, y@PAGEOFF]
adrp x5, x@PAGE
ldr x4, [x5, x@PAGEOFF]
neg x4, x4
str x4, [x5, x@PAGEOFF]
修复后,我们会收到一些链接器错误:
ld: Undefined symbols:
_main, referenced from:
<initial-undefines>
printf, referenced from:
main in t-15d51f.o
main in t-15d51f.o
main in t-15d51f.o
scanf, referenced from:
main in t-15d51f.o
main in t-15d51f.o
clang: error: linker command failed with exit code 1 (use -v to see invocation)
第一个错误已经暗示了解决方案:在 Darwin 上,C 函数的程序集名称需要以下划线作为前缀,因此
_main
、_printf
和 _scanf
。
修复后,我们会收到一个新的链接器错误:
ld: 'y' from '/private/var/folders/0s/mrkxlvcs10l0tswpv763_fdw0000gn/T/t-262b4d.o' not 8-byte aligned, which cannot be encoded as a target of LDR/STR in '_main' from '/private/var/folders/0s/mrkxlvcs10l0tswpv763_fdw0000gn/T/t-262b4d.o'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
这实际上是两个错误合二为一。
首先,标签没有与任何东西对齐。由于
ldr
和 str
存储按加载值大小缩放的偏移量,因此 8 字节加载只能从 8 字节对齐的地址加载。我们可以通过将 .balign 8
放在 .bss
和 x:
之间来解决这个问题。.space 4
。所以我们实际上需要调整组件以使用w1
、w4
等而不是x1
、x4
等。然后我们应该使用.balign 4
。
修复后,现在可以编译了。但它还没有起作用,我们立即得到一个分段错误。这是因为由于协同设计,
ldr xN, =...
不适用于 Darwin。请参阅如何在 Apple Silicon (ARM64) 上按标签加载数据。=
应该位于字符串内。
但是代码仍然崩溃。现在是时候查看一些编译器输出了。
我已将上面的 C 代码移至
int x, y, result=0;
之外以匹配程序集正在执行的操作,并为其指定了 main()
修饰符以防止编译器优化任何访问。使用
volatile
-S -O3
和 .loh
指令以及一些自动生成的注释,给我们留下这样的结果:.cfi*
您会注意到的一件事是它使用了堆栈。不仅仅是设置堆栈帧,不仅仅是溢出寄存器,而是在某些函数调用之前主动将内容写入堆栈。具体来说:varargs
。 .section __TEXT,__text,regular,pure_instructions
.build_version macos, 14, 0 sdk_version 14, 0
.globl _main
.p2align 2
_main:
sub sp, sp, #64
stp x22, x21, [sp, #16]
stp x20, x19, [sp, #32]
stp x29, x30, [sp, #48]
add x29, sp, #48
adrp x0, l_.str@PAGE
add x0, x0, l_.str@PAGEOFF
bl _printf
adrp x20, _x@GOTPAGE
ldr x20, [x20, _x@GOTPAGEOFF]
str x20, [sp]
adrp x19, l_.str.1@PAGE
add x19, x19, l_.str.1@PAGEOFF
mov x0, x19
bl _scanf
adrp x0, l_.str.2@PAGE
add x0, x0, l_.str.2@PAGEOFF
bl _printf
adrp x21, _y@GOTPAGE
ldr x21, [x21, _y@GOTPAGEOFF]
str x21, [sp]
mov x0, x19
bl _scanf
ldr w8, [x21]
tbnz w8, #31, LBB0_2
ldr w9, [x21]
adrp x8, _result@PAGE
cmp w9, #1
b.ge LBB0_3
b LBB0_5
LBB0_2:
ldr w8, [x20]
neg w8, w8
str w8, [x20]
ldr w8, [x21]
neg w8, w8
str w8, [x21]
ldr w9, [x21]
adrp x8, _result@PAGE
cmp w9, #1
b.lt LBB0_5
LBB0_3:
mov w9, #0
LBB0_4:
ldr w10, [x20]
ldr w11, [x8, _result@PAGEOFF]
add w10, w11, w10
str w10, [x8, _result@PAGEOFF]
add w9, w9, #1
ldr w10, [x21]
cmp w9, w10
b.lt LBB0_4
LBB0_5:
ldr w8, [x8, _result@PAGEOFF]
str x8, [sp]
adrp x0, l_.str.3@PAGE
add x0, x0, l_.str.3@PAGEOFF
bl _printf
mov w0, #0
ldp x29, x30, [sp, #48]
ldp x20, x19, [sp, #32]
ldp x22, x21, [sp, #16]
add sp, sp, #64
ret
.globl _result
.zerofill __DATA,__common,_result,4,2
.section __TEXT,__cstring,cstring_literals
l_.str:
.asciz "Please enter x: "
l_.str.1:
.asciz "%d"
.comm _x,4,2
l_.str.2:
.asciz "Please enter y: "
.comm _y,4,2
l_.str.3:
.asciz "x*y = %d\n"
.subsections_via_symbols
和
printf
的原型如下:scanf
确切的 ABI 有很多边缘情况(请参阅此处以获得有关该主题的更全面的答案
),但在这种情况下,足以说明命名参数位于寄存器中,而可变参数位于堆栈中,每个都填充到8 字节。
所以你的格式字符串进入int printf(const char * restrict format, ...);
int scanf(const char *restrict format, ...);
,scanf的指针和printf的整数进入
x0
...这也意味着你需要保留一些堆栈空间。在 [sp]
的开头添加 sub sp, sp, 0x10
就足够了,您只需 需要确保堆栈始终与 16 字节对齐。 在
_main
print_result
,这......我不知道它来自哪里? ldr x1, [x2]
已经是整数结果了,所以我用x2
替换了它。解决所有这些问题使代码真正起作用:
str w2, [sp]
...但最后还是崩溃了。那是因为您正在使用 Linux 的系统调用 ABI。有关(不稳定!)arm64 XNU 系统调用 ABI 的更多详细信息,请参阅这个答案
,但这里只要说系统调用编号进入 Please enter x: 7
Please enter y: 8
x*y = 56
zsh: invalid system call ./t
且
x16
的系统调用编号为 1:就足够了
exit
这样,它就干净地退出了。