В первой части нашей статьи мы рассказали о том, каким образом можно получить простую программу “Hello World”, которая запускается без операционной системы и печатает сообщение на экран.
В этой части статьи, хочется развить получившийся в первой части код таким образом, чтобы он мог быть отлажен через GDB, компилировался через оболочку Visual Studio и печатал на экран список PCI устройств.
! ВАЖНО!: Все дальнейшие действия могут успешно осуществляться только после успешного прохождения всех 6-ти шагов описанных в первой части статьи).
Учимся отлаживать программу
Основная статья: Использование отладчика GDB по максимуму
Как отладить код kernel.bin? Для этого нужно добавить в kernel.bin симовлы для отладки и запустить отладчик:
1. Добавим опцию компилятора в файле makefile, чтобы он генерировал отладочные символы:
CFLAGS = -Wall -fno-builtin -nostdinc -nostdlib -ggdb3
2. Добавим пару строк на этапе сборки, чтобы на диск записывался kernel.bin без символов (такой файл можно сделать при помощи утилиты strip). Для этого нужно исправить цель kernel.bin в makefile:
kernel.bin: $(OBJFILES)
$(LD) -T linker.ld -o $@ $^
cp $@ $@.dbg
strip $@
тогда:
kernel.bin – не содержит символы – его можно запускать;
kernel.bin.dbg – содержит и символы и код – его можно скормить отладчику.
3. Установим отладчик:
sudo apt-get install cgdb
4. Перекомпилируем программу:
make clean
make all
sudo make image
5. Запустим qemu с опцией ожидания отладчика:
sudo qemu-system-i386 -s -S -hda hdd.img &
6. Запустим отладчик c указанием файла с символами:
cgdb kernel.bin.dbg
7. В отладчике подключимся к qemu и поставим breakpoint сразу на функции main:
(gdb) target remote localhost:1234
(gdb) break main
8. Попадаем в main и отлаживаем ее:
(gdb) c
(gdb) n
(gdb) n
Таким образом, получается мощный инструмент отладки. Этот способ будет работать для QEMU, а для того, чтобы отладить программу непосредственно на железе, необходимо подключить модуль отладчика к нашей программе – это мы рассмотрим в одной из следующих статей.
Компиляция из Visual Studio
Основная статья: Использование оболочки Visual Studio 2010 для компиляции проектов с помощью gcc в Linux
Как работать с полученным кодом из Visual Studio? Следуя инструкциям в статье собираем проект Visual Studio, не создавая проект на Linux – он уже есть.
1. Установим в системе ssh:
sudo apt-get install ssh
2. Располагаем исходники проекта с kernel.bin на shared directory для виртуальной машины.
3. Устанавливаем утилиту plink в папку tools и проверяем ее работу.
4. Создаем проект Visual Studio следуя инструкциям и получаем такое дерево:
projkernel.c
projloader.s
projcommonprintf.c
projcommonscreen.c
projincludeprintf.h
projincludestdarg.h
projincludescreen.h
projincludetypes.h
projmakefile
projlinker.ld
projtoolsplink.exe
projkernelkernel.sln
projkernelkernel.suo
projkernelkernel.sdf
projkernelvskernel.vcxproj
projkernelvskernel.vcxproj.filters
projkernelvsmake_vs.props
5. Формируем файл ”projkernelvsmake_vs.props” так же по инструкции:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="RemoteBuildLocals">
<RblFolder>proj</RblFolder>
<RblIncludePath>$(SolutionDir)include</RblIncludePath>
<RblExecute>sudo make image; sudo qemu-system-i386 -hda hdd.img</RblExecute>
</PropertyGroup>
<PropertyGroup Label="RemoteBuildSettings">
<RbHost>192.168.1.8</RbHost>
<RbUser>user</RbUser>
<RbPassword>123456</RbPassword>
<RbRoot> ~/Desktop/_habr</RbRoot>
</PropertyGroup>
<PropertyGroup Label="RemoteBuild">
<RbToolArgs> -pw $(RbPassword) $(RbUser)%40$(RbHost) cd $(RbRoot); cd $(RblFolder);</RbToolArgs>
<RbToolExe>$(SolutionDir)toolsplink -batch $(RbToolArgs)</RbToolExe>
<RbBuildCmd>$(RbToolExe) make all</RbBuildCmd>
<RbRebuildAllCmd>$(RbToolExe) make rebuild</RbRebuildAllCmd>
<RbCleanCmd>$(RbToolExe) make cleanall</RbCleanCmd>
<RbExecuteCmd>$(RbToolArgs) $(RblExecute)</RbExecuteCmd>
<RbIncludePath>$(RblIncludePath)</RbIncludePath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<NMakeBuildCommandLine>$(RbBuildCmd)</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>$(RbRebuildAllCmd)</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>$(RbCleanCmd)</NMakeCleanCommandLine>
<IncludePath>$(RbIncludePath)</IncludePath>
<LocalDebuggerCommand>$(SolutionDir) toolsplink</LocalDebuggerCommand>
<LocalDebuggerCommandArguments>$(RbExecuteCmd)</LocalDebuggerCommandArguments>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<NMakeBuildCommandLine>$(RbBuildCmd)</NMakeBuildCommandLine>
<NMakeReBuildCommandLine>$(RbRebuildAllCmd)</NMakeReBuildCommandLine>
<NMakeCleanCommandLine>$(RbCleanCmd)</NMakeCleanCommandLine>
<IncludePath>$(RbIncludePath)</IncludePath>
<LocalDebuggerCommand>$(SolutionDir)toolsplink</LocalDebuggerCommand>
<LocalDebuggerCommandArguments>$(RbExecuteCmd)</LocalDebuggerCommandArguments>
</PropertyGroup>
</Project>
6. Меняем файл ”projkernelvskernel.vcxproj ” так же по инструкции:
<Import Project="$(VCTargetsPath)Microsoft.Cpp.props" />
<Import Project="$(SolutionDir)vsmake_vs.props" />
<ImportGroup Label="ExtensionSettings">
7. В итоге должно получиться примерно следующее:
8. Проверяем, что все работает:
Таким образом, для дельнейшей разработки нашей программы можно использовать оболочку Visual Studio, хоть и компилятор GCC на Linux.
Сканирование устройств PCI
Основная статья: Как найти PCI устройства без операционной системы
Как теперь просканировать системную шину PCI на наличие устройств? Следуя инструкциям в статье выполняем следующие действия (загрузочный образ уже готов, поэтому только добавляем код сканирования PCI):
1. Добавляем в файл includetypes.h, следующее определение типа:
typedef unsigned long u32;
typedef unsigned short u16;
typedef unsigned char u8;
2. Добавляем файл includeio.h, который без изменений можно взять из проекта bitvisor из каталога (includeio.h).
3. Добавляем файл includepci.h, который содержит основные определения для функций работы с PCI. Он имеет следующее содержимое:
#ifndef _PCI_H
#define _PCI_H
#include "types.h"
#define PCI_CONFIG_PORT 0x0CF8
#define PCI_DATA_PORT 0x0CFC
#define PCI_MAX_BUSES 255
#define PCI_MAX_DEVICES 32
#define PCI_MAX_FUNCTIONS 8
#define PCI_HEADERTYPE_NORMAL 0
#define PCI_HEADERTYPE_BRIDGE 1
#define PCI_HEADERTYPE_CARDBUS 2
#define PCI_HEADERTYPE_MULTIFUNC 0x80
typedef union
{
struct
{
u16 vendorID;
u16 deviceID;
u16 commandReg;
u16 statusReg;
u8 revisionID;
u8 progIF;
u8 subClassCode;
u8 classCode;
u8 cachelineSize;
u8 latency;
u8 headerType;
u8 BIST;
} __attribute__((packed)) option;
u32 header[4];
} __attribute__((packed)) PCIDevHeader;
void ReadConfig32(u32 bus, u32 dev, u32 func, u32 reg, u32 *data);
char *GetPCIDevClassName(u32 class_code);
void PCIScan();
#endif
4. Добавляем файл pci.c в корень проекта, со следующим содержимым (мы немного улучшили этот код по сравнению с основной статьей):
#include "types.h"
#include "printf.h"
#include "io.h"
#include "pci.h"
typedef struct
{
u32 class_code;
char name[32];
} PCIClassName;
static PCIClassName g_PCIClassNames[] =
{
{ 0x00, "before PCI 2.0"},
{ 0x01, "disk controller"},
{ 0x02, "network interface"},
{ 0x03, "graphics adapter"},
{ 0x04, "multimedia controller"},
{ 0x05, "memory controller"},
{ 0x06, "bridge device"},
{ 0x07, "communication controller"},
{ 0x08, "system device"},
{ 0x09, "input device"},
{ 0x0a, "docking station"},
{ 0x0b, "CPU"},
{ 0x0c, "serial bus"},
{ 0x0d, "wireless controller"},
{ 0x0e, "intelligent I/O controller"},
{ 0x0f, "satellite controller"},
{ 0x10, "encryption controller"},
{ 0x11, "signal processing controller"},
{ 0xFF, "proprietary device"}
};
typedef union
{
struct
{
u32 zero : 2;
u32 reg_num : 6;
u32 func_num : 3;
u32 dev_num : 5;
u32 bus_num : 8;
u32 reserved : 7;
u32 enable_bit : 1;
};
u32 val;
} PCIConfigAddres;
void ReadConfig32(u32 bus, u32 dev, u32 func, u32 reg, u32 *data)
{
PCIConfigAddres addr;
addr.val = 0;
addr.enable_bit = 1;
addr.reg_num = reg;
addr.func_num = func;
addr.dev_num = dev;
addr.bus_num = bus;
out32(PCI_CONFIG_PORT, addr.val);
in32(PCI_DATA_PORT, data);
return;
}
char *GetPCIDevClassName(u32 class_code)
{
int i;
for (i = 0; i < sizeof(g_PCIClassNames)/sizeof(g_PCIClassNames[0]); i++)
{
if (g_PCIClassNames[i].class_code == class_code)
return g_PCIClassNames[i].name;
}
return NULL;
}
int ReadPCIDevHeader(u32 bus, u32 dev, u32 func, PCIDevHeader *p_pciDevice)
{
int i;
if (p_pciDevice == 0)
return 1;
for (i = 0; i < sizeof(p_pciDevice->header)/sizeof(p_pciDevice->header[0]); i++)
ReadConfig32(bus, dev, func, i, &p_pciDevice->header[i]);
if (p_pciDevice->option.vendorID == 0x0000 ||
p_pciDevice->option.vendorID == 0xffff ||
p_pciDevice->option.deviceID == 0xffff)
return 1;
return 0;
}
void PrintPCIDevHeader(u32 bus, u32 dev, u32 func, PCIDevHeader *p_pciDevice)
{
char *class_name;
printf("bus=0x%x dev=0x%x func=0x%x venID=0x%x devID=0x%x",
bus, dev, func, p_pciDevice->option.vendorID, p_pciDevice->option.deviceID);
class_name = GetPCIDevClassName(p_pciDevice->option.classCode);
if (class_name)
printf(" class_name=%s", class_name);
printf("n");
}
void PCIScan(void)
{
int bus;
int dev;
for (bus = 0; bus < PCI_MAX_BUSES; bus++)
for (dev = 0; dev < PCI_MAX_DEVICES; dev++)
{
u32 func = 0;
PCIDevHeader pci_device;
if (ReadPCIDevHeader(bus, dev, func, &pci_device))
continue;
PrintPCIDevHeader(bus, dev, func, &pci_device);
if (pci_device.option.headerType & PCI_HEADERTYPE_MULTIFUNC)
{
for (func = 1; func < PCI_MAX_FUNCTIONS; func++)
{
if (ReadPCIDevHeader(bus, dev, func, &pci_device))
continue;
PrintPCIDevHeader(bus, dev, func, &pci_device);
}
}
}
}
5. Добавляем запуск сканирования PCI устройств в kernel.c:
#include "printf.h"
#include "screen.h"
#include "types.h"
#include "pci.h"
void main()
{
clear_screen();
printf("n>>> Hello World!n");
PCIScan();
}
6. Вносим необходимые изменения в makefile:
OBJFILES =
loader.o
common/printf.o
common/screen.o
pci.o
kernel.o
7. Теперь можно пересобрать проект:
make rebuild
sudo make image
8. Запускаем проект, чтобы убедиться, что все работает:
sudo qemu-system-i386 -hda hdd.img
Так мы получили список PCI устройств на компьютере. Это так же будет работать и на обычном компьютере, загрузившись с флешки.
Пройдя все шаги в этой статье вы можете собственноручно разобраться во всем и увидеть работающую программу, которую можно полноценно отлаживать.
Автор: NWOcs