Опубликован: 19.01.2025 | Доступ: свободный | Студентов: 1 / 0 | Длительность: 02:34:00
Лекция 5:

Программирование на языке Assembler

< Лекция 4 || Лекция 5: 12345 || Лекция 6 >

Эти директивы очень важны, когда пишется программа, которая собирается для исполнения внутри некоторого окружения, например, встроенной системы или операционной системы.

Директива .text предоставляет компилятору информацию, что следующий текст является набором машинных инструкций. Директива .data говорит о том, что начинается область с объявлением данных, которые должны быть проинициализированы и могут меняться в процессе выполнения программы. Директива .rodata предназначена для объявления инициализированных данных, доступных только для чтения (константные значения). Директива .bss служит для объявления модифицируемых, но не инициализированных данных. Все эти области памяти могут быть объявлены с помощью директивы .section с последующим указанием типа секции (.text, .data, .rodata и т.д.).

Имя для константного значения может быть задано с помощью директивы equ. Данные для ASCII-строк задаются директивами .ascii, .asciz, и .string. Разница между ними в том, что директивы .asciz и .string добавляют терминальный нулевой байт в конце строки, а директива .ascii - нет. Текст располагается следом за директивной.

Директивы .byte, .half, .word, и .dword служат для задания одного или более числовых значений, которые указываются следом за директивами. Указанные директивы отличаются размером (в байтах) описываемых значений.

Директива .zero объявляет массив байт, которые заполняются нулями, при этом первое число, идущее за директивой .zero задаёт количество таких элементов.

Директива .align выравнивает в памяти данные, следующие за числом, задаваемым как 2 в степени аргумента параметра. Выравнивание достигается путём вставки нулевых байтов.

Директива .globl symbol_name определяет идентификатор symbol_name, который обычно задаётся как значение, видимое компоновщика. Символ _start требуется для задания точки входа в программу. В данном руководстве мы не рассматриваем синтаксис скриптов компоновщика, просто используем скрипт по умолчанию, который поставляется вместе с набором инструментов сборки Linux Gnu toolchain. Тем не менее, скрипты компоновщика очень важны, когда идёт разработка для различных окружений или встраиваемых систем, поскольку такие скрипты могут определять расположение программы в памяти устройств.

Также используется такая конструкция, как точка (.), которая подменяет текущий адрес.

Пример на директивы языка Assemler

Рассмотрим пример. Символ решётка '#' задаёт комментарий:

# define exit as 93
.equ exit, 93
# program code
.section .text
# export _start for linker
.globl  _start
_start:
        li      a7, exit
        ecall
# data: init one word (16-bit value) with 1 and read/write
.section .data
counter:
.word 1
# rodata: constant text string
.section .rodata
text_begin:
.asciz  "Text"
text_end:
# current address minus address of text_begin = length of text
.byte .-text_begin
# non initialized block with same size as the text
.section .bss 
# start next part by address aligned to multiple of 2^2 = 4
.align 2
copy_begin:
.zero text_end-text_begin

Если этот пример сохранить в файле с названием example.s, то его можно будет собрать в исполняемый модуль в формате ELF (Executable and Linkable Format, ELF), и проанализировать результат сборки и компоновки с помощью утилиты objdump:

riscv64-linux-gnu-as -o example.o example.s 
riscv64-linux-gnu-ld -o example example.o
riscv64-linux-gnu-objdump -f -d -Mno-aliases,numeric example

example:     file format elf64-littleriscv
architecture: riscv:rv64, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00000000000100e8

Disassembly of section .text:

00000000000100e8 <_start>:
   100e8:	05d00893          	addi	x17,x0,93
   100ec:	00000073          	ecall

Команда objdump с ключами -f -d показывает дизассемблированный файл (-d) с заголовком файла (-f). Опции, указанные после ключа -Mno-aliases,numeric говорят, что утилита objdump должна печатать только базовые инструкции и не выводить псевдоинструкции и печатать номера регистров, а не их имена в синтаксисе ABI (что будет рассмотрено позже). В листинге показан только программный код, содержащийся в секции.text. После указания ключа -t , утилита objdump печатает все секции исполняемого файла:

riscv64-linux-gnu-objdump -t example

