LINUX.ORG.RU

генерируем FFI с помощью gdb


0

0

Доброго всем!
Есть несколько подходов к FFI, но не один из них мне не понравился,
когда понадобилось сделать интерфейс к stat_t. Проблема - в том, что
типы в ней определены слишком сложно и трудно не ошибиться. Особенно
непонятно, как сделать FFI переносимым.

struct stat
{
__dev_t st_dev; /* Device. */
unsigned short int __pad1;
#ifndef __USE_FILE_OFFSET64
__ino_t st_ino; /* File serial number. */
#else
__ino_t __st_ino; /* 32bit file serial number. */
#endif
__mode_t st_mode; /* File mode. */
....
__nlink_t st_nlink;
#ifndef __USE_FILE_OFFSET64
__blkcnt_t st_blocks; /* Number 512-byte blocks allocated. */
#else
__blkcnt64_t st_blocks; /* Number 512-byte blocks allocated. */


В итоге, я придумал вот что:
1. Пишем вот такую программку:
//gdb-grovel.c
#include <sys/stat.h>

int main(int args, char**argv) {
struct stat test;
return 0;
}
//eof
2. Собираем:
$gcc -g -m32 -fPIC -o gdb-grovel gdb-grovel.c
3. Запускаем gdb:
$gdb gdb-grovel
4. В gdb делаем следующее:
(gdb)break main
(gdb)r
; gdb останавливается в breakpoint
(gdb)ptype test
type = struct stat {
__dev_t st_dev;
short unsigned int __pad1;
...
}

Если мы перенаправим вывод gdb в файл, мы узнаем перечень полей
структуры. Его можно распарсить и для каждого поля узнать его тип:

(gdb) whatis test.st_dev
type = __dev_t
(gdb) whatis __dev_t
type = __u_quad_t
(gdb) whatis __u_quad_t
type = long long unsigned int

Таким образом, у нас есть полная инфа о типе и осталось только
собрать из неё определение FFI.

Вопросы:
1. Нов ли этот подход?
2. Если не нов, известны ли в нём какие-то подставы?
3. Если всё в порядке, то есть ли где-то доведённая до приличного
уровня реализация генератора FFI (для любого языка)?
4. Или я лох и что-то недопонимаю и можно всё сделать и так?
Я генерю FFI для Common Lisp, вроде уже попробовал SWIG и
cffi-grovel, результат примерно одинаковый.

★★★★★

Так и не понял до конца суть проблем, если тебе надо дефайны развернуть -- юзай cpp

www_linux_org_ru ★★★★★
()

> 4. Или я лох и что-то недопонимаю и можно всё сделать и так?

Ты, похоже, не понимаешь, что используешь отладочную информацию в стандартном формате DWARF. И что этой информации самой по себе маловато для генерации полноценных оберток.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Мммм. Я не то, что этого не понимаю, я даже не знаю, зачем мне нужно это знать. А чем это плохо? Если у меня есть n числовых типов (возможно, как-то typedef-енных или #define-нных) и структуры, включающие в себя эти типы, то мне достаточно знать имена полей и имена типов каждого поля. Тогда я могу с помощью CFFI-GROVEL узнать смещения полей в структуре (он пишет для этого специальную программку на С, которая их печатает) и тем самым смогу обращаться к ним из лиспа.

Более того, я это уже сделал (вручную) и написал работающую программу (с использованием fuse). Видимо, мне просто повезло и не попался какой-то более сложный случай типов С? Вот я и хочу узнать (для общего развития), в чём этот случай мог бы состоять.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

> Я не то, что этого не понимаю, я даже не знаю, зачем мне нужно это знать.

Ты спросил "я лох и что-то недопонимаю и можно всё сделать и так?". Да, ты что-то недопонимаешь (DWARF), да, можно всё сделать не так (как минимум - пользоваться не gdb, а readelf или libdwarf). Нужно ли тебе это знать - понятия не имею. Если ты пишешь _полностью автоматический_ генератор FFI-оберток, флаг тебе^W^Wудачи.

> Видимо, мне просто повезло и не попался какой-то более сложный случай типов С? Вот я и хочу узнать (для общего развития), в чём этот случай мог бы состоять.

Попробуй обернуть функцию "int foo(int, int *)"

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Не, не полностью автоматический. Меня больше интересует переносимость. Обёртку к такой функции foo я напишу и руками.

Хотя... К слову, (gdb) whatis main type = int (int, char **) Т.е., мне не нужно писать тип руками, его можно спросить у gdb. Этой информации достаточно для вызова такой ф-ии из лиспа.

А в структуре stat некоторые типы полей (их имена, пример см. выше) зависят от неких #define, которые могут различаться на разных системах. Т.е., я не знаю имя типа и не сказать, чтобы cffi-grovel его изучил.

Хотя... хм... я могу скопировать определение типа вместе с соответствующим #ifdef в свою экспериментальную программу и сделать соответствующий typedef, а от этого typedef - уже FFI декларацию. Это как-то кривовато, но работать будет...

За ключевые слова readelf и libdwarf - спасибо, буду знать.

den73 ★★★★★
() автор топика
Ответ на: комментарий от den73

> Меня больше интересует переносимость.

Куда? На не-DWARF?

> К слову, (gdb) whatis main type = int (int, char **) Т.е., мне не нужно писать тип руками, его можно спросить у gdb.

Ага, и распарсить (причем тип может быть не таким тривиальным, как у main).

> Хотя... хм... я могу скопировать определение типа вместе с соответствующим #ifdef в свою экспериментальную программу и сделать соответствующий typedef, а от этого typedef - уже FFI декларацию.

В DWARF есть все поля, с типами и смещениями, уже после препроцессирования.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Куда? На не-DWARF?

Нет, всего лишь на другие ядра, например, туда, где другое значение #define-ов.

Ладно, в целом всё понял, спасибо. Похоже, на данном этапе частная проблема решена и я могу обойтись без SWIG и без DWARF (решение частной задачи описано в моём предыдущем посте). На будущее буду знать - вдруг пригодится.

den73 ★★★★★
() автор топика

интересно, может стоит попробовать генерировать FFI прямо из GENERIC дерева в gcc? Всё равно GCC компилирует в это представление, почему бы не использовать его. http://www.linuxjournal.com/article/7884 http://gcc.gnu.org/onlinedocs/gccint/GENERIC.html http://www.cse.iitb.ac.in/~uday/gcc-workshop/downloads/workshop-slides/gcc-in... http://gcc.fyxm.net/summit/2003/GENERIC%20and%20GIMPLE.pdf , есть что-то на D http://languagemachine.sourceforge.net/gcc_interface.html -- но там как раз типы и не доделаны

anonymous
()
Ответ на: комментарий от anonymous

То есть, вообще не возимся с типами. Просто экспортируем GENERIC дерево-представление скомпилированной gcc библиотеки в S-выражения, и для него генерируем обёртки в "лисп".

anonymous
()
Ответ на: комментарий от anonymous

Это прикольно. На данный момент мне нужно (для очистки совести) подготовить к публикации примеры работы с fuse из лиспа и нет ресурсов с этим разбираться. Но, в целом, gcc меня радует :)

den73

anonymous
()
Ответ на: комментарий от anonymous

Толку нет, все равно информации о типах параметров недостаточно для формирования обёртки (в общем случае).

Begemoth ★★★★★
()
Ответ на: комментарий от Begemoth

Лиспу не нужны обёртки. Вместо этого, он импортирует типы С и манипулирует объектами С так же, как это делала бы программа на С. См., например, http://common-lisp.net/project/cffi/

Для импорта типов тоже есть неплохие средства, http://common-lisp.net/project/cffi/manual/html_node/The-Groveller.html#The-G...

Например, если мы знаем, что тип T - целочисленный, то нужно просто сказать "лисп, пойми тип" и он его поймёт (аналогично тому, как это делает autoconf).

Если мы знаем, что T - это структура, знаем, какие поля мы хотим видеть из лиспа (необязательно все), знаем имена типов этих полей, и лисп знает эти типы, то этого достаточно для того, чтобы лисп работал с типом T. То же касается юнионов, можно также определять enum-ы. А функции надо вручную прописывать (типы, известные лиспу, их массивы и поинтеры на них).

В моём случае, проблема всего лишь в том, что я не знаю имя типа некоторых полей в struct stat, т.к. они зависят от дефайнов, а дефайны зависят от системы. И я не хочу закладываться на конкретные значения дефайнов, т.к. это будет непереносимо.

den73

anonymous
()
Ответ на: комментарий от anonymous

> Лиспу не нужны обёртки

Правда? Вы так и будете напрямую из лиспа использовать функции с типами 
вида bool (char***, int*)? У вас бдует эта функция, но для ее 
использования нужно будет писать обёртку, причём вручную.

Begemoth ★★★★★
()
Ответ на: комментарий от anonymous

Имеется в виду, буду напрямую. Нетривиальных вызовов функций
у меня нет, но вот как делается функция обратного вызова (из 
работающего примера fuse)

(defcallback hello_readdir_callback :int
    ((path :string)
     (buf :pointer)
     (filler :pointer)
     (offset off_t)
     (fi struct-fuse_file_info*-grovel))
  (declare (ignorable offset fi))
  (cond
    ((string= path "/")
     (flet ((doit (name)
              (foreign-funcall-pointer filler () 
                 :pointer buf 
                 :string name 
                 :pointer (null-pointer) 
                 off_t 0 
                 :int)))
       (mapcar #'doit '("." ".." "hallo")))
     0)
    (t
     (- sb-posix:enoent))))

Собственно, здесь в рантайме определяется и компилируется функция 
обратного вызова, которая вызывает переданный из C указатель на 
функцию filler. Кстати, я соврал: некоторые обёртки все же тут есть. 
Я могу написать тип параметра или возвращаемого значения как :string 
и тогда произойдёт преобразование в/из char* 

Этот созданный callback я засовываю в С-шную таблицу диспетчеризации
hello_oper_ptr, имеющую тип какой-то вроде fuse_operations. Этот код 
я немного упростил для ясности. 

(setf (foreign-slot-value
       hello_oper_ptr 'fuse_operations 'readdir)
      (callback hello_readdir_callback))

den73

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.