Структуры

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

GNU ассемблер по умолчанию не поддерживает структуры в виде отдельного типа данных, как это реализовано в более высокоуровневых языках. Тем не менее мы можем эмулировать структуры данных с помощью расположения в памяти набора отдельных данных. Например:

section .data
; условная структура
person:
    db "Alice",10    ; имя
    dq 34           ; возраст

Здесь переменная person представляет два подряд расположенных объекта. Во-первых, это строка. Во-вторых, это 64-разрядное число. Условно будем считать, что эта переменная представляется структуру, которая, в свою очередь, представляет человека. Строка представляет имя, а 64-разрядное число - возраст человека.

Напишем простейшую программу по работе с этой структурой. Допустим, мы хотим выводить на консоль имя и возраст человека:

global _start

section .data
; условная структура
person:
    db "Alice",10    ; имя
    dq 34           ; возраст

; смещение компонентов в структуре
NAME_OFFSET equ 0
AGE_OFFSET equ 6

section .text
_start:
    
    mov rsi, person     ; в RSI - адрес строки
    mov rdi, 1          ; в RDI - дексриптор вывода в стандартный поток (консоль)
    mov rdx, AGE_OFFSET    ; в RDX - длина строки
    mov rax, 1        ; в RAX - номер функции для вывода в поток 
    syscall              ; вызываем функцию Linux

    mov rdi, [rsi + AGE_OFFSET]  ; в RDI - возраст
    mov rax, 60
    syscall 

Важный момент здесь заключается в установке смещений для каждого компонента структуры:

.equ NAME_OFFSET, 0
.equ AGE_OFFSET, 6

С нулевого байта в структуре начинается имя, а с 6-го байта - возраст. И константа AGE_OFFSET хранит смещение возраста в структуре. При этом AGE_OFFSET также представляет количество символов в строке, где символы занимают 1 байт.

Для вывода имени на консоль мы помещаем строку с именем в регистр RSI:

mov rsi, person

Поскольку имя находится по нулевому смещению, то адресом имени в структуре является адрес самой структуры.

Далее в регистр RDI помещается дескриптор консольного вывода - 1:

mov rdi, 1

Затем в регистр RDX помещается количество символов строки - в нашем случае это смещение в структуре компонента возраста - константа AGE_OFFSET:

mov rdx, AGE_OFFSET

Затем вызываем системную функцию Linux номер 1 - функцию write:

mov rax, 1
syscall   

Для простоты возраст выводится в виде статуса результата в регистр RDI:

mov rdi, [rsi + AGE_OFFSET]  ; в RDI - возраст

Поскольку выше в программе в RSI был загружен адрес структуры person, то мы можем применить к нему смещение AGE_OFFSET и получить адрес компоненты возраста в структуре.

Пример компиляции и консольный вывод программы:

eugene@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o
eugene@Eugene:~/asm# ld hello.o -o hello
eugene@Eugene:~/asm# ./hello
Alice
eugene@Eugene:~/asm# echo $?
34
eugene@Eugene:~/asm#

Это доврльно простой способ представления структуры. Но здесь есть недостатки. Если нам потребуется создать набор подобных структур, то в каждом случае у нас могут оказаться имена с разной длиной. Соответственно для каждой отдельной структуры придется прописывать отдельные смещения. При этом компонентов, в том числе строк, в структуре может быть множество. Кроме того, при перемещении по набору структур нам может потребоваться знать точный размер структуры. В условиях, когда имена могут различаться по длине, точный размер каждой структуры также будет отличаться. Мы можем выйти из этой проблемы, вынеся строку из структуру, а в структуру поместив адрес этой строки:

global _start

section .data
; условная структура
person:
    dq alice    ; адрес строки
    dq alice_name_size   ; размер имени
    dq 34      ; возраст

; смещение компонентов в структуре
NAME_OFFSET equ 0
NAME_SIZE_OFFSET equ 8
AGE_OFFSET equ 16

alice db "Alice", 10  ; имя
alice_name_size equ $-alice   ; размер имени

