Объявление и индексирование целочисленного массива qwords в сборке
У меня вопрос, как инициализировать массив в сборке. Я попытался:
.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)
Я попытался распечатать элементы, чтобы проверить, все ли в порядке, и только последний выводится нормально, у двух других есть какие-то странные значения.
Может быть, я не должен так декларировать и работать с этим?
1 ответ
Прежде всего, unsigned
- это имя типа в C, поэтому это плохой выбор для массива. Назовем этоarr
вместо.
Вы хотите рассматривать этот блок пространства в BSS как элементы массива qword. Таким образом, каждый элемент составляет 8 байтов. Значит вам нужно хранить доarr+0
, arr+8
, а также arr+16
. (Общий размер вашего массива составляет 10000 байт, что составляет 10000/8 qwords).
Но вы используете %r8
как байтовое смещение, а не масштабированный индекс. В целом это хорошо, при прочих равных; режимы индексированной адресации в некоторых случаях медленнее на некоторых процессорах. Но проблема в том, что вы увеличиваете его только на1
с участием inc
, не со add $8, %r8
.
Итак, вы на самом деле хранитеarr+0
, arr+1
, а также arr+2
, с 8-байтовыми хранилищами, которые перекрывают друг друга, оставляя только младший байт последнего хранилища. x86 имеет прямой порядок байтов, поэтому результирующее содержимое памяти фактически является этим, а за ним следуют остальные незаписанные байты, которые остаются нулевыми.
# static array that matches what you actually stored
arr: .byte 500 & 0xFF, 10, 15, 0, 0, 0, 0, 0, 0, 0, ...
Конечно, вы могли бы просто использовать .qword
в .data
раздел, чтобы объявить статический массив с желаемым содержимым. Но поскольку только первые 3 элемента не равны нулю, размещение его в BSS имеет смысл для одного такого большого, вместо того, чтобы иметь страницу ОС с нулями с диска.
Если вы собираетесь полностью развернуть вместо того, чтобы использовать цикл над 3-элементным массивом qword, начиная с par4
, вам вообще не нужно увеличивать регистр. Вам также не нужно, чтобы инициализаторы находились в памяти данных, вы можете просто использовать немедленно, потому что все они подходят как 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 &arr
сбросить содержимое памяти на arr
как массив из 4-х слов.
Или, если вы действительно хотите использовать регистр, можно просто зациклить указатель вместо индекса.
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 циклов дополнительной задержки для сохранения / перезагрузки для слияния данных из кеша с хранилищем из буфера хранилища)
Или, если вы все еще хотите скопировать 24 байта изpar4
в .rodata
, вы можете использовать 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 вы можете загружать + расширять небольшие константы, поэтому вам не нужно целое qword для каждого небольшого числа, которое вы собираетесь скопировать в массив BSS.
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