Assembly 如何在ARM64中初始化16位整数数组?

Assembly 如何在ARM64中初始化16位整数数组?,assembly,arm,arm64,Assembly,Arm,Arm64,我想学习一些汇编语言,特别是ARM64 我试图将16位整数数组初始化为某个固定值(123) 以下是我所拥有的: .global _main .align 2 _main: mov x0, #0 ; start with a 0-byte offset mov x1, #123 ; the value to set each 16-bit element to lsl x1, x1, #48 ; shift thi

我想学习一些汇编语言,特别是ARM64

我试图将16位整数数组初始化为某个固定值(123)

以下是我所拥有的:

.global _main
.align 2

_main:
  mov x0, #0               ; start with a 0-byte offset
  mov x1, #123             ; the value to set each 16-bit element to
  lsl x1, x1, #48          ; shift this value to the upper 16-bits of the register

  loop:
    str x1, [sp, x0]       ; store the full 64-bit register at some byte offset
    add x0, x0, #2         ; advance by two bytes (16-bits)
    cmp x0, #10            ; loop until we've written five 16-bit integers
    b.ne loop

  ldr x2, [sp]             ; load the first four 16-bit integers into x2
  ubfm x0, x2, #48, #63    ; set the exit status to the leftmost 16-bit integer
  mov x16, #1              ; system exit
  svc 0                    ; supervisor call
我希望退出状态为123,但它是0。我不明白为什么

如果我注释掉循环的最后两行,则退出状态为123,这是正确的

有人能解释一下发生了什么事吗?这是一个对齐问题吗


谢谢

假设您在
Aaarch64系统上运行程序
,在给定的循环迭代中,您将覆盖在上一个循环中修改的字节:

您实际上正在写入字节:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7b

发件人:
sp+x0+0

至:
sp+x0+7

初始条件:

(gdb) p/x $sp
$3 = 0x40010000
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
0x40010010:     0x0000  0x0000  0x0000  0x0000

# initializing some memory to 0xff so that we may see what is going on
(gdb) set {unsigned long }(0x40010000) = 0xffffffffffffffff
(gdb) set {unsigned long }(0x40010008) = 0xffffffffffffffff
(gdb) set {unsigned long }(0x40010010) = 0xffffffffffffffff

(gdb) x/12xh 0x40010000
0x40010000:     0xffff  0xffff  0xffff  0xffff  0xffff  0xffff  0xffff  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff
在第一个循环之前:

(gdb) p/x$x1
$4 = 0x7b000000000000
循环:

# pass #1, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x007b  0xffff  0xffff  0xffff  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff

# pass #2, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x007b  0xffff  0xffff  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff

# pass #3, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x007b  0xffff  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff

# pass #4, after str x1, [sp, x0]
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x007b  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff

# pass #5, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x007b
0x40010010:     0xffff  0xffff  0xffff  0xffff

# after ldr x2, [sp]:
(gdb) p/x $sp
$2 = 0x40010000
(gdb) p/x $x2
$3 = 0x0
(gdb) 
如果您没有通过注释掉
lsl x1,x1,#48
来移动
x1
中的值,则程序将正常工作:

# after ldr x2, [sp]
(gdb) x/12xh 0x40010000
0x40010000:     0x007b  0x007b  0x007b  0x007b  0x007b  0x0000  0x0000  0x0000
0x40010010:     0x0000  0x0000  0x0000  0x0000
(gdb) p/x $x2
$1 = 0x7b007b007b007b
(gdb) 
这就是说,使用指令可能会更好,这样可以避免在循环的每次迭代中写入比应该写入的字节更多的字节,即16字节而不是2字节

底线是,在一个小尾端系统上,常量
0x0000007b
将作为
7b 00 00
存储在内存中(升序地址),常量
0x7b00000000
将作为
00 00 00 00 7b
存储


由于您所做的转换,您正在将
0x7b00000000000000
,而不是
0x0000000000007b
,存储到内存中。

假设您正在
Aaarch64系统上运行程序,在给定的循环迭代中,您正在覆盖在上一个循环中修改的字节:

您实际上正在写入字节:
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x7b

发件人:
sp+x0+0

至:
sp+x0+7

初始条件:

(gdb) p/x $sp
$3 = 0x40010000
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000
0x40010010:     0x0000  0x0000  0x0000  0x0000

# initializing some memory to 0xff so that we may see what is going on
(gdb) set {unsigned long }(0x40010000) = 0xffffffffffffffff
(gdb) set {unsigned long }(0x40010008) = 0xffffffffffffffff
(gdb) set {unsigned long }(0x40010010) = 0xffffffffffffffff

(gdb) x/12xh 0x40010000
0x40010000:     0xffff  0xffff  0xffff  0xffff  0xffff  0xffff  0xffff  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff
在第一个循环之前:

