我需要有关将此 C 代码转换为汇编的一般提示和指导

问题描述 投票:0回答:1

不幸的是,我的教授是有史以来最糟糕的教授之一,她制作的材料几乎没有任何材料可以对此提供任何帮助。我并不是在寻找直接答案,我只是想要教材,这样我就可以自己学习如何做到这一点。我知道您可以使用 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

请参阅上面我尝试的代码。

macos assembly arm64
1个回答
0
投票

好吧,让我们一步步修复你的组件。

我们首先对其进行编译。按原样将其扔到

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 源代码树
    中的 
    DarwinAsmParser::parseSectionDirective
     中搜索 
    llvm/lib/MC/MCParser/DarwinAsmParser.cpp
    可以找到节别名的完整列表。

  2. 使用

    []
    访问全局变量。事情不是这样的。
    []
    用于取消引用寄存器。该指令集允许从类似
    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:
之间来解决这个问题。
但第二,8 个字节是不对的!我们的全局变量只有
.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

这样,它就干净地退出了。

© www.soinside.com 2019 - 2024. All rights reserved.