Структуры

Последнее обновление: 24.09.2023

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#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850