【问题标题】:Declaring and indexing an integer array of qwords in assembly在汇编中声明和索引 qwords 的整数数组
【发布时间】:2019-10-15 20:19:50
【问题描述】:

我有一个关于如何在程序集中初始化数组的问题。我试过了:

.bss
#the array
unsigned:    .skip 10000
.data
#these are the values that I want to put in the array
par4:   .quad 500 
par5:   .quad 10
par6:   .quad 15

这就是我声明我的字符串和我想要放入其中的变量的方式。 这就是我尝试将它们放入数组的方式:

movq $0 , %r8

movq par4 , %rax
movq %rax , unsigned(%r8)
incq %r8

movq par5 , %rax
movq %rax , unsigned(%r8)
incq %r8

movq par6 , %rax
movq %rax , unsigned(%r8)

我尝试打印元素以检查是否一切正常,只有最后一个打印正常,其他两个有一些奇怪的值。

也许这不是我应该声明和使用它的方式?

【问题讨论】:

  • 您使用%r8 作为字节偏移量,而incq %r8 仅将其前移1 个字节,而不是8。您需要缩放索引寻址模式或add $8, %r8。此外,您可以通过将数组放入 .data 而不是 bss 来静态初始化数组,就像从 par4 开始的 3 元素数组一样。或者您可以使用 .equ 常量并使用 mov-immediate 进入内存。这里也不需要增加指针,只需存储到unsignedunsigned+8unsigned+16即可。
  • 另外,如果你想从 C 访问数组,unsigned 将是一个不方便的符号名称选择。unsigned 是一个关键字,所以你不能声明 extern uint64_t unsigned[1000/8]

标签: arrays assembly x86-64 att


【解决方案1】:

首先,unsigned 是 C 中类型的名称,因此对于数组来说这是一个糟糕的选择。让我们改为 arr

您希望将 BSS 中的该空间块视为数组 qword 元素。所以每个元素是8个字节。 所以你需要存储到arr+0arr+8arr+16(你的数组的总大小是10000字节,也就是10000/8个qwords)。

但是您使用%r8 作为字节偏移量,而不是缩放索引。这通常是一件好事,其他一切都一样;在某些情况下,索引寻址模式在某些 CPU 上较慢。但问题是你只能用inc 增加1,而不是用add $8, %r8

所以您实际上是在存储到arr+0arr+1arr+2,其中 8 个字节的存储相互重叠,只留下最后一个的最低有效字节店铺。 x86 是 little-endian,因此最终的内存内容实际上是 this,然后是其余保持为零的未写入字节。

# static array that matches what you actually stored
arr: .byte 500 & 0xFF, 10, 15, 0, 0, 0, 0, 0, 0, 0, ...

您当然可以在.data 部分中使用.qword 来声明一个包含您想要的内容的静态数组。但是只有前 3 个元素非零,将它放在 BSS 中对于一个那么大的元素是有意义的,而不是将操作系统页面从磁盘放在零中。


如果您要完全展开而不是在从 par4 开始的 3 元素 qword 数组上使用循环,则根本不需要递增寄存器。您也不需要将初始化程序放在数据内存中,您可以只使用立即数,因为它们都适合 32 位符号扩展。

  # these are assemble-time constants, not associated with a section
.equ par4, 500
.equ par5, 10
.equ par6, 15

.text  # already the default section but whatever

.globl _start
_start:
    movq    $par4, arr(%rip)            # use RIP-relative addressing when there's no register
    movq    $par5, arr+8(%rip)
    movq    $par6, arr+16(%rip)

    mov $60, %eax
    syscall               # Linux exit(0)

.bss
    arr:   .skip 10000

您可以在 GDB 下运行它并检查内存以查看您得到的结果。 (用gcc -nostdlib -static foo.s 编译它)。在 GDB 中,使用starti 启动程序(在入口点停止),然后使用si 单步执行。使用x /4g &arrarr 的内存内容转储为4 个qwords 的数组。

或者,如果您确实想使用寄存器,不妨只循环指针而不是索引。

    lea     arr(%rip), %rdi           # or mov $arr, %edi in a non-PIE executable
    movq    $par4, (%rdi)
    add     $8, %rdi                  # advance the pointer 8 bytes = 1 element
    movq    $par5, (%rdi)
    add     $8, %rdi
    movq    $par6, (%rdi)

或缩放索引:

## Scaled-index addressing
    movq    $par4, arr(%rip)
    mov     $1, %eax
    movq    $par5, arr(,%rax,8)       # [arr + rax*8]
    inc     %eax
    movq    $par6, arr(,%rax,8)

有趣的技巧:你可以只做一个字节存储而不是一个 qword 存储来设置低字节,其余的保持为零。这将节省代码大小,但如果您立即执行 qword 加载,您将获得存储转发停滞。 (存储/重新加载将缓存中的数据与存储缓冲区中的存储合并的额外延迟约 10 个周期)


或者如果您确实仍想从par4 复制.rodata 中的 24 个字节,则可以使用 SSE。 x86-64 保证 SSE2 可用。

    movaps   par4(%rip), %xmm0
    movaps   %xmm0, arr(%rip)          # copy par4 and par5

    mov      par6(%rip), %rax          # aka par4+16
    mov      %rax, arr+16(%rip)

.section .rodata          # read-only data.
.p2align 4         # align by 2^4 = 16 for movaps
  par4:  .quad 500
  par5:  .quad 10
  par6:  .quad 15

.bss
.p2align 4        # align by 16 for movaps
  arr: .skip 10000
# or use .lcomm arr, 10000  without even switching to .bss

或者使用 SSE4.1,您可以加载+扩展小常量,因此您不需要为每个要复制到 BSS 数组中的小数使用一个完整的 qword。

    movzxwq    initializers(%rip), %xmm0       # zero-extend 2 words into 2 qwords
    movaps     %xmm0, arr(%rip)
    movzwl     initializers+4(%rip), %eax      # zero-extending word load
    mov        %rax, arr+16(%rip)

.section .rodata
  initializers: .word 500, 10, 15

【讨论】:

  • (我找不到一个像样的规范问答来关闭它作为重复,所以我决定写一个,因为问题很短,格式正确,并且没有其他干扰。不过,我知道我已经多次看到这个错误。尤其是在 Intel 语法中,寻址具有与 C 数组索引相同的外观,它确实会缩放索引。)
猜你喜欢
  • 1970-01-01
  • 2016-03-14
  • 2012-01-20
  • 1970-01-01
  • 2019-02-12
  • 1970-01-01
  • 1970-01-01
  • 2019-03-29
  • 1970-01-01
相关资源
最近更新 更多