GNU ассемблер по умолчанию не поддерживает структуры в виде отдельного типа данных, как это реализовано в более высокоуровневых языках. Тем не менее мы можем эмулировать структуры данных с помощью расположения в памяти набора отдельных данных. Например:
.data # условная структура person: .ascii "Alice\n" # имя .quad 34 # возраст
Здесь переменная person представляет два подряд расположенных объекта. Во-первых, это строка. Во-вторых, это 64-разрядное число. Условно будем считать, что эта переменная представляется структуру, которая, в свою очередь, представляет человека. Строка представляет имя, а 64-разрядное число - возраст человека.
Напишем простейшую программу по работе с этой структурой. Допустим, мы хотим выводить на консоль имя и возраст человека:
.globl _start .data # условная структура person: .ascii "Alice\n" # имя .quad 34 # возраст # смещение компонентов в структуре .equ NAME_OFFSET, 0 .equ AGE_OFFSET, 6 .text _start: movq $person, %rsi # в RSI - адрес строки movq $1, %rdi # в RDI - дексриптор вывода в стандартный поток (консоль) movq $AGE_OFFSET, %rdx # в RDX - длина строки movq $1, %rax # в RAX - номер функции для вывода в поток syscall # вызываем функцию Linux movq AGE_OFFSET(%rsi), %rdi # в RDI - возраст movq $60, %rax syscall
Важный момент здесь заключается в установке смещений для каждого компонента структуры:
.equ NAME_OFFSET, 0 .equ AGE_OFFSET, 6
С нулевого байта в структуре начинается имя, а с 6-го байта - возраст. И константа AGE_OFFSET хранит смещение возраста в структуре. При этом AGE_OFFSET также представляет количество символов в строке, где символы занимают 1 байт.
Для вывода имени на консоль мы помещаем строку с именем в регистр RSI:
movq $person, %rsi
Поскольку имя находится по нулевому смещению, то адресом имени в структуре является адрес самой структуры.
Далее в регистр RDI помещается дескриптор консольного вывода - 1:
movq $1, %rdi
Затем в регистр RDX помещается количество символов строки - в нашем случае это смещение в структуре компонента возраста - константа AGE_OFFSET:
movq $AGE_OFFSET, %rdx
Затем вызываем системную функцию Linux номер 1 - функцию write:
movq $1, %rax syscall
Для простоты возраст выводится в виде статуса результата в регистр RDI:
movq AGE_OFFSET(%rsi), %rdi # в RDI - возраст
Поскольку выше в программе в RSI был загружен адрес структуры person, то мы можем применить к нему смещение AGE_OFFSET и получить адрес компоненты возраста в структуре.
Пример компиляции и консольный вывод программы:
eugene@Eugene:~/asm# as hello.s -o hello.o eugene@Eugene:~/asm# ld hello.o -o hello eugene@Eugene:~/asm# ./hello Alice eugene@Eugene:~/asm# echo $? 34 eugene@Eugene:~/asm#
Это доврльно простой способ представления структуры. Но здесь есть недостатки. Если нам потребуется создать набор подобных структур, то в каждом случае у нас могут оказаться имена с разной длиной. Соответственно для каждой отдельной структуры придется прописывать отдельные смещения. При этом компонентов, в том числе строк, в структуре может быть множество. Кроме того, при перемещении по набору структур нам может потребоваться знать точный размер структуры. В условиях, когда имена могут различаться по длине, точный размер каждой структуры также будет отличаться. Мы можем выйти из этой проблемы, вынеся строку из структуру, а в структуру поместив адрес этой строки:
.globl _start .data # условная структура person: .quad alice # адрес строки .quad alice_name_size # размер имени .quad 34 # возраст # смещение компонентов в структуре .equ NAME_OFFSET, 0 .equ NAME_SIZE_OFFSET, 8 .equ AGE_OFFSET, 16 alice: .ascii "Alice\n" # имя alice_name_size= .-alice # размер имени .text _start: movq $person, %rbx movq NAME_OFFSET(%rbx), %rsi # в RSI - адрес строки movq $1, %rdi # в RDI - дексриптор вывода в стандартный поток (консоль) movq NAME_SIZE_OFFSET(%rbx), %rdx # в RDX - длина строки movq $1, %rax # в RAX - номер функции для вывода в поток syscall # вызываем функцию Linux movq AGE_OFFSET(%rbx), %rdi # в RDI - возраст movq $60, %rax syscall
Теперь структура представляет набор из трех компонентов - 3 чисел типа .quad
. Строка имени вынесена в отдельную переенную alice, а в структуру в качестве первого компонента помещается
адрес этой строки. Адреса в х86-64 всегда имеют размер 8 байт, а в качестве адреса служит само название переменной.
person: .quad alice # адрес строки .quad alice_name_size # размер имени .quad 34 # возраст alice: .ascii "Alice\n" # имя alice_name_size= .-alice # размер имени
Второй компонент структуры для удобства определяет размер имени, который вычисляется автоматически и сохраняется в константу alice_name_size.
Третий компонент структуры - возраст. В связи с новым строением структуры применяются новые смещения:
.equ NAME_OFFSET, 0 .equ NAME_SIZE_OFFSET, 8 .equ AGE_OFFSET, 16
Использование подобных смещений позволит быстро перейти к определенному компоненту структуры, зная ее адрес:
movq $person, %rbx movq NAME_OFFSET(%rbx), %rsi # в RSI - адрес строки movq NAME_SIZE_OFFSET(%rbx), %rdx # в RDX - длина строки movq AGE_OFFSET(%rbx), %rdi # в RDI - возраст
Подобный подход упрощает работу с набором структур. Например, определим массив структур-пользователей и выведем на консоль имя каждого пользователя:
.globl _start .data # массив структур people: .quad tom, tom_name_size, 39 .quad sam, sam_name_size, 29 .quad bob, bob_name_size, 43 .quad kate, kate_name_size, 25 .quad alice, alice_name_size, 34 count= (. - people)/PERSON_SIZE # количество данных в массиве tom: .ascii "Tom\n" tom_name_size=.-tom sam: .ascii "Sam\n" sam_name_size=.-sam bob: .ascii "Bob\n" bob_name_size=.-bob kate: .ascii "Kate\n" kate_name_size=.-kate alice: .ascii "Alice\n" alice_name_size=.-alice # смещение компонентов в структуре .equ NAME_OFFSET, 0 .equ NAME_SIZE_OFFSET, 8 .equ AGE_OFFSET, 16 .equ PERSON_SIZE, 24 # размер одной структуры в байтах .text _start: movq $0, %r9 movq $people, %rbx jmp condition mainloop: movq $people, %rbx # получаем смещение для имени и длины имени movq $PERSON_SIZE, %rax mulq %r9 addq %rax, %rbx movq NAME_OFFSET(%rbx), %rsi # в RSI - адрес строки movq $1, %rdi # в RDI - дексриптор вывода в стандартный поток (консоль) movq NAME_SIZE_OFFSET(%rbx), %rdx # в RDX - длина строки movq $1, %rax # в RAX - номер функции для вывода в поток syscall # вызываем функцию Linux incq %r9 # инкрементируем значение RCX для перехода к новой структуре condition: cmpq $count, %r9 jne mainloop movq %r9, %rdi movq $60, %rax syscall
Здесь для перебора всех элементов структуры применяется счетчик-регистр R9, который хранит индекс текущей структуры в наборе people.
Компиляция и консольный вывод программы:
eugene@Eugene:~/asm# as hello.s -o hello.o eugene@Eugene:~/asm# ld hello.o -o hello eugene@Eugene:~/asm# ./hello Tom Sam Bob Kate Alice eugene@Eugene:~/asm#