Лицензия Creative Commons
Содержимое блога доступно по лицензии Creative Commons Атрибуция — С сохранением условий
(Attribution-ShareAlike) 3.0 Unported
, если не указано иное.

воскресенье, 1 декабря 2013 г.

Монтирование разделов с IMG-образов дисков, используемых QEMU

Доброго времени суток.

Заметка о том, как монтировать разделы с "сырых" образов дисков (англ. raw images), используемых, в том числе, эмулятором QEMU.

Итак, дано: Образ Debian GNU/Hurd, взятый отсюда. Цель: подмонтировать корневой раздел с образа диска к файловой системе рабочей ОС с помощью mount.

Положим, что мы уже находимся в каталоге с образом диска, а монтировать собираемся первый раздел с него в /mnt/hurd. Имя файла образа: debian-hurd-20130504.img

Первое, что нужно сделать -- посмотреть расположение разделов в образе диска (иными словами, с какого сектора диска начинается каждый из разделов). Это можно сделать как минимум двумя способами: с помощью file и с помощью fdisk. Рассмотрим оба способа.

Утилита file, как я уже как-то раз писал, умеет показывать информацию не только о простых файлах, но и о всяких чудных штуках вроде сохранённой в файл Главной Загрузочной Записи (Master Boot Record, MBR). А сейчас эта утилита поможет нам получить информацию о разделах, запечатанных внутри образа диска:

$ file debian-hurd-20130504.img | fmt
debian-hurd-20130504.img: ; partition 1: ID=0x83, starthead 32,
startsector 2048, 5785600 sectors; partition 2: ID=0x5, starthead 99,
startsector 5789694, 354306 sectors
.

Здесь утилита fmt используется, дабы уместить вывод команды file в 75 столбцов.

Посмотрим же на вывод команды. Видно, что первый раздел (partition 1) начинается с 2048-го сектора диска (startsector 2048). Это как раз то, что нам нужно.

Попробуем теперь получить эту же информацию с помощью утилиты fdisk:

$ su -
# fdisk -l debian-hurd-20130504.img
Disk debian-hurd-20130504.img: 3146 MB, 3146776576 bytes, 6146048 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00053020

                  Устр-во Загр  Начало    Конец   Блоки  Id Система
debian-hurd-20130504.img1        2048 5787647 2892800 83 Linux
debian-hurd-20130504.img2     5789694 6143999  177153  5 Расширенный
debian-hurd-20130504.img5     5789696 6143999  177152 82 Linux своп / Solaris

Видим то же самое -- начало первого раздела находится на 2048-м секторе. Разница лишь в подробности вывода и в том, что нам нужно быть суперпользователем для использования fdisk, а утилита file может быть запущена от имени обычного пользователя.

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

$ echo $((2048 * 512))
1048576

Осталось только передать полученное значение команде mount:

$ su -
# mount -o loop,offset=1048576 debian-hurd-20130504.img /mnt/hurd

Ручного копирования посчитанного значения можно избежать, используя подстановку вывода команды:

# mount -o loop,offset=`echo $((2048 * 512))` \
debian-hurd-20130504.img /mnt/hurd

Полюбуемся результатом:

$ df -h | grep hurd
/dev/loop0  2,8G  804M  1,8G  31%  /mnt/hurd

И последнее: согласно документации, необходимо отмонтировать образ диска прежде, чем загружать его в QEMU -- иначе можно повредить данные на подмонтированных разделах образа диска.

Использованные источники:

- Артём

понедельник, 10 июня 2013 г.

Отладка разделяемой библиотеки в детективном жанре

Одна из проблем при разрабоке -- сделать так, чтобы программа собиралась и работала не только на машине у разработчика, но и на машине конечного пользователя. А ещё лучше -- чтобы всё работало без присутствия разработчика рядом.

Собственно, о чём речь

При работе над Guile-SSH столкнулся с интересной проблемой -- на Gentoo GNU/Linux (моей основной системе) библиотека работает без нареканий, а на Debian GNU/Linux 6.0.7 тестовая программа завершается с ошибкой.

