assembly 我需要将此C代码转换为AArch64 macOS(M1)程序集的一般提示和指导

raogr8fs  于 2023-10-19  发布在  Mac
关注(0)|答案(1)|浏览(142)

我知道你可以使用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

请看上面我尝试的代码。

von4xj4u

von4xj4u1#

好吧,让我们一步一步地修复你的组件。
让我们先把它编译一下。把它扔到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]

这实际上是两种错误,每种都重复了几次:
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工具链仅在加载的标签与执行加载的指令位于同一节时才允许。由于您从中加载的标签xy位于数据中,因此不能在此处使用。但这只允许你加载,但你也想存储,所以你需要生成标签的地址。您可以通过使用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)

第一个错误已经暗示了解决方案:在达尔文上,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)

这实际上是两个错误之一。
首先,标签 * 没有对齐到任何东西。由于ldrstr存储的偏移量按加载值的大小缩放,因此8字节加载只能从8字节对齐的地址加载。我们可以通过将.balign 8放在.bssx:之间来解决这个问题。
但是第二,8字节是不正确的!我们的全局变量只有.space 4。因此,我们实际上需要调整程序集以使用w1w4等。而不是x1x4等。然后我们应该使用.balign 4
有了这个固定的,它现在编译。但它还不起作用,我们立即得到一个分割错误。这是因为ldr xN, =...在达尔文上不起作用,这是由于协同设计的原因。参见How to load data by label on Apple Silicon (ARM64)
一旦修复了这个问题,代码中唯一剩下的=应该在一个字符串中。
但代码还是崩溃了。在这一点上,它的时间来看看一些编译器输出。
我已经把你上面的C代码移到了main()之外,以匹配程序集的功能,并给它一个volatile修饰符,以防止编译器优化任何访问。
-S -O3编译它,然后去掉一些.loh.cfi*指令以及一些自动生成的注解,我们得到了这样的结果:

.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

你会注意到的一件事是,它使用了堆栈。不仅仅是建立一个堆栈框架,不仅仅是溢出寄存器,而是在某些函数调用之前主动向堆栈写入内容。具体而言:varargsprintfscanf的原型如下:

int printf(const char * restrict format, ...);
int scanf(const char *restrict format, ...);

确切的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]替换了它。
修复所有这些使代码真正工作:

Please enter x: 7
Please enter y: 8
x*y = 56
zsh: invalid system call  ./t

但最后还是会崩溃这是因为您使用的是Linux系统调用ABI。有关(不稳定!)arm 64 XNU syscall ABI,参见this answer,但在此只需说明syscall编号位于x16中,而exit的syscall编号为1:

exit:
    mov x0, 0
    mov x16, 1
    svc 0

就这样,它干净地离开了。

相关问题