- PVSM.RU - https://www.pvsm.ru -
С недавнего времени я увлекся микроконтроллерами. Сначала AVR, затем ARM. Для программирования микроконтроллеров существует два основных варианта: ассемблер и С. Однако, я фанат языка программирования Форт [1] и занялся портированием его на эти микроконтроллеры. Конечно, существуют и готовые решения, но ни в одном из них не было того, что я хотел: отладки с помощью gdb. И я задался целью заполнить этот пробел (пока только для ARM). В моем распоряжении была плата stm32vldiscovery [2] с 32-битным процессором ARM Cortex-M3, 128кБ flash и 8 кБ RAM, поэтому я и начал с нее.
Писал я кросс-транслятор Форта конечно на Форте, и кода в статье не будет, так как этот язык считается экзотическим. Ограничусь достаточно подробными рекомендациями. Документации и примеров в сети по предмету почти нет, некоторые параметры подбирались мной путем проб и ошибок, некоторые — путем анализа выходных файлов компилятора gcc. Кроме того, я использовал только необходимый минимум отладочной информации, не касаясь, например, relocation-ов и множества других вещей. Тема очень обширна и, признаюсь, разобрался я с ней только процентов на 30, что оказалось для меня достаточным.
Кого заинтересует этот проект, может скачать код здесь [3].
Стандартные средства разработки компилируют вашу программу в файл ELF (Executable and Linkable Format) [4] с возможностью включения отладочной информации. Спецификацию формата можно прочитать здесь [5]. Кроме того, для каждой архитектуры имеются свои особенности, например особенности ARM [6]. Рассмотрим кратко этот формат.
Исполняемый файл формата ELF состоит из таких частей:
Содержит общую информацию о файле и его основные характеристики.
Это таблица соответствия секций файла сегментам памяти, указывает загрузчику, в какую область памяти писать каждую секцию.
Секции содержат всю информацию в файле (программа, данные, отладочная информация и т.д)
У каждой секции есть тип, имя и другие параметры. В секции ".text" обычно хранится код, в ".symtab" — таблица символов программы (имена файлов, процедур и переменных), в ".strtab" — таблица строк, в секциях с префиксом ".debug_" — отладочная информация и т.д. Кроме того, в файле должна обязательно быть пустая секция с индексом 0.
Это таблица, содержащая массив заголовков секций.
Более подробно формат рассматривается в разделе Создание ELF.
DWARF [7] — это стандартизованный формат отладочной информации. Стандарт [8] можно скачать на официальном сайте [9]. Там же лежит замечательное краткое обозрение формата: Introduction to the DWARF Debugging Format [10] (Michael J. Eager).
Зачем нужна отладочная информация? Она позволяет:
Эта информация хранится в виде древовидной структуры. Каждый узел дерева имеет родителя, может иметь потомков и называется DIE (Debugging Information Entry). Каждый узел имеет свой тэг (тип) и список атрибутов (свойств), описывающих узел. Атрибуты могут содержать все, что угодно, например, данные или ссылки на другие узлы. Кроме того, существует информация, хранящаяся вне дерева.
Узлы делятся на два основных типа: узлы, описывающие данные, и узлы, описывающие код.
Каждый объект данных имеет атрибут DW_AT_location, который указывает, как вычисляется адрес, по которому находится данные. Например переменная может иметь фиксированный адрес, находиться в регистре или на стеке, быть членом класса или объекта. Этот адрес может вычисляться довольно сложным образом, поэтому стандарт предусматривает так называемые Location Expressions, которые могут содержать последовательность операторов специальной внутренней стековой машины.
Информация, описанная выше, находится в секциях ".debug_info" и ".debug_abbrev".
Создавать файлы в формате EFL мы будем при помощи библиотеки libelf из пакета elfutils [11]. В сети есть хорошая статья по использованию libelf — LibELF by Example [12] (к сожалению, созданию файлов в ней описано очень кратко) а также документация [13].
Создание файла состоит из нескольких этапов:
Рассмотрим этапы подробнее
Сначала вам нужно будет вызвать функцию elf_version(EV_CURRENT) и проверить результат. Если он равен EV_NONE — возникла ошибка и дальнейшие действия производить нельзя. Затем нужно создать нужный нам файл на диске, получить его дескриптор и передать его в функцию elf_begin:
Elf * elf_begin(
int fd,
Elf_Cmd cmd,
Elf *elf)
Функция возвращает указатель на созданный дескриптор, который будет использоваться во всех функциях libelf, 0 возвращается в случае ошибки.
Новый заголовок файла создается функцией elf32_newehdr:
Elf32_Ehdr * elf32_newehdr(
Elf *elf);
Возвращает 0 при ошибке или указатель на структуру — заголовок ELF-файла:
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
Некоторые поля ее заполнены стандартным образом, некоторые нужно заполнить нам:
Как уже говорилось, заголовок программы (Program Header Table) — это таблица соответствия секций файла сегментам памяти, которая указывает загрузчику, куда писать каждую секцию. Загоовок создается создаются с помощью функции elf32_newphdr:
Elf32_Phdr * elf32_newphdr(
Elf *elf,
size_t count);
Возвращает 0 при ошибке или указатель на заголовок программы.
Каждая элемент в таблице заголовка описывается такой структурой:
typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
size_t elf32_fsize(Elf_Type type, size_t count, unsigned int version);
type — здесь константа ELF_T_ххх, нам нужны будут размеры ELF_T_EHDR и ELF_T_PHDR; count — количество элементов нужного типа, version — нужно установить в EV_CURRENT
После создания заголовков можно приступать к созданию секций. Пустая секция создается при помощи функции elf_newscn:
Elf_Scn * elf_newscn(
Elf *elf);
Функция возвращает указатель на секцию или 0 при ошибке.
После создания секции нужно заполнить заголовок секции и создать описатель данных секции.
Указатель на заголовок секции мы можем получить при помощи функции elf32_getshdr:
Elf32_Shdr * elf32_getshdr(
Elf_Scn *scn);
Заголовок секции выглядит так:
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
Соответственно, для секции .text с программой нужно установить флаги SHF_ALLOC + SHF_EXECINSTR
После заполнения заголовка нужно создать описатель данных секции функцией elf_newdata:
Elf_Data * elf_newdata(
Elf_Scn *scn);
Функция возвращает 0 при ошибке, или указатель на структуру Elf_Data, которую нужно будет заполнить:
typedef struct {
void* d_buf;
Elf_Type d_type;
size_t d_size;
off_t d_off;
size_t d_align;
unsigned d_version;
} Elf_Data;
Для наших целей нам нужно будет создать минимально необходимый набор секций:
Все секции создаются так, как описано в предыдущем разделе, но у каждой специальной секции есть свои особенности.
Эта секция содержит исполняемый код, поэтому нужно sh_type установить в SHT_PROGBITS, sh_flags — в SHF_EXECINSTR + SHF_ALLOC, sh_addr — установить равным адресу, по которому будет загружен этот код
Секция содержит описание всех символов (функций) программы и файлов, в которых они были описаны. Она состоит из таких элементов длиной по 16 байт:
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
#define ELF32_ST_INFO(b,t) (((b)<<4)+((t)&0xf))
где b — область видимости, а t — тип символа
Область видимости может быть STB_LOCAL (символ не виден из других объектных файлов) или STB_GLOBAL (виден). Для упрощения используем STB_GLOBAL.
Тип символа — STT_FUNC для функции, STT_FILE для файла
size_t elf_ndxscn(
Elf_Scn *scn);
Данные для секции можно собирать при проходе по исходному тексту в массив, указатель на который затем записать в описатель данных секции (d_buf).
Эта секция создается обычным образом, только sh_type нужно установить в SHT_SYMTAB, а индекс секции .strtab записать в поле sh_link, таким образом эти секции станут связанными.
В этой секции находятся имена всех символов из секции .symtab. Создается как обычная секция, но sh_type нужно установить в SHT_STRTAB, sh_flags — в SHF_STRINGS, таким образом эта секция становится таблицей строк.
Данные для секции можно собирать при проходе по исходному тексту в массив, указатель на который затем записать в описатель данных секции (d_buf).
Секция — таблица строк, содержит заголовки всех секций файла, в том числе и свой заголовок. Создается так же, как и секция .strtab. После создания ее индекс нужно записать в поле e_shstrndx заголовка файла.
Таблицы строк содержат идущие подряд строки, оканчивающиеся нулевым байтом, первый байт в этой таблице должен быть также 0. Индекс строки в таблице — это просто смещение в байтах от начала таблицы, таким образом, первая строка 'name' имеет индекс 1, следующая строка 'var' имеет индекс 6.
Индекс 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
n a m e v a r
Итак, заголовки и секции уже сформированы, теперь их нужно записать в файл и завершить работу с libelf. Запись производит функция elf_update:
off_t elf_update(
Elf *elf,
Elf_Cmd cmd);
Функция возвращает -1 при ошибке. Текст ошибки можно получить вызвав функцию elf_errmsg(-1), которая возвратит указатель на строку с ошибкой.
Заканчиваем работу с библиотекой функцией elf_end, которой передаем наш дескриптор. Осталось только закрыть ранее открытый файл.
Однако наш созданный файл не содержит отладочной информации, которую мы добавим в следующем разделе.
Создавать отладочную информацию будем с помощью библиотеки libdwarf [14], в комплекте с которой идет pdf-файл с документацией (libdwarf2p.1.pdf — A Producer Library Interface to DWARF).
Создание отладочной информации состоит из таких этапов:
Рассмотрим этапы подробнее
Мы будем создавать отладочную информацию во время компиляции одновременно с созданием символов в секции .symtab, поэтому инициализацию библиотеки нужно осуществлять после инициализации libelf, создания ELF-заголовка и заголовка программы, до создания секций.
Для инициализации будем использовать функцию dwarf_producer_init_c. В библиотеке есть еще несколько функций инициализации (dwarf_producer_init, dwarf_producer_init_b), которые различаются некоторыми нюансами, описанными в документации. В принципе, можно использовать любую из них.
Dwarf_P_Debug dwarf_producer_init_c(
Dwarf_Unsigned flags,
Dwarf_Callback_Func_c func,
Dwarf_Handler errhand,
Dwarf_Ptr errarg,
void * user_data,
Dwarf_Error *error)
Функция возвращает Dwarf_P_Debug — дескриптор, используемый во всех последующих функциях, или -1 в случае ошибки, при этом в error будет код ошибки (получить текст сообщения об ошибке по его коду можно при помощи функции dwarf_errmsg, передав ей этот код)
Как было описано выше, отладочная информация образует древовидную структуру. Для того, чтобы создать узел этого дерева, нужно:
Узел создается при помощи функции dwarf_new_die:
Dwarf_P_Die dwarf_new_die(
Dwarf_P_Debug dbg,
Dwarf_Tag new_tag,
Dwarf_P_Die parent,
Dwarf_P_Die child,
Dwarf_P_Die left_sibling,
Dwarf_P_Die right_sibling,
Dwarf_Error *error)
Функция возвращает DW_DLV_BADADDR при ошибке или дескриптор узла Dwarf_P_Die в случае успеха
Для создания атрибутов узла есть целое семейство функций dwarf_add_AT_хххх. Иногда проблематично определить, какой функцией нужно создавать необходимый атрибут, так что я даже несколько раз копался в исходном коде библиотеки. Некоторые из функций будут описаны здесь, некоторые ниже — в соответствующих разделах. Все они принимают параметр ownerdie — дескриптор узла, к которому будет добавлен атрибут, и возвращают код ошибки в параметре error.
Функция dwarf_add_AT_name добавляет к узлу атрибут «имя» (DW_AT_name). У большинства узлов должно быть имя (например у процедур, переменных, констант), у некоторых имени может и не быть (например у Compilation Unit)
Dwarf_P_Attribute dwarf_add_AT_name(
Dwarf_P_Die ownerdie,
char *name,
Dwarf_Error *error)
Возвращает DW_DLV_BADADDR в случае ошибки или дескриптор атрибута при успешном завершении.
Функции dwarf_add_AT_signed_const, dwarf_add_AT_unsigned_const добавляют к узлу указанный атрибут и его знаковое (беззнаковое) значение. Знаковые и беззнаковые атрибуты используются для задания значений констант, размеров, номеров строк и т.д. Формат функций:
Dwarf_P_Attribute dwarf_add_AT_(un)signed_const(
Dwarf_P_Debug dbg,
Dwarf_P_Die ownerdie,
Dwarf_Half attr,
Dwarf_Signed value,
Dwarf_Error *error)
Возвращают DW_DLV_BADADDR в случае ошибки или дескриптор атрибута при успешном завершении.
В любом дереве должен быть корень — у нас это единица компиляции, которая содержит информацию о программе (например, имя главного файла, используемый язык программирования, название компилятора, чувствительность символов (переменных, функций) к регистру, главную функцию программы, начальный адрес и.т.д). В принципе, никакие атрибуты не являются обязательными. Для примера создадим информацию о главном файле и компиляторе.
Для хранения информации о главном файле используется атрибут «имя» (DW_AT_name), применяйте функцию dwarf_add_AT_name, как показано в разделе «Создание атрибутов узла».
Используем функцию dwarf_add_AT_producer:
Dwarf_P_Attribute dwarf_add_AT_name(
Dwarf_P_Die ownerdie,
char *producer_string,
Dwarf_Error *error)
Возвращает DW_DLV_BADADDR в случае ошибки или дескриптор атрибута при успешном завершении.
Обычно при вызове функции (подпрограммы) ее параметры и адрес возврата помещается в стек (хотя каждый компилятор может делать это по-своему), все это называется Call Frame. Отладчику нужна информация о формате фрейма чтобы правильно определить адрес возврата из функции и построить backtrace — цепочку вызовов функций, которая привела нас в текущую функцию, и параметры этих функций. Также обычно указываются регистры процессора, которые сохраняются на стеке. Код, который резервирует место на стеке и сохраняет регистры процессора, называется прологом функции, код, восстанавливающий регистры и стек — эпилогом.
Эта информация сильно зависит от компилятора. Например, пролог и эпилог необязательно должны быть в самом начале и конце функции; иногда фрейм используется, иногда нет; регистры процессора могут сохраняться в других регистрах и т.д.
Итак, отладчику нужно знать, как меняют свое значение регистры процессора и где они будут сохранены при входе в процедуру. Эта информация называется Call Frame Information — информация о формате фрейма. Для каждого адреса в программе (содержащего код) указывается адрес фрейма в памяти (Canonical Frame Address — CFA) и информация о регистрах процессора, к примеру можно указать, что:
Поскольку информация должна указываться для каждого адреса в коде, она очень объемна и сохраняется в сжатом виде в секции .debug_frame. Так как от адреса к адресу она изменяется мало, то кодируются только ее изменения в виде инструкций DW_CFA_хххх. Каждая инструкция указывает на одно изменение, например:
Элементы секции .debug_frame — это записи, которые могут быть двух типов: Common Information Entry (CIE) и Frame Description Entry (FDE). CIE содержит информацию, которая является общей для многих записей FDE, грубо говоря она описывает определенный тип процедур. FDE же описывают каждую конкретную процедуру. При входе в процедуру отладчик сначала выполняет инструкции из CIE, а затем из FDE.
Мой компилятор создает процедуры, в которых CFA находится в регистре sp (r13). Создадим CIE для всех процедур. Для этого есть функция dwarf_add_frame_cie:
Dwarf_Unsigned dwarf_add_frame_cie(
Dwarf_P_Debug dbg,
char *augmenter,
Dwarf_Small code_align,
Dwarf_Small data_align,
Dwarf_Small ret_addr_reg,
Dwarf_Ptr init_bytes,
Dwarf_Unsigned init_bytes_len,
Dwarf_Error *error);
Функция возвращает DW_DLV_NOCOUNT при ошибке или дескриптор CIE, который должен быть использован при создании FDE для каждой процедуры, что мы рассмотрим далее в разделе «Создание FDE процедуры»
Перед тем, как создавать процедуры и переменные, нужно сначала создать узлы, соответствующие типам данных. Типов данных существует множество, но все они основываются на базовых типах (элементарные типы вроде int, double и т.д), остальные типы строятся из базовых.
Базовый тип — это узел с тэгом DW_TAG_base_type. У него должны быть атрибуты:
Также узел может содержать другие необязательные атрибуты.
Например, чтобы создать 32-битный целый знаковый базовый тип «int», нам нужно будет создать узел с тэгом DW_TAG_base_type и установить ему атрибуты DW_AT_name — «int», DW_AT_encoding — DW_ATE_signed, DW_AT_byte_size — 4.
После создания базовых типов можно создавать производные от них. Такие узлы должны содержать атрибут DW_AT_type — ссылку на их базовый тип. Например указатель на int — узел с тэгом DW_TAG_pointer_type должен содержать в атрибуте DW_AT_type ссылку на ранее созданный тип «int».
Атрибут со ссылкой на другой узел создается функцией dwarf_add_AT_reference:
Dwarf_P_Attribute dwarf_add_AT_reference(
Dwarf_P_Debug dbg,
Dwarf_P_Die ownerdie,
Dwarf_Half attr,
Dwarf_P_Die otherdie,
Dwarf_Error *error)
Для создания процедур мне необходимо пояснить еще один тип отладочной информации — информация о номерах строк (Line Number Information). Она служит для сопоставления каждой машинной инструкции определенной строке исходного кода а также для возможности построковой отладки программы. Эта информация хранится в секции .debug_line. Если бы у нас было достаточно места, то она хранилась бы в виде матрицы, по одной строке для каждой инструкции с такими колонками:
Такая матрица была бы очень большая, поэтому ее приходится сжимать. Во-первых, дублирующиеся строки удаляются, и во-вторых, сохраняются не сами строки, а только изменения в них. Эти изменения выглядят как команды для конечного автомата, а сама информация уже считается программой, которая будет «исполняться» этим автоматом. Команды этой программы выглядят, например так: DW_LNS_advance_pc — продвинуть счетчик команд в некоторый адрес, DW_LNS_set_file — установить файл, в котором определена процедура, DW_LNS_const_add_pc — продвинуть счетчик команд на несколько байт и т.д.
На таком низком уровне создавать эту информацию сложно, поэтому в библиотеке libdwarf предусмотрено несколько функций, облегчающие эту задачу.
Хранить имя файла для каждой инструкции накладно, поэтому вместо имени хранится его индекс в специальной таблице. Для создания индекса файла нужно использовать функцию dwarf_add_file_decl:
Dwarf_Unsigned dwarf_add_file_decl(
Dwarf_P_Debug dbg,
char *name,
Dwarf_Unsigned dir_idx,
Dwarf_Unsigned time_mod,
Dwarf_Unsigned length,
Dwarf_Error *error)
Функия вернет индекс файла или DW_DLV_NOCOUNT при ошибке.
Для создания информации о номерах строк есть три функции dwarf_add_line_entry_b, dwarf_lne_set_address, dwarf_lne_end_sequence, которые мы рассмотрим ниже.
Создание отладочной информации для процедуры проходит в несколько этапов:
Символ процедуры создается как описано выше в разделе «Секция .symtab». В ней симолы процедур перемежаются с символами файлов в которых находится исходный код этих процедур. Сначала создаем символ файла, затем процедуры. При этом файл становится текущим, и если следующая процедура находится в текущем файле, символ файла опять создавать не нужно.
Сначала создаем узел с помощью функции dwarf_new_die (см. раздел «Создание Узлов»), указав в качестве тега DW_TAG_subprogram, а в качестве родителя — Compilation Unit (если это глобальная процедура) или соответствующий DIE (если локальная). Далее создаем атрибуты:
Атрибуты DW_AT_low_pc и DW_AT_high_pc нужно создавать специально предназначенной для этого функцией dwarf_add_AT_targ_address_b:
Dwarf_P_Attribute dwarf_add_AT_targ_address_b(
Dwarf_P_Debug dbg,
Dwarf_P_Die ownerdie,
Dwarf_Half attr,
Dwarf_Unsigned pc_value,
Dwarf_Unsigned sym_index,
Dwarf_Error *error)
Функция вернет DW_DLV_BADADDR при ошибке.
Как говорилось выше в разделе «Создание Common Information Entry», для каждой процедуры нужно создать описатель фрейма, что происходит в несколько этапов:
Cоздать новый FDE можно функцией dwarf_new_fde:
Dwarf_P_Fde dwarf_new_fde(
Dwarf_P_Debug dbg,
Dwarf_Error *error)
Функция вернет дескриптор нового FDE или DW_DLV_BADADDR при ошибке.
Присоединить новый FDE к списку можно при помощи dwarf_add_frame_fde:
Dwarf_Unsigned dwarf_add_frame_fde(
Dwarf_P_Debug dbg,
Dwarf_P_Fde fde,
Dwarf_P_Die die,
Dwarf_Unsigned cie,
Dwarf_Addr virt_addr,
Dwarf_Unsigned code_len,
Dwarf_Unsigned sym_idx,
Dwarf_Error* error)
Функция вернет DW_DLV_NOCOUNT при ошибке.
После всего этого можно добавлять инструкции DW_CFA_хххх к нашему FDE. Делается это функциями dwarf_add_fde_inst и dwarf_fde_cfa_offset. Первая добавляет к списку заданную инструкцию:
Dwarf_P_Fde dwarf_add_fde_inst(
Dwarf_P_Fde fde,
Dwarf_Small op,
Dwarf_Unsigned val1,
Dwarf_Unsigned val2,
Dwarf_Error *error)
Функция dwarf_fde_cfa_offset добавляет инструкцию DW_CFA_offset:
Dwarf_P_Fde dwarf_fde_cfa_offset(
Dwarf_P_Fde fde,
Dwarf_Unsigned reg,
Dwarf_Signed offset,
Dwarf_Error *error)
Например, компилятор создает процедуру, в прологе которой в стековый фрейм сохраняется регистр lr (r14). Первым делом нужно добавить инструкцию DW_CFA_advance_loc с первым параметром, равным 1, что значит продвижение регистра pc на 2 байта (см. Создание Common Information Entry, code_align), затем добавить DW_CFA_def_cfa_offset с параметром 4 (задание смещения данных во фрейме на 4 байта) и вызвать функцию dwarf_fde_cfa_offset с параметром reg=14 offset=1, что означает запись регистра r14 в фрейм со смещением -4 байта от CFA.
Создание параметров процедуры аналогично созданию обычных переменных, см. «Создание переменных и констант»
Создание этой информации происходит так:
Функция dwarf_lne_set_address задает адрес, по которому начинается блок инструкций:
Dwarf_Unsigned dwarf_lne_set_address(
Dwarf_P_Debug dbg,
Dwarf_Addr offs,
Dwarf_Unsigned symidx,
Dwarf_Error *error)
Возвращает 0 (успех) или DW_DLV_NOCOUNT (ошибка).
Функция dwarf_add_line_entry_b добавляет в секцию .debug_line информацию о строках исходного кода. Эту функцию я вызываю для каждой машинной инструкции:
Dwarf_Unsigned dwarf_add_line_entry_b(
Dwarf_P_Debug dbg,
Dwarf_Unsigned file_index,
Dwarf_Addr code_offset,
Dwarf_Unsigned lineno,
Dwarf_Signed column_number,
Dwarf_Bool is_source_stmt_begin,
Dwarf_Bool is_basic_block_begin,
Dwarf_Bool is_epilogue_begin,
Dwarf_Bool is_prologue_end,
Dwarf_Unsigned isa,
Dwarf_Unsigned discriminator,
Dwarf_Error *error)
Функция возвращает 0 (успех) или DW_DLV_NOCOUNT (ошибка).
И наконец, функция dwarf_lne_end_sequence завершает процедуру:
Dwarf_Unsigned dwarf_lne_end_sequence(
Dwarf_P_Debug dbg,
Dwarf_Addr address;
Dwarf_Error *error)
Возвращает 0 (успех) или DW_DLV_NOCOUNT (ошибка).
На этом завершаем создание процедуры.
В общем, переменные довольно просты. У них есть имя, участок памяти (или регистр процессора), где находится их данные а также тип этих данных. Если переменная глобальная — ее родителем должна быть Единица Компиляции, если локальная — соответсвующий узел (особенно это касается параметров процедур, у них родителем должна быть сама процедура). Также можно указать, в каком файле, строке и колонке находится объявление переменной.
В простейшем случае значение переменной находится по некоторому фиксированному адресу, но многие переменные динамически создаются при входе в процедуру на стеке или регистре, иногда вычисление адреса значения может быть весьма нетривиальным. В стандарте предусмотрен механизм описания того, где находится значение переменной — адресные выражения (location expressions). Адресное выражение — это набор инструкций (константы DW_OP_хххх) для форт-подобной стековой машины, фактически это отдельный язык с ветвлениями, процедурами и арифметическими операциями. Не будем обозревать полностью этот язык, нас фактически будут интересовать только несколько инструкций:
Для того чтобы создать адресное выражение, нужно сначала создать пустое выражение (dwarf_new_expr), добавить в него инструкции (dwarf_add_expr_addr, dwarf_add_expr_gen и др.) и добавить его к узлу в качестве значения атрибута DW_AT_location (dwarf_add_AT_location_expression).
Функция создания пустого адресного выражения возвращает его дескриптор или 0 при ошибке:
Dwarf_Expr dwarf_new_expr(
Dwarf_P_Debug dbg,
Dwarf_Error *error)
Для добавления инструкций в выражение нужно использовать функцию dwarf_add_expr_gen:
Dwarf_Unsigned dwarf_add_expr_gen(
Dwarf_P_Expr expr,
Dwarf_Small opcode,
Dwarf_Unsigned val1,
Dwarf_Unsigned val2,
Dwarf_Error *error)
Функция возвращает DW_DLV_NOCOUNT при ошибке.
Для явного задания адреса переменной вместо предыдущей должна использоваться функция dwarf_add_expr_addr:
Dwarf_Unsigned dwarf_add_expr_addr(
Dwarf_P_Expr expr,
Dwarf_Unsigned address,
Dwarf_Signed sym_index,
Dwarf_Error *error)
Функция также возвращает DW_DLV_NOCOUNT при ошибке.
И наконец, добавить созданное адресное выражение к узлу можно функцией dwarf_add_AT_location_expr:
Dwarf_P_Attribute dwarf_add_AT_location_expr(
Dwarf_P_Debug dbg,
Dwarf_P_Die ownerdie,
Dwarf_Half attr,
Dwarf_P_Expr loc_expr,
Dwarf_Error *error)
Функция возвращает дескриптор атрибута или DW_DLV_NOCOUNT при ошибке.
Переменные (а также параметры процедур) и константы — это обычные узлы с тэгом DW_TAG_variable, DW_TAG_formal_parameter и DW_TAG_const_type соответственно. Для них нужны такие атрибуты:
После создания всех узлов дерева отладочной информации можно приступать к формированию elf-секций с ней. Это происходит в два этапа:
Функция
dwarf_transform_to_disk_form (
Dwarf_P_Debug dbg,
Dwarf_Error* error)
переводит созданную нами отладочную информацию в бинарный формат, но ничего не записывает на диск. Она возвратит нам количество созданных elf-секций или DW_DLV_NOCOUNT при ошибке. При этом для каждой секции будет вызвана callback-функция, которую мы передали при инициализации библиотеки в функцию dwarf_producer_init_c. Эту функцию должны написать мы сами. Ее спецификация такая:
typedef int (*Dwarf_Callback_Func_c)(
char* name,
int size,
Dwarf_Unsigned type,
Dwarf_Unsigned flags,
Dwarf_Unsigned link,
Dwarf_Unsigned info,
Dwarf_Unsigned* sect_name_index,
void * user_data,
int* error)
В этой функции мы должны:
После завершения функция dwarf_transform_to_disk_form вернет нам количество созданных секций. Нам нужно будет пройтись в цикле от 0 по каждой секции, выполнив такие шаги:
Dwarf_Ptr dwarf_get_section_bytes(
Dwarf_P_Debug dbg,
Dwarf_Signed dwarf_section,
Dwarf_Signed *elf_section_index,
Dwarf_Unsigned *length,
Dwarf_Error* error)
Функция возвращает указатель на полученный данные или 0 (в том случае,
когда секций для создания больше не осталось)
После формирования секций можно завершать работу с libdwarf функцией dwarf_producer_finish:
Dwarf_Unsigned dwarf_producer_finish(
Dwarf_P_Debug dbg,
Dwarf_Error* error)
Функция возвращает DW_DLV_NOCOUNT при ошибке.
Замечу, что запись на диск на этом этапе не производится. Запись нужно делать посредством функций из раздела «Создание ELF — Запись файла».
На этом все.
Повторюсь, создание отладочной информации — тема очень обширная, и многих тем я не коснулся, только приоткрыв завесу. Желающие же могут углубляться до бесконечности.
Если у вас будут вопросы — постараюсь на них ответить.
Автор: oco
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/46929
Ссылки в тексте:
[1] Форт: http://ru.wikipedia.org/wiki/%D0%A4%D0%BE%D1%80%D1%82_%28%D1%8F%D0%B7%D1%8B%D0%BA_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%29
[2] stm32vldiscovery: http://www.st.com/web/en/catalog/tools/FM116/SC959/SS1532/PF250863
[3] здесь: http://oco.org.ua/m3forth/
[4] ELF (Executable and Linkable Format): http://ru.wikipedia.org/wiki/Executable_and_Linkable_Format
[5] здесь: http://www.skyfree.org/linux/references/ELF_Format.pdf
[6] особенности ARM: http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044b/IHI0044B_aaelf.pdf
[7] DWARF: http://en.wikipedia.org/wiki/DWARF
[8] Стандарт: http://dwarfstd.org/doc/DWARF4.pdf
[9] официальном сайте: http://dwarfstd.org/
[10] Introduction to the DWARF Debugging Format: http://dwarfstd.org/doc/Debugging%20using%20DWARF-2012.pdf
[11] elfutils: https://fedorahosted.org/elfutils/
[12] LibELF by Example: http://people.freebsd.org/%7Ejkoshy/download/libelf--/article.html
[13] документация: http://www.nxmnpg.com/3/elf
[14] libdwarf: http://sourceforge.net/projects/libdwarf/
[15] Источник: http://habrahabr.ru/post/199490/
Нажмите здесь для печати.