Проблема была обнаружена уже на Linux Install Fest'е, перед презентацией проекта, где я должен был показать пример использования библиотеки. И вот ведь незадача -- оказалось, что эту проблему не так-то просто решить. Я потратил кучу времени, пытаясь понять -- из-за чего, собственно, происходит аварйный останов тестовой программы.

Тестовая программа -- sssh, или Scheme Secure Shell -- работает, как упрощенный вариант ssh в неинтерактивном режиме. То есть, выполняет команду на хосте и возвращает результат. Авторизация на хосте происходит по открытым ключам.

Характерные признаки Bug'а

Bug проявляет себя громким шуршанием за плинтусом и чередующиемися ошибками вида Segmentation Fault (SIGSEGV) и Illegal Hardware Instruction (SIGILL):


$ ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname
libssh version:       0.5.2
libguile-ssh version: 0.2
1. ssh_new
2. ssh_options_set
3. ssh_connect
4. ssh_is_server_known
   ok
[1]    2205 segmentation fault  ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname

Инструменты сыщика

Один из способов отладки -- это добавление отладочных сообщений (или трейсов, от англ. trace, т.е. след) в код. Это на удивление эффективный способ узнать, как работает (или не работает) программа -- в том числе, он может помочь выследить даже хитрого bug'а.

С кодом на Scheme всё достаточно просто -- можно использовать, например, display и write. Примеры:


(display "debug message\n")
=> debug message

...

(write "Hello Scheme World\n")
=> "Hello Scheme World
"

...

