8 мая 2014 г.

QEMU: Монтирование образа qcow2 с LVM

Самый простой способ - использовать guestmount.

# guestmount -a  image.qcow2 -i --rw /mount/point

В Gentoo это пакет app-emulation/libguestfs (лучше использовать версию >= 1.26).

Если возникнет ошибка:

libguestfs: error: cannot find any suitable libguestfs supermin, fixed or old-style appliance on LIBGUESTFS_PATH (search path: /usr/lib64/guestfs)

ее можно вылечить так:

# ln -s /usr/share/guestfs/appliance /usr/lib64/guestfs

11 февр. 2014 г.

Kernel: отладка функции инициализации модуля ядра

1.1 Устанавливаем breakpoint на загрузчик модулей


(gdb) break do_one_initcall

1.2 Загружаем модуль


# insmod mymodule

1.3 Загружаем символы


(gdb) frame 1
(gdb) print mod->module_core
0xdeadbeaf
(gdb) add-symbol-file mymodule.ko 0xdeadbeaf

Gentoo: Кросскомпиляция и отладка ядра другой архитектуры

Ссылки


ARM Versatile PB


Пути


Создаем каталог, в котором будет проходить вся работа.

# mkdir /some/dir
# export VM=/some/dir

Виртуальная машина (Debian Squeeze)


Устанавливаем QEMU с включенной опцией arm в переменных QEMU_SOFTMMU_TARGETS и QEMU_USER_TARGETS.


Создаем диск.

# cd $VM
# qemu-img create -f qcow2 vm.qcow2 20G

Скачиваем образ установщика.

# cd $VM
# wget http://caesar.acc.umu.se/cdimage/archive/6.0.8/armel/iso-cd/debian-6.0.8-armel-netinst.iso

Скачиваем ядро и initrd установщика (QEMU не поддерживает загрузчик для ARM).

# cd $VM
# wget ftp://ftp.us.debian.org/debian/dists/wheezy/main/installer-armel/20130613+deb7u1+b1/images/versatile/netboot/vmlinuz-3.2.0-4-versatile
# wget ftp://ftp.us.debian.org/debian/dists/wheezy/main/installer-armel/20130613+deb7u1+b1/images/versatile/netboot/initrd.gz

Устанавливаем Debian.

# brctl show br0
bridge name        bridge id                STP enabled       interfaces
br0                8000.3085a997443e        no                eth0

# cat $VM/vm-ifup.sh
#! /bin/bash
set -x
ifconfig $1 up && brctl addif br0 $1

# qemu-system-arm \
  -m 2047 -M versatilepb -hda $VM/vm.qcow2 \
  -net nic,vlan=1,macaddr=de:ad:be:af:11:22 \
  -net tap,vlan=1,ifname=myvm,script=$VM/vm-ifup.sh \
  -kernel $VM/vmlinuz-3.2.0-4-versatile \
  -initrd $VM/initrd.gz \
  -cdrom $VM/debian-6.0.8-armel-netinst.iso \
  -boot d

Вытаскиваем initrd из установленного Debian.

# qemu-nbd --connect=/dev/nbd0 $VM/vm.qcow2
# mkdir $VM/vm-disk
# fdisk -l /dev/nbd0
# mount /dev/nbd0p1 $VM/vm-disk
# cp $VM/vm-disk/boot/initrd.img-2.6.32-5-versatile $VM/
# umount $VM/vm-disk
# rmdir $VM/vm-disk
# qemu-nbd --disconnect $VM/vm.qcow2

Загружаем дефолтный Debian.

# qemu-system-arm \
  -m 2047 -M versatilepb -hda $VM/vm.qcow2 \
  -net nic,vlan=1,macaddr=de:ad:be:af:11:22 \
  -net tap,vlan=1,ifname=myvm,script=$VM/vm-ifup.sh \
  -kernel $VM/vmlinuz-3.2.0-4-versatile \
  -initrd $VM/initrd.img-2.6.32-5-versatile \
  -append "root=/dev/sda1"

Тулчейн


