LINUX.ORG.RU

Сообщения AccumPlus

 

Ассемблер в среде BIOS. Передача управления

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

Пишу загрузчик на языке GNU Assembler-а. Загрузчик своеобразный... Его основная работа - загрузить оригинальный загрузчик и передать ему управление. Для этого, я подменяю оригинальный загрузчик своим (записываю в первый сектор раздела), а оригинальный сохраняю в другом секторе.

При этом мой загрузчик делает следующие действия:

  1. После загрузки в оперативную память по адресу 0x7C00 он копирует себя же в другое место (например, по адресу 0xA000)
  2. Далее прыгает на этот адрес со смещением (чтобы не выполнять тот же код) и продолжает работу оттуда
  3. Читает раздел с оригинальным загрузчиком в память по адресу 0x7C00
  4. Прыгает по адресу 0x7C00 и передаёт управление оригинальному загрузчику

Проблемы возникают при работе с адресами.

Есть фишка, что адрес рассчитывается относительно соответствующего сегментного регистра по формуле:

реальный_адрес = (сегментный_регистр << 4) + относительный_адрес

Но когда эта формула срабатывает - я не всегда могу понять.

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

	movw $0x0, %si		# откуда = DS:SI = (0x07C0 << 4) + 0x0 = 0x7C00
	movw $0x0A00, %ax
	movw %ax, %es		# ES = 0x0A00
	movw $0x0, %di		# куда ES:DI = (0x0A00 << 4) + 0x0 = 0xA000
	movw $0x0200, %cx	# сколько = 512
	rep movsb

По комментариям видно, что я предполагаю, что как адрес «откуда», так и адрес «куда» рассчитывается именно с учётом сдвига значения сегментного регистра.

Далее по команде jmp. Если я не ошибаюсь, то при передаче ей в качестве аргумента имени метки, итоговый адрес перехода рассчитывается как раз относительно значения сегментного регистра кода cs. Тогда, если я обнулю этот регистр, то по сути я могу передать команде в качестве аргумента абсолютный адрес:

	movw $0x0, %ax
	movw %ax, %cs
	jmp 0xA000

Но это не работает. Более того, для меня стало неожиданным, что сработал код:

	jmp 0x0A00

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

_boot:
# Устанавливаем начальное значение сегментных регистров
	cli
	movb %dl, iBootDrive
	movw $0x07C0, %ax
	movw %ax, %ds
	movw %ax, %es
	movw %ax, %ss
	movw %ax, %sp
	sti

	mWriteString msgHello

Кроме этого, меня интересует, как пропустить уже выполненный код. Логичным для меня является что-то вроде этого:

	jmp 0xA000 + continue
	continue:

То есть выполняется прыжок в адрес, равный 0xA000 (там скопированный загрузчик) + значение метки. Вроде бы всё логично, но следующий код опять же просто выводит два сообщения:

	jmp 0x0A00 + continue
	continue:

Как будто просто игнорирует смещение метки и выполняет код заново.

Плюс ко всему, инициализация сегментного регистра кода cs вообще ломает исполнение кода. Даже элементарный вывод строки не может выполниться.

Возможно, следующая информация будет полезна. Собираю загрузчик так:

as boot.s -o boot.o
ld -Ttext 0x0000 --oformat=binary boot.o -o boot.bin

Директиву org не указываю.

Таким образом, у меня вопрос: Как правильно реализовать копирование памяти и передачу туда управления?

Спасибо!

 ,

AccumPlus
()

Способы указания версии ПО

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

Есть проект, написанный на C++ с использованием системы контроля версий Git и системы сборки CMake.

Я хочу сделать так, чтобы в конечный исполняемый файл «вшивалась» версия этого проекта. При этом я не хочу следить за номером версии.

Принимая во внимание тот факт, что версия в наиболее распространённом случае состоит из набора «подверсий» (major, minor, build...), я бы хотел иметь возможность указывать, какую часть версии инкрементировать.

Я знаю, что в Git используются тэги для указания меток версии. В CMake же есть возможность указания макросов через конфигурационные файлы *.in.

Первый способ не обеспечит мне «вшивание» версии в ПО (я не смогу сделать что-то вроде «myProg --version»). В то же время для использования конфиг файлов Cmake-а я буду вынужден перед изменением версии залазить в CMakeLists.txt и править числа прямо там, что, как по мне, выглядит очень грубо.

Так вот вопрос мой в том, есть ли уже какие-либо готовые решения указания версии ПО?

Спасибо!

 , , ,

AccumPlus
()

Шифрование раздела вручную

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

Возникла задача зашифровать раздел «своими руками». Алгоритм шифрования не так важен, поэтому пусть будет простейший, например, XOR.

Написал программу на C++:

#include <iostream>
#include <fstream>

int main()
{
	std::fstream filestream;

	const char key = 0b11111111;

	filestream.rdbuf()->pubsetbuf(0, 0);
	filestream.open("/dev/sdc1",  std::ios_base::binary | std::ios_base::in | std::ios_base::out);

	char b;

	while (filestream.read(&b, 1))
	{
		b ^= key;
		filestream.seekp((int)filestream.tellp() - 1);
		filestream.write(&b, 1);
	}

	filestream.close();

	return 0;
}

Запускать, очевидно, из-под рута. Она работает, и девайс шифруется, но это длится крайне долго даже для флешки объёмом 2Гб.

Подскажите, есть ли более быстрый способ выполнить это? Или просто, как бы это сделали вы.

Спасибо!

 , ,

AccumPlus
()

Драйвер для дешифрования устройства

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

Возникла задача создать минимальный образ, зашифровать его простейшим способом и написать драйвер, который будет дешифровать его «на лету».

Сразу оговорюсь, что почти всё делаю по интуиции, и некоторые моменты могут оказаться для вас абсурдными.

Создаю образ:

dd if=/dev/zero of=myFS bs=1024 count=60
mkfs.ext2 myFS

Шифрую XOR-ом:

int main()
{
	std::fstream streamIn;
	std::fstream streamOut;

	const char key = 0b00001111;

	streamIn.open("myFS", std::ios_base::binary | std::ios_base::in);
	streamOut.open("newFS", std::ios_base::binary | std::ios_base::trunc | std::ios_base::out);

	char b;
	while (streamIn.read(&b, 1))
	{
		b ^= key;
		streamOut.write(&b, 1);
	}

	streamIn.close();
	streamOut.close();

	return 0;
}

Далее пишу драйвер. Он основан на device-mapper target-е, и основная функция обработки запросов имеет следующий прототип:

static int sddm_target_map(struct dm_target *ti, struct bio *bio);

То есть я имею в своём распоряжении структуру bio, в которой, как мне это видится, я должен дешифровать все байты. Хороших примеров я не нашёл, так что прошу не заряжать помидоры) Сделал так:

struct bio_vec vec;
struct bvec_iter it;
unsigned int len;
char *addr;
unsigned int i;

bio_for_each_segment(vec, bio, it)
{
	len = vec.bv_len;
	addr = (char*)(page_address(vec.bv_page) + vec.bv_offset);

	for (i = 0; i != len; ++i)
	{
		*(addr+i) ^= 0b00001111;
	}
}

В драйвере device-mapper target-а необходимо изменить целевое устройство bio->bi_bdev на реальное и повторить запрос submit_bio(bio). Хотя к данной задаче это не особо относится, но может быть кому-то пригодится.

Очевидно, что не всё так просто. При попытке монтирования моего устройства получаю следующее:

mount: wrong fs type, bad option, bad superblock on /dev/mapper/mydevice

Видел ещё такой вариант:

bio_for_each_segment(vec, bio, it)
{
	len = vec.bv_len;
	addr = kmap_atomic(vec.bv_page);
	pointer = (char *)(addr + vec.bv_offset);

	for (i = 0; i != len; ++i)
	{
		*(pointer+i) ^= 0b00001111;
	}

	kunmap_atomic(addr);
}

Но и он выдаёт ту же ошибку.

Подскажите, как правильно реализовать задуманное! Спасибо!

 , ,

AccumPlus
()

Device-mapper target модуль ядра

На основе этой статьи разрабатываю модуль ядра для device-mapper-а. Статья написана для какой-то 2.6+ версии ядра. Мне же надо её преобразовать к версии 2.4.

Основная проблема в том, что в ядре 2.4 используется иной формат обмена данными между устройствами. Если в современных системах сообщения хранятся в структуре bio, то в 2.4 версии используется некая структура buffer_head.

Примеров использования я не нашёл, качественной документации тоже. Поэтому делал по интуиции.

Вот получившийся код (специально для сравнения версий оставил директивы define):

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/device-mapper.h>
 
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#include <linux/bio.h>
#endif
 
#include <linux/fs.h>
#include <linux/kdev_t.h>
 
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,32)
#include <linux/mm.h>
#endif
 
struct Sddm_target
{
    struct dm_dev *dev;
    sector_t start;
};
 
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,32)
static int sddm_target_map(struct dm_target *ti, struct buffer_head *bh, int rw, union map_info *map_context)
#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32)
static int sddm_target_map(struct dm_target *ti, struct bio *bio, union map_info *map_context)
#else
static int sddm_target_map(struct dm_target *ti, struct bio *bio)
#endif
{
    struct Sddm_target *mdt;
 
    mdt = (struct Sddm_target *) ti->private;
 
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,32)
    bh->b_dev = mdt->dev->dev;
#else
    bio->bi_bdev = mdt->dev->bdev;
#endif
 
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,32)
    submit_bh(rw, bh);
#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32)
    submit_bio(bio->bi_rw, bio);
#else
    submit_bio(bio);
#endif
 
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,32)
    return 0;
#else
    return DM_MAPIO_SUBMITTED;
#endif
}
 
static int sddm_target_ctr(struct dm_target *ti, unsigned int argc, char **argv)
{
    struct Sddm_target *mdt;
    unsigned long start;
    unsigned long len;
    int err;
 
    if (argc != 2)
    {
        ti->error = "Invalid argument count";
        return -EINVAL;
    }
 
    mdt = (struct Sddm_target*)kmalloc(sizeof(struct Sddm_target), GFP_KERNEL);
 
    if (mdt == NULL)
    {
        printk(KERN_CRIT "\n Mdt is null\n");
        ti->error = "dm-basic_target: Cannot allocate linear context";
        return -ENOMEM;
    }       
 
    if (sscanf(argv[1], "%lu", &start) != 1)
    {
        ti->error = "dm-basic_target: Invalid deviceee sector";
        kfree(mdt);
        printk(KERN_CRIT "\n>>out function basic_target_ctr with errorrrrrrrrrr \n");           
        return -EINVAL;
    }
 
    mdt->start = (sector_t)start;
 
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,32)
    err = dm_get_device(ti, argv[0], ti->begin, ti->len, dm_table_get_mode(ti->table), &mdt->dev);
#else
    err = dm_get_device(ti, argv[0], dm_table_get_mode(ti->table), &mdt->dev);
#endif
    if (err)
    {
        ti->error = "dm-basic_target: Device lookup failed";
        kfree(mdt);
        return -EINVAL;
    }
 
    ti->private = mdt;
 
    return 0;
}
 
static void sddm_target_dtr(struct dm_target *ti)
{
    struct Sddm_target *mdt = (struct Sddm_target *) ti->private;
 
    dm_put_device(ti, mdt->dev);
    kfree(mdt);
}
 
static struct target_type sddm_target = {
        .name = "sddm_target",
        .version = {1,0,0},
        .module = THIS_MODULE,
        .ctr = sddm_target_ctr,
        .dtr = sddm_target_dtr,
        .map = sddm_target_map,
};
 
static int __init init_sddm_target(void)
{
    int result;
 
    result = dm_register_target(&sddm_target);
 
    return 0;
}
 
static void __exit cleanup_sddm_target(void)
{
    dm_unregister_target(&sddm_target);
}
 
module_init(init_sddm_target);
 
module_exit(cleanup_sddm_target);
 
MODULE_LICENSE("GPL");

Сейчас модуль должен просто транслировать сообщения, направленные на виртуальное устройство, на реальное устройство. Это выполняется в «маппирующей» функции sddm_target_map, а именно во фрагменте:

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,32)
    bh->b_dev = mdt->dev->dev;
#else
    bio->bi_bdev = mdt->dev->bdev;
#endif

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,4,32)
    submit_bh(rw, bh);
#elif LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,32)
    submit_bio(bio->bi_rw, bio);
#else
    submit_bio(bio);
#endif

То есть просто меняю целевое устройство и повторяю запрос.

Использую я представленный модуль в момент загрузки системы для создания «маппирующего» блочного устройства. Это устройство в дальнейшем должно замещать root-раздел. (Смежный вопрос).

Код для версий 2.6+ работает. Для 2.4 - нет. Виртуальное устройство создаётся, несколько раз выполняется функция «маппинга», но процесс монтирования виснет, выдавая следующее сообщение:

kjoutnald starting. Commit interval 5 seconds

Предполагаю, что ошибка кроется либо в функции-конструкторе sddm_target_ctr, либо в sddm_target_map.

Прошу знатоков 2.4 ядра и просто kernel-space программирования помочь! Спасибо!

 ,

AccumPlus
()

Правка initrd файла

Необходимо изменить initrd файл так, чтобы в конечном итоге иное блочное устройство использовалось в качестве корневого. При этом используются системы с версиями ядра 2.4 и 2.6 :(

В системе с ядром 2.4 заключительная часть init-файла (linuxrc) образа initrd выглядит следующим образом:

...
mkrootdev /dev/root
echo 0x0100 > /proc/sys/kernel/real-root-dev
mount -o acl,nosecdel,secrm --ro -t ext3 /dev/root /sysroot
pivot_root /sysroot /sysroot/initrd
umount /initrd/proc

В системе с ядром 2.6 иначе:

...
mkrootdev -t ext4 -o defaults,ro /devv/sda3
mount /sysroot
setuproot
switchroot

На данном этапе у меня возникает путаница, что именно делает каждая строка в обоих приведённых фрагментах. Подробному разъяснению этого момента я буду очень рад.

Кроме того, мне надо изменить файлы так, чтобы в качестве корневого монтировалось другое устройство, создаваемое в момент загрузки. А создаётся это устройство с помощью device-mapper-а. Не так важно, что именно делает получаемое устройство, но важно, что оно приводит реальное устройство в состояние «занято».

Так как иное устройство монтируется в качестве корневого, то я меняю (а в идеале удаляю) параметр root из конфигов загрузчиков (для LILO, например, вариант только удалить этот параметр, ибо LILO отказывается конфигурироваться, если устройство не существует).

Виртуальное устройство создаю так:

ld-linux.so.2 /bin/dmsetup create root_part /opt/bin/table

Устройство создаётся успешно по пути /dev/mapper/root_part

Подскажите, как изменить приведённые выше фрагменты. Спасибо!

 ,

AccumPlus
()

RSS подписка на новые темы