オペレーティングシステムなしでプログラムを実行する方法:パート2





記事の最初の部分では 、オペレーティングシステムなしで実行され、画面にメッセージを出力する単純なプログラム「Hello World」を取得する方法について説明しました。



記事のこのパートでは、最初のパートで取得しコードを開発して、GDBを介してデバッグし、Visual Studioシェルでコンパイルし、画面にPCIデバイスのリストを印刷できるようにします。



重要! :以降のすべてのアクションは、記事の最初の部分で説明した6つのステップすべてを正常に完了した後にのみ正常に実行できます。





プログラムのデバッグを学ぶ





主要な記事GDBデバッガーを最大限に活用する



kernel.binコードをデバッグする方法は? これを行うには、kernel.binにデバッグシンボルを追加し、デバッガーを実行します。



1.デバッグシンボルを生成するように、メイクファイルにコンパイラオプションを追加します。

CFLAGS = -Wall -fno-builtin -nostdinc -nostdlib -ggdb3
      
      







2.アセンブリ段階で数行を追加して、文字のないkernel.binがディスクに書き込まれるようにします(このようなファイルはstripユーティリティを使用して作成できます)。 これを行うには、makefileのkernel.binターゲットを修正します。

 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.次の文字を含むファイルを示すデバッガーを実行します。

 cgdb kernel.bin.dbg
      
      







7.デバッガーで、qemuに接続し、メイン関数に直接ブレークポイントを設定します。

 (gdb) target remote localhost:1234 (gdb) break main
      
      











8. mainにアクセスしてデバッグします。

 (gdb) c (gdb) n (gdb) n
      
      











したがって、強力なデバッグツールが取得されます。 このメソッドはQEMUで機能します。ハードウェアでプログラムを直接デバッグするには、デバッガモジュールをプログラムに接続する必要があります。これについては、次の記事のいずれかで検討します。



Visual Studioからのコンパイル





主要な記事Visual Studio 2010シェルを使用してLinuxでgccを使用してプロジェクトをコンパイルする



Visual Studioから受け取ったコードを使用する方法 記事の指示に従って、Linuxでプロジェクトを作成せずにVisual Studioプロジェクトをアセンブルします-既に存在します。



1. sshシステムにインストールします。

 sudo apt-get install ssh
      
      







2.仮想マシンの共有ディレクトリに kernel.binからプロジェクトソースを配置します。



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 \リンカー.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.すべてが機能することを確認します。







したがって、プログラムをさらに開発するために、LinuxのGCCコンパイラーでもVisual Studioシェルを使用できます。



PCIデバイスをスキャンする





詳細は 、オペレーティングシステムなしでPCIデバイスを見つける方法



デバイスのPCIシステムバスを今すぐスキャンする方法は? この記事の指示に従って、次のアクションを実行します(ブートイメージは既に準備ができているので、PCIスキャンコードを追加するだけです)。



1.次のタイプ定義をinclude \ types.hファイルに追加します。

 typedef unsigned long u32; typedef unsigned short u16; typedef unsigned char u8;
      
      







2.ファイルinclude \ io.hを追加します。このファイル 、変更せずに、ディレクトリ(include \ io.h)からbitvisorプロジェクトから取得できます。



3. PCI機能の基本的な定義を含むinclude \ pci.hファイルを追加します 。 次の内容があります。

 #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. kernel.cでPCIデバイスのスキャンの開始を追加します。



 #include "printf.h" #include "screen.h" #include "types.h" #include "pci.h" void main() { clear_screen(); printf("\n>>> Hello World!\n"); PCIScan(); }
      
      







6.メイクファイルに必要な変更を加えます。

 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デバイスのリストを取得しました。 これは通常のコンピューターでも動作し、USBフラッシュドライブから起動します。



この記事のすべての手順を実行すると、すべてを個人的に理解し、完全にデバッグできる作業プログラムを確認できます。



シリーズの次の記事へのリンク:

オペレーティングシステムなしでプログラムを実行する方法: パート3:グラフィックス

オペレーティングシステムなしでプログラムを実行する方法: パート4.並列計算

オペレーティングシステムなしでプログラムを実行する方法: パート5. OSからBIOSにアクセスする

オペレーティングシステムなしでプログラムを実行する方法: パート6. FATファイルシステムでディスクを操作するためのサポート



All Articles