4. Написание собственных модулей¶
Начиная с YARA версии 3.0 вы можете расширить ее возможности. Это возможно с помощью модулей, которые вы можете использовать для определения структур данных и функций, которые впоследствии можно будет применить в ваших правилах. Вы можете увидеть некоторые примеры того, что может делать модуль, в Главе 3 Модули.
Цель данной главы состоит в том, чтобы научить вас создавать собственные модули для предоставления YARA дополнительных функций.
4.1. Модуль “Hello World!”¶
Модули пишутся на языке программирования C и встраиваются в YARA в процессе компиляции. Чтобы создавать свои собственные модули, вы должны быть знакомы с языком программирования C, а также с тем, как конфигурировать и собирать YARA из исходного кода. Вам не нужно понимать, как YARA реализует свои функции; YARA предоставляет простой API для модулей, и это все, что вам нужно знать.
Исходный код модуля должен находиться в каталоге libyara/modules
с исходными кодами. Рекомендуется использовать имя модуля в качестве имени файла для исходного файла, если имя вашего модуля foo
, его исходный файл должен быть foo.с
.
В каталоге libyara/modules
вы найдете файл demo.c
, который мы будем использовать в качестве отправной точки. Файл выглядит следующим образом:
#include <yara/modules.h>
#define MODULE_NAME demo
begin_declarations;
declare_string("greeting");
end_declarations;
int module_initialize(YR_MODULE* module)
{
return ERROR_SUCCESS;
}
int module_finalize(YR_MODULE* module)
{
return ERROR_SUCCESS;
int module_load(YR_SCAN_CONTEXT* context, YR_OBJECT* module_object, void* module_data, size_t module_data_size)
{
set_string("Hello World!", module_object, "greeting");
return ERROR_SUCCESS;
}
int module_unload(YR_OBJECT* module_object)
{
return ERROR_SUCCESS;
}
#undef MODULE_NAME
Начнем разбирать исходный код, чтобы вы могли понять каждую деталь. Первая строка в коде:
#include <yara/modules.h>
В заголовочном файле modules.h
находятся определения API для модулей YARA, поэтому эта директива include
необходима во всех ваших модулях. Вторая строка:
#define MODULE_NAME demo
Это, определение имени вашего модуля. Для каждого модуля необходимо определить свое имя в начале исходного кода. Имена модулей должны быть уникальными среди модулей, встроенных в YARA.
Затем следует раздел объявлений функций и данных:
begin_declarations;
declare_string("greeting");
end_declarations;
Здесь модуль объявляет функции и структуры данных, которые будут доступны для ваших правил YARA. В этом случае мы объявляем только строковую переменную с именем greeting
. Более подробно мы обсудим эти вопросы в разделе 4.2.
После раздела объявлений, показанного выше, идет пара функций:
int module_initialize(YR_MODULE* module)
{
return ERROR_SUCCESS;
}
int module_finalize(YR_MODULE* module)
{
return ERROR_SUCCESS;
}
Функция module_initialize
вызывается во время инициализации YARA, в то время как функция module_finalize
вызывается при завершении YARA. Эти функции позволяют инициализировать и завершить любую глобальную структуру данных, использование которой требуется для работы модуля.
Затем идет функция module_load:
int module_load(
YR_SCAN_CONTEXT* context,
YR_OBJECT* module_object,
void* module_data,
size_t module_data_size)
{
set_string("Hello World!", module_object, "greeting");
return ERROR_SUCCESS;
}
Эта функция вызывается один раз для каждого сканируемого файла, но только если модуль импортируется в какое-либо правило с помощью директивы import
. Функция module_load
позволяет модулю проверять сканируемый файл, разбирать и анализировать его, а затем заполнять структуры данных, определенные в разделе объявлений.
В этом примере функция module_load
вообще не проверяет содержимое файла, она просто присваивает строку “Hello World!”к переменной greeting
, объявленной ранее.
И, наконец, у нас есть функция module_unload
:
int module_unload(YR_OBJECT* module_object)
{
return ERROR_SUCCESS;
}
Для каждого вызова module_load
существует соответствующий вызов module_unload
. Эта функция позволяет модулю освободить любой ресурс, выделенный во время module_load
. В нашем случае ничего не нужно освобождать, поэтому функция просто возвращает ERROR_SUCCESS
. И module_load
и module_unload
должны возвращать ERROR_SUCCESS
, чтобы указать, что все прошло нормально. Если возвращается другое значение, сканирование будет прервано и пользователю будет сообщено об ошибке.
4.1.1. Сборка нашего “Hello World!”¶
Для того, чтобы встроить модули в YARA, необходимо поместить их исходный код в каталог libyara/modules
, и выполнить два дальнейших шага, чтобы заставить их работать. Первым шагом является добавление модуля в файл module_list
, который также находится в каталоге libyara/modules
.
Файл module_list
выглядит следующим образом:
MODULE(tests)
MODULE(pe)
#ifdef CUCKOO_MODULE
MODULE(cuckoo)
#endif
Второй шаг-изменение файла Makefile.am
чтобы сообщить программе make
, что исходный код вашего модуля должен быть скомпилирован и связан с YARA. В самом начале файла libyara/Makefile.ам
вы найдете следующее:
MODULES = modules/tests.c
MODULES += modules/pe.c
if CUCKOO_MODULE
MODULES += modules/cuckoo.c
endif
Просто добавьте новую строку для вашего модуля:
И это все! Теперь вы готовы построить YARA с вашим новым модулем. Просто перейдите в корневой каталог с исходниками и введите:
make
sudo make install
Теперь вы можете создать такое правило:
import "demo"
rule HelloWorld
{
condition:
demo.greeting == "Hello World!"
}
Любой файл, отсканированный с помощью этого правила, будет ему соответствовать, поскольку условие demo.greeting == "Hello World!"
всегда true
.
4.2. Раздел объявлений¶
В разделе объявлений объявляются переменные, структуры и функции, которые будут доступны для правил YARA. Каждый модуль должен содержать это раздел, который выглядит следующим образом:
begin_declarations;
<your declarations here>
end_declarations;
4.2.1. Основные типы¶
В разделе объявлений можно использовать declare_string(<имя переменной>)
, declare_integer(<имя переменной>)
и declare_float(<имя переменной>)
для объявления строковых, целочисленных переменных или переменных с плавающей запятой соответственно. Например:
begin_declarations;
declare_integer("foo");
declare_string("bar");
declare_float("baz");
end_declarations;
Note
Переменные с плавающей запятой требуют YARA версии 3.3.0 или более поздней.
Имена переменных могут содержать такие символы как; буквы, цифры и символы подчеркивания. Эти переменные могут быть использованы позже в ваших правилах в любом месте, где ожидается число или строка. Предположим, что имя вашего модуля mymodule
, тогда переменные могут быть использованы следующим образом:
mymodule.foo > 5
mymodule.bar matches /someregexp/
4.2.2. Структуры¶
Ваши объявления могут быть организованы более структурированным образом:
begin_declarations;
declare_integer("foo");
declare_string("bar");
declare_float("baz");
begin_struct("some_structure");
declare_integer("foo");
begin_struct("nested_structure");
declare_integer("bar");
end_struct("nested_structure");
end_struct("some_structure");
begin_struct("another_structure");
declare_integer("foo");
declare_string("bar");
declare_string("baz");
declare_float("tux");
end_struct("another_structure");
end_declarations;
В этом примере мы используем begin_struct(<имя структуры>)
и end_struct (<имя структуры>)
для разграничения двух структур some_structure
и another_structure
. В разделители структуры можно поместить любые другие объявления, включая другое объявление структуры. Также обратите внимание, что члены разных структур могут иметь одно и то же имя, но члены одной структуры должны иметь уникальные имена.
Обращение к этим переменным из ваших правил будет выглядеть следующим образом:
mymodule.foo
mymodule.some_structure.foo
mymodule.some_structure.nested_structure.bar
mymodule.another_structure.baz
4.2.3. Массивы¶
Точно так же, как вы объявляете отдельные строки, целые числа, числа с плавающей запятой или структуры, вы можете объявлять массивы из них:
begin_declarations;
declare_integer_array("foo");
declare_string_array("bar");
declare_float_array("baz");
begin_struct_array("struct_array");
declare_integer("foo");
declare_string("bar");
end_struct_array("struct_array");
end_declarations;
К отдельным значениям в массиве обращаются, как и в большинстве языков программирования:
foo[0]
bar[1]
baz[3]
struct_array[4].foo
struct_array[1].bar
Массивы начинаются с нуля и не имеют фиксированного размера, они будут увеличиваться по мере необходимости, когда вы начнете инициализировать его значения.
4.2.4. Словари¶
Добавлено в версии 3.2.0.
Вы также можете объявить словари целых чисел, чисел с плавающей запятой, строк или структур:
begin_declarations;
declare_integer_dictionary("foo");
declare_string_dictionary("bar");
declare_float_dictionary("baz")
begin_struct_dictionary("struct_dict");
declare_integer("foo");
declare_string("bar");
end_struct_dictionary("struct_dict");
end_declarations;
Отдельные значения в словаре доступны с помощью строкового ключа:
foo["somekey"]
bar["anotherkey"]
baz["yetanotherkey"]
struct_dict["k1"].foo
struct_dict["k1"].bar
4.2.5. Функции¶
Одной из наиболее мощных возможностей модулей YARA является возможность объявления функций, которые впоследствии могут быть вызваны из ваших правил. Функции должны появляться в разделе объявлений следующим образом:
declare_function(<function name>, <argument types>, <return tuype>, <C function>);
<function name>
- это имя, которое будет использоваться в ваших правилах YARA для вызова функции.
<argument types>
- это строка, содержащая один символ на аргумент функции, где символ указывает тип аргумента. Функции могут принимать четыре различных типа аргументов: строка, целое число, число с плавающей точкой и регулярное выражение, обозначаемые символами: s
, i
, f
и r
соответственно. Если ваша функция в качестве аргумента получает два целых числа, <argument types>
должен быть "ii"
, если она получает целое число в качестве первого аргумента и строку в качестве второго, то <argument types>
должен быть "is"
, если она получает три строки и число с плавающей запятой <argument types>
должен быть "sssf"
.
<return tuype>
- это строка с одним символом, обозначающим тип возвращаемого значения. Возможные типы возвращаемых значений: строка "s"
, целое число "i"
и число с плавающей запятой "f"
.
<C function>
- идентификатор для фактической реализации вашей функции.
Ниже приведен полный пример:
define_function(isum)
{
int64_t a = integer_argument(1);
int64_t b = integer_argument(2);
return_integer(a + b);
}
define_function(fsum)
{
double a = float_argument(1);
double b = float_argument(2);
return_integer(a + b);
}
begin_declarations;
declare_function("sum", "ii", "i", sum);
end_declarations;
Как вы можете видеть в приведенном выше примере, ваш код функции должен быть определен перед разделом объявлений, например:
define_function(<function identifier>)
{
//..ваш код
}
Функции могут быть перегружены, как в C++ и других языках программирования. Вы можете объявить две функции с одинаковыми именами, если они различаются по типу или количеству аргументов. Один пример перегруженных функций можно найти в модуле Hash
, он имеет две функции для вычисления MD5-хэшей, одна получает в качестве аргументов смещение и длину в файле, а другая получает строку:
begin_declarations;
declare_function("md5", "ii", "s", data_md5);
declare_function("md5", "s", "s", string_md5);
end_declarations;
Подробнее обсудим реализацию функций в разделе 4.5 Подробнее о функциях.
4.3. Инициализация и завершение¶
Каждый модуль должен реализовать две функции для инициализации и завершения: module_initialize
и module_finalize
. Первый вызывается во время инициализации YARA через функцию yr_initialize ()
(см. п. 7.6.2), а второй-во время завершения через функцию yr_finalize()
(см. п. 7.6.2). Обе функции вызываются независимо от того, импортируется ли модуль каким-либо правилом.
Эти функции дают модулю возможность инициализировать любую глобальную структуру данных, которая ему может понадобиться, но в большинстве случаев это просто пустые функции:
int module_initialize(YR_MODULE* module)
{
return ERROR_SUCCESS;
}
int module_finalize(YR_MODULE* module)
{
return ERROR_SUCCESS;
}
Любое возвращаемое значение, отличное от ERROR_SUCCESS
, прервет выполнение YARA.
4.4. Реализации логики работы модуля¶
Кроме module_initialize
и module_finalize
каждый модуль должен реализовывать еще две функции, которые вызываются YARA при сканировании файла или пространства памяти процесса: module_load
и module_unload
. Обе функции вызываются один раз для каждого сканируемого файла или процесса, но только если модуль был импортирован с помощью директивы import
. Если модуль не импортируется в какое-либо правило, то module_load
или module_unload
вызываться не будут.
Функция module_load
имеет следующий прототип:
int module_load(
YR_SCAN_CONTEXT* context,
YR_OBJECT* module_object,
void* module_data,
size_t module_data_size)
Аргумент context
содержит информацию относительно текущего сканирования, включая сканируемые данные. Аргумент module_object
является указателем на структуру YR_OBJECT
, связанную с модулем. Каждая структура, переменная или функция, объявленная в модуле YARA, представлена структурой YR_OBJECT
. Эти структуры образуют дерево, корнем которого является структура модуля YR_OBJECT
. Например, если у вас есть следующие объявления в модуле с именем mymodule
:
begin_declarations;
declare_integer("foo");
begin_struct("bar");
declare_string("baz");
end_struct("bar");
end_declarations;
Тогда дерево будет выглядеть так:
YR_OBJECT(type=OBJECT_TYPE_STRUCT, name="mymodule")
!
!_ YR_OBJECT(type=OBJECT_TYPE_INTEGER, name="foo")
!
!_ YR_OBJECT(type=OBJECT_TYPE_STRUCT, name="bar")
!
!_ YR_OBJECT(type=OBJECT_TYPE_STRING, name="baz")
Обратите внимание, что и bar
, и mymodule
имеют одинаковый тип OBJECT_TYPE_STRUCT
, что означает, что YR_OBJECT
, связанный с модулем, является просто еще одной структурой, подобной bar
. Фактически, когда вы пишете в своих правилах что-то вроде mymodule.foo
, вы выполняете поиск полей в структуре так же, как это делает bar.baz
.
Таким образом, аргумент module_object
позволяет вам получить доступ к каждой переменной, структуре или функции, объявленной модулем, предоставив указатель на корень дерева объектов.
Аргумент module_data
- это указатель на любые дополнительные данные, передаваемые модулю, а module_data_size
- это размер этих данных. Не все модули требуют дополнительных данных, большинство из них полагаются только на данные, которые сканируются, но некоторые из них требуют дополнительной информации в качестве входных данных. Модуль Cuckoo
является хорошим примером этого, он получает отчет о поведении, связанный с проверяемыми PE-файлами, который передается в аргументах module_data
и module_data_size
.
Для получения дополнительной информации о том, как передать дополнительные данные в ваш модуль, посмотрите на применение опции -x
в Главе 5.
4.4.1. Доступ к сканируемым данным¶
Большинству модулей YARA необходим доступ к сканируемому файлу или памяти процесса, чтобы извлечь из него информацию. Сканируемые данные отправляются в модуль в структуре YR_SCAN_CONTEXT
, передаваемой в функцию mdule_load
. Данные иногда разбиваются на блоки, поэтому вашему модулю необходимо выполнять итерации по блокам с помощью макроса foreach_memory_block
:
int module_load(
R_SCAN_CONTEXT* context,
YR_OBJECT* module_object,
void* module_data,
size_t module_data_size)
{
YR_MEMORY_BLOCK* block;
foreach_memory_block(context, block)
{
//..делаем какие-либо операции с текущим блоком памяти
}
}
Каждый блок памяти представлен структурой YR_MEMORY_BLOCK
со следующими атрибутами:
- YR_MEMORY_BLOCK_FETCH_DATA_FUNC fetch_data
Указатель на функцию, возвращающую указатель на блок данных.
- size_t size
Размер блока данных.
- size_t base
Базовое смещение / адрес для этого блока. Если файл сканируется, это поле содержит смещение в файле, с которого начинается блок, если сканируется область памяти процесса, он содержит виртуальный адрес, с которого начинается блок.
Блоки всегда повторяются в том же порядке, в котором они появляются в файле или в памяти процесса. В случае файлов первый блок будет содержать начало файла. Фактически, в большинстве случаев один блок будет содержать содержимое всего файла, но вы не можете полагаться на это при написании кода. Для очень больших файлов YARA может в конечном итоге разбить файл на два или более блоков, и ваш модуль должен быть готов к этому.
При сканировании пространства памяти процесса ваш модуль определенно получит большое количество блоков, по одному для каждой выделенной области памяти в адресном пространстве процесса.
Однако в некоторых случаях перебирать блоки не требуется. Если ваш модуль просто анализирует заголовок какого-либо формата файла, вы можете смело предполагать, что весь заголовок содержится в первом блоке (тем не менее, добавьте некоторые проверки в ваш код). В этих случаях вы можете использовать макрос first_memory_block
:
int module_load(
YR_SCAN_CONTEXT* context,
YR_OBJECT* module_object,
void* module_data,
size_t module_data_size)
{
YR_MEMORY_BLOCK* block;
const uint8_t* block_data;
block = first_memory_block(context);
block_data = block->fetch_data(block)
if (block_data != NULL)
{
//..делаем какие-либо операции с текущим блоком памяти
}
}
В предыдущем примере вы также можете увидеть, как использовать функцию fetch_data
. Эта функция, которая является членом структуры YR_MEMORY_BLOCK
, получает указатель на тот же блок и возвращает указатель на данные блока. Вашему модулю не принадлежит память, на которую указывает этот указатель, освобождение этой памяти не является вашей ответственностью. Однако имейте в виду, что указатель действителен только до тех пор, пока вы не запросите следующий блок памяти. Пока вы используете указатель в пределах foreach_memory_block
, вы в безопасности. Также учтите, что fetch_data
может возвращать указатель NULL
, ваш код должен быть подготовлен для этого случая.
const uint8_t* block_data;
foreach_memory_block(context, block)
{
block_data = block->fetch_data(block);
if (block_data != NULL)
{
// использование block_data здесь безопасно.
}
}
// память, на которую указывает block_data, здесь уже может быть освобождена.
4.4.2. Присваивание значений переменным¶
Функция module_load
позволяет назначать значения переменным, объявленным в разделе объявлений, после того, как вы пропарсили или проанализировали сканируемые данные и/или данные любого дополнительного модуля. Это делается с помощью функций set_integer
и set_string
:
void set_integer (int64_t value, YR_OBJECT* object, const char* field, …)
void set_string (const char* value, YR_OBJECT* object, const char* field, …)
Обе функции получают значение, которое должно быть присвоено переменной, указатель на YR_OBJECT
, представляющий саму переменную или некоторого предка этой переменной, дескриптор поля и дополнительные аргументы, как определено дескриптором поля.
Если мы присваиваем значение переменной, представленной самим объектом, то дескриптор поля должен быть NULL
.
Например, предполагая, что объект указывает на структуру YR_OBJECT
, соответствующую некоторой целочисленной переменной, мы можем установить значение для этой целочисленной переменной с помощью:
set_integer(<value>, object, NULL);
Дескриптор поля используется, когда вы хотите присвоить значение некоторому потомку объекта. Например, рассмотрим следующие объявления:
begin_declarations;
begin_struct("foo");
declare_string("bar");
begin_struct("baz");
declare_integer("qux");
end_struct("baz");
end_struct("foo");
end_declarations;
Если объект указывает на YR_OBJECT
, связанный со структурой foo
, вы можете установить значение для строки bar
следующим образом:
set_string(<value>, object, "bar");
И значение для qux
таким образом:
set_integer(<value>, object, "baz.qux");
Вы помните, что аргумент module_object
для module_load
был указателем на YR_OBJECT
? Вы помните, что этот YR_OBJECT
является структурой, как и bar
? Исходя из этого, вы также можете установить значения для bar
и qux
следующим образом:
set_string(<value>, module_object, "foo.bar");
set_integer(<value>, module_object, "foo.baz.qux");
Но что происходит с массивами? Каким образом можно установить значения для элементов массива? Если у вас есть следующее объявление:
begin_declarations;
declare_integer_array("foo");
begin_struct_array("bar")
declare_string("baz");
declare_integer_array("qux");
end_struct_array("bar");
end_declarations;
Тогда следующие представления операторов set_integer
и set_struing
являются валидными:
set_integer(<value>, module, "foo[0]");
set_integer(<value>, module, "foo[%i]", 2);
set_string(<value>, module, "bar[%i].baz", 5);
set_string(<value>, module, "bar[0].qux[0]");
set_string(<value>, module, "bar[0].qux[%i]", 0);
set_string(<value>, module, "bar[%i].qux[%i]", 100, 200);
Спецификатор формата %i
в дескрипторе поля заменяются дополнительными целочисленными аргументами, передаваемыми функции. Это работает так же, как printf
в программах на C, но единственными допустимыми спецификаторами формата являются %i
и %s
для целочисленных и строковых аргументов соответственно.
Спецификатор формата %s
используется для назначения значений определенному ключу в словаре:
set_integer(<value>, module, "foo[\"key\"]");
set_integer(<value>, module, "foo[%s]", "key");
set_string(<value>, module, "bar[%s].baz", "another_key");
Если явно не присвоить значение объявленной переменной, массиву или элементу справочника, то они останутся в неопределенном состоянии. Это не проблема, и даже полезно во многих случаях. Например, если модуль предназначен для анализа файлов определенного формата, а получает для анализа файлы другого формата, можно оставить все переменные неопределенными, а не присваивать им фиктивные значения, которые не имеют смысла. YARA будет обрабатывать неопределенные значения в условиях правила, как описано в Главе 3 Модули.
В дополнение к функциям set_integer
и set_string
у вас есть их аналоги get_integer
и get_string
. Как следует из их имен, они используются для получения значения переменной, что может быть полезно при реализации ваших функций для получения значений, ранее сохраненных в module_load
.
int64_t get_integer (YR_OBJECT* object, const char* field, …)
char* get_string (YR_OBJECT* object, const char* field, …)
Также есть функция для получения любого YR_OBJECT
в дереве объектов:
YR_OBJECT* get_object (YR_OBJECT* object, const char* field, …)
Теперь небольшой экзамен…
Эквивалентны ли следующие две строки? Почему?
set_integer(1, get_object(module_object, "foo.bar"), NULL);
set_integer(1, module_object, "foo.bar");
4.4.3. Сохранение данных для дальнейшего использования¶
Иногда информации, хранящейся непосредственно в ваших переменных, записанных с помощью set_integer
и set_string
, недостаточно. Возможно, вам потребуется хранить более сложные структуры данных или информацию, которую не нужно предоставлять правилам YARA.
Хранение информации важно, когда ваш модуль экспортирует функции для использования в правилах YARA. Реализация этих функций обычно требует доступа к информации, генерируемой module_load
, которая должна где-то храниться. У вас может возникнуть желание определить глобальные переменные для хранения необходимой информации, но это сделает ваш код не поточно-ориентированным. Правильный подход заключается в использовании поля данных структур YR_OBJECT
.
Каждый YR_OBJECT
имеет поле void* data
, которое может быть безопасно использовано вашим кодом для хранения указателя на любые данные, которые вам могут понадобиться. Типичный шаблон использует поле data
YR_OBJECT
модуля, как в следующем примере:
typedef struct _MY_DATA
{
int some_integer;
} MY_DATA;
int module_load(
YR_SCAN_CONTEXT* context,
YR_OBJECT* module_object,
void* module_data,
size_t module_data_size)
{
module->data = yr_malloc(sizeof(MY_DATA));
((MY_DATA*) module_object->data)->some_integer = 0;
return ERROR_SUCCESS;
}
Не забудьте освободить выделенную память в функции module_unload
:
int module_unload(YR_OBJECT* module_object)
{
yr_free(module_object->data);
eturn ERROR_SUCCESS;
}
Note
Не используйте глобальные переменные для хранения данных. Функции в модуле могут быть вызваны из разных потоков одновременно, и может произойти повреждение данных или неправильное поведение.
4.5. Подробнее о функциях¶
Мы уже показали, как объявить функцию в разделе объявлений (см. п. 4.2.5). Здесь мы собираемся показать, как обеспечить их реализацию.
4.5.1. Аргументы функций¶
В коде функции вы получаете ее аргументы с помощью integer_argument(n)
, float_argument(n)
, regexp_argument(n)
, string_argument(n)
или sized_string_argument(n)
в зависимости от типа аргумента, где n
- номер аргумента начиная с 1.
string_argument(n)
может использоваться, когда ваша функция ожидает получить C-строку с нулевым завершением, если ваша функция может получать произвольные двоичные данные, возможно содержащие нулевые байты, вы должны использовать sized_string_argument(n)
.
Вот несколько примеров:
int64_t arg_1 = integer_argument(1);
RE* arg_2 = regexp_argument(2);
char* arg_3 = string_argument(3);
SIZED_STRING* arg_4 = sized_string_argument(4);
double arg_5 = float_argument(1);
Тип C для целочисленных аргументов - int64_t
, для аргументов с плавающей запятой - double
, для регулярных выражений - RE*
, для NULL-завершенных строк - char*
, а для строк, возможно содержащих NULL-символы, - SIZED_STRING*
. Структуры SIZED_STRING
имеют следующие атрибуты:
SIZED_STRING
- length - Длина строки.
- c_string -
char*
указатель на содержимое строки.
4.5.2. Возвращаемые значения¶
Функции могут возвращать три типа значений: строки, целые числа и числа с плавающей точкой. Вместо использования оператора возврата, используемого в языке программирования C вы должны использовать return_string (x)
, return_integer (x)
или return_float (x)
для возврата из функции, в зависимости от типа возвращаемого значения функции. Во всех случаях x
является константой, переменной или выражением, оцениваемым как char*
, int64_t
или double
соответственно.
Вы можете использовать return_string (UNDEFINED)
, return_float (UNDEFINED)
и return_integer (UNDEFINED)
для возврата неопределенных значений из функции. Это полезно во многих ситуациях, например, если аргументы, переданные функциям, не имеют смысла, или если ваш модуль ожидает определенный формат файла, а сканируемый файл - другого формата, или в любом другом случае, когда ваша функция не может возвратить верное значение.
Note
Не используйте оператор возврата C для возврата из функции. Возвращаемое значение будет интерпретировано как код ошибки.
4.5.3. Доступ к объектам¶
При написании функции нам иногда требуется доступ к значениям, ранее назначенным переменным модуля, или дополнительным данным, хранящимся в поле data
структур YR_OBJECT
, как обсуждалось ранее в п. 4.4.3, для последующего использования. Но для этого нам нужен способ, позволяющий получить доступ к соответствующей структуре YR_OBJECT
. Для этого есть две функции: module ()
и parent ()
. Функция module ()
возвращает указатель на YR_OBJECT
верхнего уровня, соответствующий модулю, который передается в функцию module_load
. Функция parent ()
возвращает указатель на YR_OBJECT
, соответствующий структуре, в которой содержится функция. Например, рассмотрим следующий фрагмент кода:
define_function(f1)
{
YR_OBJECT* module = module();
YR_OBJECT* parent = parent();
// parent == module;
}
define_function(f2)
{
YR_OBJECT* module = module();
YR_OBJECT* parent = parent();
// parent != module;
}
begin_declarations;
declare_function("f1", "i", "i", f1);
begin_struct("foo");
declare_function("f2", "i", "i", f2);
end_struct("foo");
end_declarations;
В функции f1
переменная module
указывает на верхний уровень YR_OBJECT
, а также на переменную parent
, потому что родителем для f1
является сам модуль. Однако в функции f2
переменная parent
указывает на YR_OBJECT
, соответствующий структуре foo
, а module
указывает на верхний уровень YR_OBJECT
, как и в первом случае.
4.5.4. Контекст сканирования¶
Из функции вы также можете получить доступ к структуре YR_SCAN_CONTEXT
, обсуждавшейся ранее в п. 4.4.1. Это полезно для функций, которые должны проверять сканируемый файл или память процесса. Вот как вы получаете указатель на структуру YR_SCAN_CONTEXT
:
YR_SCAN_CONTEXT* context = scan_context();