В первой части нашей статьи мы рассказали о том, каким образом можно получить простую программу “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 следуя инструкциям и получаем такое дерево:
\proj\kernel.c
\proj\loader.s
\proj\common\printf.c
\proj\common\screen.c
\proj\include\printf.h
\proj\include\stdarg.h
\proj\include\screen.h
\proj\include\types.h
\proj\makefile
\proj\linker.ld
\proj\tools\plink.exe
\proj\kernel\kernel.sln
\proj\kernel\kernel.suo
\proj\kernel\kernel.sdf
\proj\kernel\vs\kernel.vcxproj
\proj\kernel\vs\kernel.vcxproj.filters
\proj\kernel\vs\make_vs.props
5. Формируем файл ”\proj\kernel\vs\make_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)tools\plink -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) tools\plink</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)tools\plink</LocalDebuggerCommand> <LocalDebuggerCommandArguments>$(RbExecuteCmd)</LocalDebuggerCommandArguments> </PropertyGroup> </Project>
6. Меняем файл ”\proj\kernel\vs\kernel.vcxproj ” так же по инструкции:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <Import Project="$(SolutionDir)\vs\make_vs.props" /> <ImportGroup Label="ExtensionSettings">
7. В итоге должно получиться примерно следующее:
8. Проверяем, что все работает:
Таким образом, для дельнейшей разработки нашей программы можно использовать оболочку Visual Studio, хоть и компилятор GCC на Linux.
Сканирование устройств PCI
Основная статья: Как найти PCI устройства без операционной системы
Как теперь просканировать системную шину PCI на наличие устройств? Следуя инструкциям в статье выполняем следующие действия (загрузочный образ уже готов, поэтому только добавляем код сканирования PCI):
1. Добавляем в файл include\types.h, следующее определение типа:
typedef unsigned long u32; typedef unsigned short u16; typedef unsigned char u8;
2. Добавляем файл include\io.h, который без изменений можно взять из проекта bitvisor из каталога (include\io.h).
3. Добавляем файл include\pci.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 устройств на компьютере. Это так же будет работать и на обычном компьютере, загрузившись с флешки.
Пройдя все шаги в этой статье вы можете собственноручно разобраться во всем и увидеть работающую программу, которую можно полноценно отлаживать.
ссылка на оригинал статьи http://habrahabr.ru/company/neobit/blog/174157/
Добавить комментарий