Устанавливаем crossdev.

# emerge -av sys-devel/crossdev

Устанавливаем тулчейн.

# crossdev --binutils 2.20.1 --gcc 4.6.4 --kernel 3.1 --libc 2.11.3 --target arm-linux-gnueabi

Устанавливаем GDB (флаг expat нужен для парсинга XML, который раздает gdbserver QEMU).

# USE=expat emerge -av cross-arm-linux-gnueabi/gdb

Ядро


У меня работали ядра 2.6.x версий от 2.6.29 до 2.6.35.9.


Скачиваем и распаковываем ядро.

# cd $VM
# wget ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.5.tar.bz2
# tar xvf linux-2.6.32.5.tar.bz2

Конфигурируем.

# cd $VM/linux-2.6.32.5
# make versatile_defconfig
# make menuconfig

Нужно включить эти опции:

General Setup --->
  [*] Kernel .config support
  [*] Enable access to .config through /proc/config.gz

Kernel Features --->
  [*] Use VM EABI to compile the kernel
  [*] Allow old ABI binaries to run with this kernel

Bus Support --->
  [*] PCI Support

Device Drivers --->
  SCSI Device Support --->
    [*] SCSI Device Support
    [*] SCSI Disk Support
    [*] SCSI CDROM support
    [*] SCSI low-lever drivers --->
      [*] SYM53C8XX  Version 2 SCSI support

  Generic Driver Options--->
    [*] Maintain a devtmpfs filesystem to mount at /dev
    [*] Automount devtmpfs at /dev, after the kernel mounted the root

  Input device support--->
    [*] Event interface

File systems --->
  <*> Ext3 journalling file system support
  <*> The Extended 4 (ext4) filesystem
  Pseudo filesystems--->
    [*] Virtual memory file system support (former shm fs)

Kernel hacking --->
  [*] Compile the kernel with debug info

Собираем ядро и модули.

# mkdir -p $VM/arm-modules
# cd $VM/linux-2.6.32.5
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules
# make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- modules_install INSTALL_MOD_PATH=$VM/arm-modules

Копируем ядро.

# cp $VM/linux-2.6.32.5/arch/arm/boot/zImage $VM/debug-kernel

Initrd


Распаковываем оригинальный initrd.

# mkdir -p $VM/initrd
# cd $VM/initrd
# gunzip - < $VM/initrd.img-2.6.32-5-versatile | cpio -id

Заменяем модули на собранные нами.

# cd $VM/initrd/lib/modules
# rm -rf *
# cp -a $VM/arm-modules/lib/modules/* .

Запаковываем новый initrd.

# cd $VM/initrd/
# find . | cpio --create --format='newc' | gzip > $VM/debug-initrd

Запуск


Проверяем, что новое ядро успешно загружается.

# qemu-system-arm \
  -m 2047 -M versatilepb -hda $VM/vm.qcow2 \
  -net nic,vlan=1,macaddr=de:ad:be:af:11:22 \
  -net tap,vlan=1,ifname=myvm,script=$VM/vm-ifup.sh \
  -kernel $VM/debug-kernel \
  -initrd $VM/debug-initrd \
  -append "root=/dev/sda1"

Отладка ядра


Запускаем QEMU с gdbserver (опции -s и -S).

# qemu-system-arm -s -S \
  -m 2047 -M versatilepb -hda $VM/vm.qcow2 \
  -net nic,vlan=1,macaddr=de:ad:be:af:11:22 \
  -net tap,vlan=1,ifname=myvm,script=$VM/vm-ifup.sh \
  -kernel $VM/debug-kernel \
  -initrd $VM/debug-initrd \
  -append "root=/dev/sda1"

Запускаем отладчик.

# cd $VM/linux-2.6.32.5
# arm-none-linux-gnueabi-gdb
(gdb) file vmlinux
(gdb) target remote :1234
(gdb) continue
.....
^C
(gdb) backtrace
(gdb) list

Можно также поставить breakpoint на функцию icmp_reply() и попинговать виртуалку.