(define value 1024)
(display (string-append "Value:" (number->string value))
=> Value: 1024

...

(define some-list '(a b c d e))
(display some-list)
=> (a b c d e)

А вот так можно добавить вывод отладочного сообщения в код на C, чтобы он печатался из Scheme:


scm_display (scm_from_locale_string ("debug message\n"),
             scm_current_output_port ());

То же самое, но в виде удобного макроса:


#define PRINT_DEBUG(data)\
  scm_display (data, scm_current_output_port ())

...

PRINT_DEBUG(scm_from_locale_string ("debug message\n"));

Сужаем круг поиска

Вернёмся к нашему логу.


...
3. ssh_connect
4. ssh_is_server_known
   ok
[1]    2205 segmentation fault  ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname

Путём добавления дополнительных отладочных сообщений после 4-го (в логе выше) можно легко понять, что sssh аварийно завершает свою работу при вызове библиотечной функции guile_ssh_public_key_to_string (ssh:public-key->string)


...
(display "4. ssh_is_server_known\n")
...
(let ((public-key (ssh:public-key->string
                    (ssh:private-key->public-key private-key))))
  ...

которая, в свою очередь, обращается к функции ssh_string_to_char из библиотеки libssh, где и происходит ошибка:


...
str_key = publickey_to_string (data->ssh_public_key);
ret = scm_from_locale_string (ssh_string_to_char (str_key));
ssh_string_free (str_key);
...

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

Вопрос к линковщику

Изучение документации по созданию разделяемых библиотек (к слову, вот этот замечательный документ) дало подсказку, что нужно посмотреть, как идёт процесс динамического связывания. Как-никак, мы же отлаживаем разделяемую библиотеку, верно? Для того, чтобы увидеть этот процесс в действии, нам потребуется увеличительное стекло и одна переменная окружения: имя её LD_DEBUG. В вышеупомянутом замечательном документе пишут, что этой переменной можно присвоить несколько значений. На самом деле, вы можете присвоить ей любое значение, просто только некоторые, вполне определённые значения несут смысл.

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


$ export LD_DEBUG=help
$ ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname
Valid options for the LD_DEBUG environment variable are:

  libs        display library search paths
  reloc       display relocation processing
  files       display progress for input file
  symbols     display symbol table processing
  bindings    display information about symbol binding
  versions    display version dependencies
  all         all previous options combined
  statistics  display relocation statistics
  unused      determined unused DSOs
  help        display this help message and exit

To direct the debugging output into a file instead of standard output
a filename can be specified using the LD_DEBUG_OUTPUT environment variable.

Voilà! Вместо ошибки сегментации от нашей программы мы получили замечательный вывод справки от линковщика о доступных значениях LD_DEBUG. Наиболее интересным для нас сейчас будет значение symbols. Присвоим его переменной и попробуем запустить программу снова (надеюсь, вы не забыли надеть защитные очки?):


$ export LD_DEBUG=symbols
$ ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname
...
      2487: symbol=ssh_string_to_char;  lookup in file=/usr/lib/i686/cmov/libcrypto.so.0.9.8 [0]
      2487: symbol=ssh_string_to_char;  lookup in file=/lib/i686/cmov/libpthread.so.0 [0]
      2487: /usr/local/lib/libguile-ssh.so.0: error: symbol lookup error: undefined symbol: ssh_string_to_char (fatal)
[2]    2487 segmentation fault  ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname

Итак, мы получили ОЧЕНЬ много информации на выходе (я пропустил здесь эту часть), но нам важны последние несколько строчек. Вот оно! Линковщик не может найти символ ssh_string_to_char. Если посмотреть лог выше, то видно, что он просматривает, в том числе, и libssh:


...
 2505: symbol=ssh_string_to_char;  lookup in file=/usr/lib/libssh.so.4 [0]
...

Но ведь эта библиотека как раз и должна содержать в себе определение данной функции!

Идём по следу

Посмотрим-ка на версию библитеки в Debian GNU/Linux. Но прежде выключим подробный вывод сообщений от линковщика, присвоив LD_DEBUG пустую строку:


$ export LD_DEBUG=""
$ aptitude search libssh
...
i A libssh-4            - tiny C SSH library
...
$ aptitude versions libssh-4
i A 0.4.5-3+squeeze1    oldstable                  500 
p A 0.5.3-1~bpo60+1     squeeze-backports          100 

То есть, на Debian GNU/Linux у меня сейчас стоит версия libssh 0.4.x, тогда как мне известно, что API библиотеки был изменён в версии 0.5.x.

Попробуем поставить libssh 0.5 и запустить программу снова. Я собрал версию 0.5.3 из исходников, make install установил её в /usr/local/lib. Укажем программе, где искать библиотеку через переменную LD_LIBRARY_PATH:


$ LD_LIBRARY_PATH=/usr/local/lib ./sssh.scm -i /home/avp/.ssh/lazycat localhost uname
libssh version:       0.5.2
libguile-ssh version: 0.2
1. ssh_new
2. ssh_options_set
3. ssh_connect
4. ssh_is_server_known
   ok
5. ssh_userauth_pubkey
6. ssh_channel_new
7. ssh_channel_open_session
8. ssh_channel_request_exec
Linux

Наконец-то! Заработало! Последняя строчка вывода получена по протоколу SSH и является выводом команды uname.

Заключение

Получается, что баг скрывался даже не в коде (точнее, не совсем в коде), а в динамической линковке с разделяемой библиотекой. При динамическом связывании линковщику не удавалось найти функцию ssh_string_to_char из libssh, тем не менее вызов этой функции всё же происходил. Я думаю так: процессор закладывал аргументы в стэк и прыгал на начало несуществующей функции -- из-за этого-то мы и получали чередующиеся ошибки SIGSEGV/SIGILL. Элементарно, Ватсон.

Странно только, что эта ошибка так себя проявляла -- вот уж чего бы я не ожидал, так это SIGSEGV/SIGILL на этапе исполнения программы из-за того, что линковщик не смог найти искомую функцию при динамическом связывании.

Одно из возможных решений проблемы -- это привязка библиотеки Guile-SSH к конкретной стабильной версии libssh. Это не самый лучший вариант, так как не во всех дистрибутивах используется новая версия библитеки (примером тому могут служить Debian GNU/Linux и старые версии Ubuntu GNU/Linux). Однако это решение позволит избежать проблем с поддержкой совместимости Guile-SSH с libssh 0.4.x.

На этом у меня всё. Надеюсь, что это расследование вам было интересно.

- Артём

суббота, 8 июня 2013 г.

Guile-SSH

Доброго времени суток, случайные и не случайные читатели этого блога.

Занимаюсь сейчас разработкой библиотеки Guile-SSH, которая призвана обеспечить возможность работы с протоколом SSH из программ, написанных на языке Scheme (с использованием интерпретатора GNU Guile).

Презентация по проекту: odp pdf (CC-BY-SA 3.0)

Guile-SSH является обёрткой над libssh и находится на начальной стадии разработки. На данный момент библиотека предоставляет API для создания простого SSH-клиента (пример клиента можно посмотреть здесь). API для создания SSH-сервера планируется.

Для сборки текущей версии библиотеки вам нужны GNU Guile 1.8 и libssh 0.5.3 или новее. Инструкции по сборке и установке можно найти здесь. Последняя версия Guile-SSH на данный момент -- 0.2, но если надумаете собирать, то лучше берите последний коммит с master'а. Код относительно стабилен (по крайней мере, стараюсь ничего не ломать в коммитах).

- Артём

воскресенье, 14 апреля 2013 г.

Макрос define-method* для использования ключевых слов совместно с GOOPS

Сегодня загрузил в репозиторий LazyCat коммит, который добавляет макрос, расширяющий стандартный набор функций GOOPS для создания методов. Макрос используется для реализации метода host-list-add-host в классе <host-list> и позволяет использовать ключевые слова (англ. keywords) для задания аргументов, передаваемых в метод. То есть, вместо создания методов с большим количеством параметров, или выдёргивания аргументов из списка по индексу, можно просто указывать каждый из аргументов по ключевому слову.

Текущая версия макроса выглядит так:

;; Needed modules
(use-modules (ice-9 optargs)
             (ice-9 syncase)
             (oop goops))

;; Macro definition
(define-syntax define-method*
  (syntax-rules ()
    ((_ (m (o <class>) (var defval) ...) body ...)
     (define-method (m (o <class>) . args)
       (let-keywords args #t ((var defval) ...)
                     body ...)))))

Пример использования макроса:

(define-class <talking-machine> ())

(define-method* (hey (obj <talking-machine>)
                     (say  "Hello, ") 
                     (name "J. Random Programmer"))
  (display (string-append say name "!\n")))

(define talking-machine (make <talking-machine>))

Результат вызова метода hey с разными аргументами:

(hey talking-machine)
=> Hello, J. Random Programmer!

(hey talking-machine #:say "Goodbye, ")
=> Goodbye, J. Random Programmer!

(hey talking-machine #:name "John Doe")
=> Hello, John Doe!

Данный макрос написан в процессе изучения макросов в GNU Guile -- может быть, данную задачу можно решить гораздо более простым и элегантным способом?

- Артём

суббота, 9 марта 2013 г.

Show bandwidth usage in GNU Screen status line

Написал простой скрипт для показа скорости загрузки/отдачи в статусной строке GNU Screen. Данный скрипт основан на скрипте для tmux, однако я его основательно переписал, убрав вызов sleep, из-за которого Screen блокировался при обновлении статусной строки.

- Артём

вторник, 29 января 2013 г.

Переворот строки c помощью указателей

Сегодня вечером вспомнил одну старую задачу -- написать функцию переворота строки только с использованием указателей. Задача на первый взгляд простая, и это действительно так -- если использовать очевидный, простой и не особенно оригинальный подход. Вопрос же в следующем -- насколько компактно и необычно можно решить эту задачу?

Вот мой вариант ответа:


#include <stdio.h>

void reverse(char *str)
{
 char *p = str;

 while (*p && *(p + 1) && *p++)
  ;

 while ((p > str) && (*str += *p) && (*p = *str - *p) && (*str++ -= *p--))
  ;
}

int main(int argc, char **argv)
{
 char *str = argv[1];
 reverse(str);
 printf("desrever -- %s\n", str);
 return 0;
}

И, разумеется, "hello world":


$ gcc reverse.c -o reverse
$ ./reverse "hello world"
desrever -- dlrow olleh

- Артём