Как запустить программу без операционной системы: часть 2

от автора

В первой части нашей статьи мы рассказали о том, каким образом можно получить простую программу “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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *