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 подписка на новые темы