(gdb) p/x$x1
$4 = 0x7b000000000000
循环:

# pass #1, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x007b  0xffff  0xffff  0xffff  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff

# pass #2, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x007b  0xffff  0xffff  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff

# pass #3, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x007b  0xffff  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff

# pass #4, after str x1, [sp, x0]
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x007b  0xffff
0x40010010:     0xffff  0xffff  0xffff  0xffff

# pass #5, after str x1, [sp, x0]
(gdb) x/12xh 0x40010000
0x40010000:     0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x0000  0x007b
0x40010010:     0xffff  0xffff  0xffff  0xffff

# after ldr x2, [sp]:
(gdb) p/x $sp
$2 = 0x40010000
(gdb) p/x $x2
$3 = 0x0
(gdb) 
如果您没有通过注释掉
lsl x1,x1,#48
来移动
x1
中的值,则程序将正常工作:

# after ldr x2, [sp]
(gdb) x/12xh 0x40010000
0x40010000:     0x007b  0x007b  0x007b  0x007b  0x007b  0x0000  0x0000  0x0000
0x40010010:     0x0000  0x0000  0x0000  0x0000
(gdb) p/x $x2
$1 = 0x7b007b007b007b
(gdb) 
这就是说,使用指令可能会更好,这样可以避免在循环的每次迭代中写入比应该写入的字节更多的字节,即16字节而不是2字节

底线是,在一个小尾端系统上,常量
0x0000007b
将作为
7b 00 00
存储在内存中(升序地址),常量
0x7b00000000
将作为
00 00 00 00 7b
存储

由于您所做的转换,您正在将
0x7b00000000000000
,而不是
0x0000000000007b
,存储到内存中。

有趣的方式:

如果每个重复中的所有设置位都是连续的,AArch64可以有效地将重复模式(任意2次幂长度)放入64位寄存器。(这就是它对位布尔指令的即时编码方式,如
orrx1,xzr,#0x0303030303
=
movx1,#…
)。这几乎适用于您的
123
=
0x7b
=
0b1111011

或者,
ldrx1,=0x007B007B007B007B
将要求汇编程序为您执行此操作;在这种情况下,GAS选择将常数放在附近的内存中,并使用PC相对寻址模式加载它

您可以在将阵列存储到堆栈的同时为其保留空间,方法是使用具有回写寻址模式的存储,该模式使用减去的偏移量更新基址寄存器(
sp
)。AArch64就是这样高效地实现堆栈“推送”操作的。e、 g.在需要保存一些寄存器的函数中,GCC使用
stp x29,x30,[sp,-32]从SP中减去32字节,并将该对寄存器存储在该空间的底部。()

所以我认为这应该行得通。这确实可以组装,但我还没有试过运行它。AArch64的标准调用约定保持16字节堆栈对齐,因此此存储对16字节存储对齐

mov     x0, #0x7f007f007f007f
and     x0, x0, #~0x0004000400040004    // construct 0x007B repeating
stp     x0, x0, [sp, -16]!              // push x0 twice

// SP now points at 8 copies of (uint16_t)123, below whatever was on the stack before
strh
(存储16位半字)的循环用于无聊的编译器;当手写时,尽量用最少的指令完成尽可能多的工作。(这是一个一般的经验法则,并不总是与性能相关!例如,如果某个存储是最近的存储,则仅与前一个存储部分重叠的大负载可能会导致存储转发暂停。

有趣的方式:

AArch64可以有效地将重复模式(任意长度的二次幂)放入64位寄存器,前提是每个重复中的所有设置位都是连续的。(这就是它对逐位布尔指令(如
orr-x1,xzr,#0x0303030303
=
mov-x1,#…
)进行立即编码的方式。)。这几乎适用于您的
123
=
0x7b
=
0b1111011

或者,
ldr x1,=0x007B007B007B007B
将要求汇编程序为您执行此操作;在这种情况下,GAS选择将常量放在附近的内存中,并使用PC相对寻址模式加载它

您可以在将阵列存储到堆栈的同时为其保留空间,方法是使用具有回写寻址模式的存储,该模式使用减去的偏移量更新基址寄存器(
sp
),AArch64就是这样实现堆栈“推送”的高效操作。例如,在需要保存一些寄存器的函数中,GCC在函数项上使用
stp x29,x30,[sp,-32]!
从sp中减去32字节,并将这对寄存器存储在该空间的底部。()

所以我认为这应该行得通。这是汇编,但我还没有试过运行它。AArch64的标准调用约定保持16字节堆栈对齐,所以这个存储对16字节存储是对齐的

mov     x0, #0x7f007f007f007f
and     x0, x0, #~0x0004000400040004    // construct 0x007B repeating
stp     x0, x0, [sp, -16]!              // push x0 twice

// SP now points at 8 copies of (uint16_t)123, below whatever was on the stack before
L