example:     file format elf64-littleriscv
SYMBOL TABLE:
00000000000100e8 l    d  .text	0000000000000000 .text
00000000000100f0 l    d  .rodata	0000000000000000 .rodata
00000000000110f6 l    d  .data	0000000000000000 .data
00000000000110fc l    d  .bss	0000000000000000 .bss
0000000000000000 l    d  .riscv.attributes	0000000000000000 .riscv.attributes
0000000000000000 l    df *ABS*	0000000000000000 example.o
000000000000005d l       *ABS*	0000000000000000 exit
00000000000100f6 l       .data	0000000000000000 counter
00000000000100f0 l       .rodata	0000000000000000 text_begin
00000000000100f5 l       .rodata	0000000000000000 text_end
00000000000110fc l       .bss	0000000000000000 copy_begin
00000000000118f6 g       *ABS*	0000000000000000 __global_pointer$
00000000000110fa g       .data	0000000000000000 __SDATA_BEGIN__
00000000000100e8 g       .text	0000000000000000 _start
0000000000011108 g       .bss	0000000000000000 __BSS_END__
00000000000110fa g       .bss	0000000000000000 __bss_start
00000000000110f6 g       .data	0000000000000000 __DATA_BEGIN__
00000000000110fa g       .data	0000000000000000 _edata
0000000000011108 g       .bss	0000000000000000 _end

Секция .section .text начинается с адреса 0x100e8, секция .rodata - с адреса 0x100f0, секция .data - с адреса 0x110f6, и секция .bss - с адреса 0x110fc. Каждой метке присваиваются адреса, то есть метка copy_begin располагается в секции .bss по адресу 0x110fc.

В итоге можно просмотреть содержимое бинарного файла в шестнадцатеричном формате, задав утилите objdump ключи -F -s, где ключ -F нужен для вывода дополнительной информации о файле:

riscv64-linux-gnu-objdump -F -s example

example:     file format elf64-littleriscv

Contents of section .text:  (Starting at file offset: 0xe8)
 100e8 9308d005 73000000                    ....s...        
Contents of section .rodata:  (Starting at file offset: 0xf0)
 100f0 54657874 0005                        Text..          
Contents of section .data:  (Starting at file offset: 0xf6)
 110f6 01000000                             ....            
Contents of section .riscv.attributes:  (Starting at file offset: 0xfa)
 0000 412d0000 00726973 63760001 23000000  A-...riscv..#...
 0010 05727636 34693270 305f6d32 70305f61  .rv64i2p0_m2p0_a
 0020 3270305f 66327030 5f643270 3000      2p0_f2p0_d2p0.  

Первые два двойных слова в секции .text являются представлением машинного кода программы. Следует обратить внимание, что в выводе дизассемблера порядок байт инвертирован, то есть написано 9308d005 вместо 05d00893. Показанное содержимое отображает то, как данные хранятся в памяти (или в файле), а не то, как 32-битное значение или инструкция составлены в формате RISC-V little endian. В формате представления little endian 32-битная инструкция составляется таким образом, что младшие адреса занимают старшие байты, а старшие адреса - младшие байты. Таким образом, четыре байта со значениями 0x93 0x08 0xd0 0x05 , расположенные по адресу 0x100e8, берутся и размещаются в 32-битное представление как 0x05d00893. Напротив, формат Big endian предписывает иной порядок следования байт в памяти.

Секция .rodata начинается после двух 32-битных инструкций по адресу 0x100f0 (что соответствует 8-ми байтному смещению от начала программы: 0x100e8 + 0x8) и содержит байты 0x54657874 0x0005. Значения 0x54 0x65 0x78 0x74 являются побайтовым представлением в таблице ASCII-символов четырёх символов: 'T' 'e' 'x' 't'. Они заканчиваются нулевым байтом (0x00) и байтом длины, равным 0x05 - это длина текста включая терминальный ноль. Затем следует секция .data, которая содержит 32-битное представление числа 1, которое кодируется в формате Little Endian. Следующая секция описывает атрибуты RISC-V.

Секция .bss не содержит данных, поэтому её содержимое не отображается. Тем не менее в таблице символов было видно, что секция .bss начинается с адреса 0x110fc, который является следующим адресом за адресом секции .data (заканчивается по адресу 0x110fa) с добавлением двух байт для выравнивания в памяти до 4 байт.

В итоге мы получили представление о том, как компилятор и компоновщик располагают данные в формате ELF.

< Лекция 4 || Лекция 5: 12345 || Лекция 6 >