Отладка модуля


Пишем простой модуль.

# cat $VM/hello/Makefile
KERNEL := $VM/linux-2.6.32.5

ARCH := arm
CROSS := arm-linux-gnueabi-

CFLAGS_hello.o := -g
obj-m += hello.o

all:
    make -C $(KERNEL) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS) modules

clean:
    make -C $(KERNEL) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS) clean
# cat $VM/hello/hello.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>

void goodbye(void); // без static!

void goodbye(void)
{
    printk(KERN_INFO "goodbye!\n");
}

static int __init hello_init(void)
{
    printk(KERN_INFO "hello_init\n");
    return 0;
}

static void __exit hello_exit(void)
{
    goodbye();
    printk(KERN_INFO "hello_exit\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("hello module");

Собираем модуль.

# cd $VM/hello
# make

Копируем hello.ko на виртуалку и загружаем.

# insmod hello.ko
# cat /sys/module/hello/sections/.text
0x0000000000000001
# cat /sys/module/hello/sections/.data
0x0000000000000002
# cat /sys/module/hello/sections/.bss
0x0000000000000003

Загружаем символы модуля в GDB.

(gdb) add-symbol-file ../hello/hello.ko \
      0x0000000000000001 -s .data 0x0000000000000002 -s .bss  0x0000000000000003
(gdb) b goodbye
(gdb) c

Выгружаем модуль, чтобы сработал breakpoint.

# rmmod hello

Отладка в Emacs


Советую настроить Emacs так:

(setq gdb-non-stop-setting nil)
(setq gdb-many-windows t)

И запускать отладчик так:

M-x gdb
arm-linux-gnueabi-gdb -i=mi

И затем в консоли GDB:

(gdb) file vmlinux
(gdb) target remote :1234
.....

PowerPC


Для PowerPC последовательность действий та же, за исключением перечисленных ниже моментов.


QEMU


QEMU был собран с флагом ppc.


Debian


Использовался Debian Wheezy с ядром 3.x.


Т.к. QEMU под PowerPC поддерживает загрузчик, вручную передавать ядро и initrd из установщика не требуется (опции -kernel и -initrd).


Crossdev


Использовался тулчейн powerpc-unknown-linux-gnu и соответственно отладчик cross-powerpc-unknown-linux-gnu/gdb.


Ядро


Проверялись ядра 2.6.39 и 3.6.11 с флагами сборки:

ARCH="powerpc" CROSS="powerpc-unknown-linux-gnu-"

Вместо versatile_defconfig и включения перечисленных опций ядра был использован конфиг ядра из Debian Wheezy.


В QEMU опцией -kernel передавался файл $VM/linux-3.6.11/vmlinux.

12 янв. 2014 г.

Debian Wheezy 64 bit, Fglrx, Steam

Список пакетов fglrx (закрытый драйвер ATI):

fglrx-atieventsd
fglrx-control
fglrx-driver
fglrx-glx
fglrx-modules-dkms
glx-alternative-fglrx
libfglrx
libfglrx-amdxvba1
libgl1-fglrx-glx


libfglrx:i386
libgl1-fglrx-glx:i386


Установка:

$ sudo apt-get install fglrx-atieventsd fglrx-control fglrx-driver fglrx-glx fglrx-modules-dkms glx-alternative-fglrx libfglrx libfglrx-amdxvba1 libgl1-fglrx-glx libfglrx:i386 libgl1-fglrx-glx:i386

$ sudo aticonfig --initial

Установщик Steam: Steam Installer for Wheezy

$ wget https://dl.dropboxusercontent.com/u/82856418/Steam-Installer-For-Wheezy/steam-debian_1.0.0.45-4_all.deb
$ sudo dpkg -i steam-debian_1.0.0.45-4_all.deb

9 янв. 2014 г.

Gentoo: /usr/lib64/libc.so.6: invalid ELF header

Ошибка при сборке или запуске программ:

/usr/lib64/libc.so.6: invalid ELF header

У меня эта ошибка проявилсь в следующей конфигурации:

$ ls -l /usr/lib64/libc.so.6
lrwxrwxrwx 1 root root 18 Jul  9  2013 libc.so.6 -> /usr/lib64/libc.so


$ file /usr/lib64/libc.so
/usr/lib64/libc.so: ASCII text


cat /usr/lib64/libc.so
/* GNU ld script
   Use the shared library, but some functions are only in
   the static library, so try that secondarily.  */
OUTPUT_FORMAT(elf64-x86-64)
GROUP ( /lib64/libc.so.6 /usr/lib64/libc_nonshared.a  AS_NEEDED ( /lib64/ld-linux-x86-64.so.2 ) )


$ ls -l /lib/libc.so.6
lrwxrwxrwx 1 root root 12 Jan  9 18:30 /lib/libc.so.6 -> libc-2.17.so*
 

$ file /lib/libc-2.17.so
/lib/libc-2.17.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.16, stripped


То есть, /usr/lib64/libc.so.6 -- это linker script (используется при линковке программы), а /lib/libc.so.6 -- это настоящая библиотека (используется при запуске программы).

Проблема проявляется, если при запуске вместо настоящей библиотеки загрузчик находит linker script.

Решение 1

$ export LD_LIBRARY_PATH=/lib64

Решение 2

# rm /usr/lib64/libc.so.6
# ln -s /lib/libc.so.6 /usr/lib64/
# ldconfig

3 нояб. 2013 г.

Debian: потеря темы GTK2 в KDE

Симпотмы проблемы:
  • в некоторых приложениях на pygtk2, при запуске в KDE, вместо выбранной темы GTK2 (например, QtCurve) используется страшная дефолтная тема;
  • gtk-chtheme отображает выбранную тему, а gtk-theme-switch2 всегда отображает дефолтную.

Возможное решение:

$ ln -s ~/.gtkrc-2.0 ~/.gtkrc-2.0-kde

7 июл. 2013 г.

Unix: job control в подсистеме терминалов

Ссылки


Что такое терминал?


Ядро предоставляет программам абстракцию терминала, которая включает в себя:
  • Ввод/вывод
    Терминал является обычным файлом. Простым программам достаточно уметь читать и записывать поток байтов.

  • Line discipline
    Терминал имеет несколько режимов обработки вводимых символов, которые называются line discipline.

    В режиме по-умолчанию ядро реализует минимальные средства построчного редактирования, вроде удаления символов и перемещения по тексту. После нажатия клавиши ввода отредактированная строка добавляется в поток байтов, который затем читает приложение.

    Как работает этот режим можно увидеть, набрав команду cat.

  • Job control
    Терминал хранит информацию о группах процессов, которые к нему привязаны, а процессы хранят информацию о своем терминале.

    Ядро предоставляет набор системных вызовов, которые позволяют реализовать классический шелл с возможностью управлять множеством задач, разделяющих один терминал.

    Job control вступает в силу, когда вы запускаете фоновую задачу, вроде gedit &, нажимаете ^C или ^Z, либо используете команды на подобие fg, bg и kill %1.

Механизм vs политика


Терминал – это один из редких примеров, когда в ядре UNIX реализована политика, а не механизм. Это обусловлено тем, что терминал позволяет незаметно для программы заменить взаимодействие с другой программой на взаимодействие с пользователем {1}.

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

Вот что получается в результате:
  1. Ядро реализует простой режим редактирования для взаимодействия с простыми программами; сложные программы, вроде vim, readline и curses, отключают его и реализуют обработку и отображение символов сами.
  2. Ядро предоставляет системные вызовы для управления группами процессов и реализует взаимодействие с этими группами драйвера терминала и line descipline с помощью сигналов; оболочки, вроде bash и zsh, создают группы процессов и предоставляют команды для переключения между этими группами.
Благодаря этому пользователю одинаково просто работать с любыми текстово-ориентированными программами, а для большинства программ взаимодействие с пользователем прозрачно.

В этом посте рассматривается второй пункт.

Параметры процессов и терминалов


Параметры процесса


Каждый процесс имеет набор параметров, которые используются для реализации job control.
  • Controlling tty
    Терминал, к которому привязан процесс.

  • pid (process id)
    Идентификатор процесса.

  • pgid (process group id)
    Идентификатор группы процессов. Равен pid лидера группы процессов (первый процесс в группе).

    Для каждого нового pipeline шелл создает новую группу и все процессы и их потомки, запущенные в этом pipeline, принадлежат этой группе. Вместе эти процессы называются "задачей" ("job").

    Группы процессов нужны для того, чтобы можно было выделить "активную" группу процессов ("foreground process group") для данного терминала.

  • sid (session id)
    Идентификатор сессии. Равен pid лидера сессии (первый процесс в сессии).

    Для каждого нового входа в систему програма login(1), которую обычно вызывает getty, создает новую сессию; шелл и его потомки принадлежат этой сессии.

    Сессии нужны для того, чтобы можно было найти все процесы, использующие данный терминал. У каждого терминала есть не более одной ассоциированной с ним сессии, она называется "controlling process group".

Параметры терминала


Каждый терминал ссылается на сессию, связанную с ним, и активную группу процессов.
  • Foreground process group
    Идентификатор pgid группы процессов, которая в данный момент активна.

    Процессам из активной группы разрешено читать и писать в терминал. Если процесс пытается читать или писать из своего управляющего терминала, но при этом не находится в активной группе процессов, драйвер посылает ему сигналы SIGTTIN (при чтении) и SIGTTOU (при записи, если включен флаг TOSTOP у терминала {2}. По-умолчанию они приостанавливают процесс до тех пор, пока кто-нибудь, например команда fg, не пошлет ему сигнал SIGCONT.

    Это очень удобно, так как в противном случае, набрав команду vim &, пользователь лишился бы возможности набрать другую комнду, потому что vim, запущенный в фоне, мог бы захватывать введенные символы и замусоривать вывод.

    NB: При вводе/выводе в терминал, который не является управляющим терминалом процесса, никакие сигналы не генерируются, но для этого обычно требуются права root.

    При нажатии специальных клавиш драйвер line discipline посылает сигналы процессам из активной группы:
    • SIGINT (^C)
    • SIGQUIT (^\)
    • SIGTSTP (^Z)

    Сигнал SIGWINCH также обычно посылается процессам активной группы.

  • Controlling process group
    Идентификатор sid сессии, ассоциированной с данным терминалом.

    При разрыве соединения с терминалом, процессам с этим sid посылается сигнал SIGHUP, который по-умолчанию убивает процесс. Разрыв соединения может возникнуть, если терминал является внешним устройством, соединенным через последовательный порт.

Примеры


Вот примеры команд, в которых все процессы будут помещены в одну группу:
$ ls
$ find / | wc -l   # При нажатии ^C или ^Z сигнал будет послан обоим процессам.
А в этом примере будет создано две группы процессов:
$ vim & cat -n
В первой группе – vim, который будет приостановлен сигналом SIGTTIN до тех пор, пока пользватель не наберет fg. Во второй группе – cat -n, эта группа станет активной группой терминала. Оба процесса будут находится в одной и той же сессии.

Основные функции


Создание группы процессов


Текущий процесс можно переместить в новую группу вызвовом setpgrp() или setpgid(0, 0). После успешного завершения вызова, pgid процесса становится равным его pid; процесс становится лидером новой группы.

Создание сессии


Текущий процесс можно переместить в новую сессию вызовом setsid(). После успешного завершения вызова, sid и pgid процесса становятся раным его pid; процесс становится лидером новой сессии и новой группы и не имеет управляющего терминала.

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

Вот пример запуска программы в отдельной сессии:
if (fork() == 0) {
  // Здесть гарантированно pgid != pid.
  if (setsid() == -1) {
    perror("setsid");
    exit(1);
  }

  execlp("bash", "bash", NULL);
}
Запустив этот код, вы можете увидеть следующее сообщение:
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
Сообщение означает, что сессия, в которой был запущен bash, не является controling process group для текущего терминала, поэтому полноценное управление процессами будет недоступно.

Если теперь набрать команду cat, а затем нажать ^C, убиты будут и cat, и bash, так как bash не создавал для cat отедльную группу.

Скорее всего вы увидели эту ошибку, так как программа была запущена из шелла, а значит controlling process group терминала уже ссылается на другую сессию, в которую входит этот шелл.

Чтобы этого не произошло, нужно чтобы controlling process group терминала был пуст, что справедливо, когда нет процессов, ассоциированных с данным терминалом. Например, это так в тот момент, когда login(1) запускает bash.

Управление группами, связанными с терминалом


С помощью функций tcgetsid() и tcgetpgrp() можно узнать, соотвественно, controlling process group и foreground process group управляющего терминала текущего процесса.

С помощью функции tcsetpgrp() можно установить foreground process group управляющего терминала текущего процесса.

Функции для установки controlling process group нет. Вместо этого этот параметр автоматически обновляется при возникновении одного из событий во время установки управляющего терминала какого-нибудь процесса (см. следующий раздел):
  • Нет ни одного процесса, управляющий терминал которого равен данному (то есть controlling process group терминала пуст) и процесс устанавливает свой управляющий терминал равным данному.
  • Процесс захватывает терминал, отбирая его у текущей controlling process group.

В обоих случаях controlling process group становится раным sid процесса.

Управляющий терминал процесса

  • Процесс может установить свой управляющий терминал следующими способами:
    • С помощью open()
      Если процесс открывает файл устройства терминала вызовом open() без флага O_NOCTTY и у процесса нет управляющего терминала, этот терминал становится его управляющим терминалом.

    • С помощью ioctl(TIOCSCTTY)
      Если процесс является лидером сессии и не имеет управляющего терминала, он может установить управляющий терминал вызовом ioctl(fd, TIOCSCTTY, arg).

      Если терминал уже имеет непустой controlling process group:
      • вызов ioctl(fd, TIOCSCTTY, 0) (arg != 1) вернет ошибку EPERM;
      • вызов ioctl(fd, TIOCSCTTY, 1) (arg = 1) захватит терминал, пошлет всем процессам из текущей controlling process group сигналы SIGHUP и SIGCONT и заменит ее на sid вызывающего процесса (для этого нужны права root).

      В последнем случае процессы, которые не умрут от SIGHUP, смогут по-прежнему читать и писать в терминал, но не будут участвовать в job control, то есть:
      • им больше не будут доставляться сигналы, связанные с терминалом;
      • они смогут хаотично захватывать введенные символы и замусоривать вывод;
      • им не будут доступны функции вроде tcgetsid() и tcgetpgrp().

  • Процесс может отказаться от управляющего терминала следующими способами:
    • С помощью setsid()
    • С помощью ioctl(TIOCNOTTY)

Примечания


{1} К статье The TTY demystified есть интересный комментарий о причинах реализации line discipline в ядре:
It's not to keep application simple that the line discipline is in the kernel. The unix philosophy is quite the contrary, to keep the KERNEL simple, and deport to user space the handing of complexities that they don't want inside the kernel. (See for example interrupted system calls and signal handling, but that's another topic).

The reason why the line discipline is inside the kernel, is to avoid context switches at the reception of each character (which in the early times of small core memories, would imply swap-outs and swap-ins!). So the line discipline keeps in a kernel buffer a line of input, and since it's simple enough to test for a specific byte and decrement a counter to implement the backspace "editing" (and a few other simple editing functions), it's done there.

The alternative, is to use the raw mode, where the characters are forwarded to the application as soon as they're received, which is needed for more sophisticated editors, like the (at the time) famously known Eight Megabytes And Constantly Swapping editor (emacs). And indeed, since emacs had to use this raw mode, which implies a context switch at the reception of each character typed, it was constantly swapping when the computers hadn't enough memory to keep emacs and all the other programs in core.
{2} Флаг TOSTOP можно включить командой stty tostop.