section .text
_start:
    mov rbx, person
    mov rsi, [rbx + NAME_OFFSET]  ; в RSI - адрес строки
    mov rdi, 1        ; в RDI - дексриптор вывода в стандартный поток (консоль)
    mov rdx, [rbx + NAME_SIZE_OFFSET]    ; в RDX - длина строки
    mov rax, 1        ; в RAX - номер функции для вывода в поток 
    syscall              ; вызываем функцию Linux

    mov rdi, [rbx + AGE_OFFSET]  ; в RDI - возраст
    mov rax, 60
    syscall 

Теперь структура представляет набор из трех компонентов - 3 чисел типа .quad. Строка имени вынесена в отдельную переенную alice, а в структуру в качестве первого компонента помещается адрес этой строки. Адреса в х86-64 всегда имеют размер 8 байт, а в качестве адреса служит само название переменной.

; условная структура
person:
    dq alice    ; адрес строки
    dq alice_name_size   ; размер имени
    dq 34      ; возраст

; смещение компонентов в структуре
NAME_OFFSET equ 0
NAME_SIZE_OFFSET equ 8
AGE_OFFSET equ 16

alice db "Alice", 10  ; имя
alice_name_size equ $-alice   ; размер имени

Второй компонент структуры для удобства определяет размер имени, который вычисляется автоматически и сохраняется в константу alice_name_size.

Третий компонент структуры - возраст. В связи с новым строением структуры применяются новые смещения:

NAME_OFFSET equ 0
NAME_SIZE_OFFSET equ 8
AGE_OFFSET equ 16

Использование подобных смещений позволит быстро перейти к определенному компоненту структуры, зная ее адрес:

mov rbx, person
mov rsi, [rbx + NAME_OFFSET]  ; в RSI - адрес строки

mov rdx, [rbx + NAME_SIZE_OFFSET]    ; в RDX - длина строки

mov rdi, [rbx + AGE_OFFSET]  ; в RDI - возраст

Подобный подход упрощает работу с набором структур. Например, определим массив структур-пользователей и выведем на консоль имя каждого пользователя:

global _start

section .data

; массив структур
people:
 dq tom, tom_name_size, 39
 dq sam, sam_name_size, 29
 dq bob, bob_name_size, 43
 dq kate, kate_name_size, 25
 dq alice, alice_name_size, 34
count equ ($ - people)/PERSON_SIZE     ; количество данных в массиве

tom db "Tom",10
tom_name_size equ $-tom
sam db "Sam",10
sam_name_size equ $-sam
bob db "Bob",10
bob_name_size equ $-bob
kate db "Kate",10
kate_name_size equ $-kate
alice db "Alice", 10
alice_name_size equ $-alice

; смещение компонентов в структуре
NAME_OFFSET equ 0
NAME_SIZE_OFFSET equ 8
AGE_OFFSET equ 16

PERSON_SIZE equ 24     ; размер одной структуры в байтах


section .text
_start:
    mov r9, 0
    mov rbx, people
    jmp condition
mainloop:  
    mov rbx, people
    ; получаем смещение для имени и длины имени
    mov rax, PERSON_SIZE
    mul r9
    add rbx, rax
    mov rsi, [rbx + NAME_OFFSET]  ; в RSI - адрес строки
    mov rdi, 1        ; в RDI - дексриптор вывода в стандартный поток (консоль)
    mov rdx, [rbx + NAME_SIZE_OFFSET]    ; в RDX - длина строки
    mov rax, 1        ; в RAX - номер функции для вывода в поток 
    syscall         ; вызываем функцию Linux

    inc r9       ; инкрементируем значение RCX для перехода к новой структуре
condition:    
    cmp r9, count
    jne mainloop

    mov rdi, r9
    mov rax, 60
    syscall 

Здесь для перебора всех элементов структуры применяется счетчик-регистр R9, который хранит индекс текущей структуры в наборе people.

Компиляция и консольный вывод программы:

eugene@Eugene:~/asm# nasm -f elf64 hello.asm -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