Bochs는 C++로 작성된 IA-32 아키텍쳐 PC 에뮬레이터이다. 쉽게 말해 소프트웨어로 PC 하드웨어 전체를 흉내내는 프로그램이다. Intel x86 CPU, 일반적인 IO 장치 및 BIOS를 에뮬레이션한다. 현재 Bochs는 386, 486, Pentium, Pentium Pro, AMD64 CPU를 에뮬레이트할 수 있으며 MMX, SSESSE, 3DNow! 명령어 세트를 포함한다.
Bochs가 실행할 수 있는 운영체제에는
- Linux
- DOS
- Windows
가 포함된다. 다양한 모드의 컴파일을 사용할 수 있으며 일부는 아직 개발 중이다. 전형적인 응용은 완전한 x86 PC 에뮬레이터를 제공하는 것으로 x86 CPU, hw 장치 및 저장장치를 포함한다.
http://bochs.sourceforge.net/ 에서 최신 버전을 다운로드 할 수 있다.
버전 2.2.1 소스코드 구성
Bochs v2.2.1 은 약 300 개의 소스파일과 20만줄의 코드로 구성되어있다. 주요 폴더로는 BIOS, Cpu, Memory, Iodev, GUI 등이 있다. 루트 디렉토리에는 VC 프로젝트 파일(.dsp, .dsw) 과 일부 헤더 파일(Bochs.h), main 함수가 있는 main.cpp 이 있다.
루트 디렉토리에는 다음과 같은 폴더가 있다.
├── bios
├── build
├── bx_debug
├── cpu
├── disasm
├── doc
├── docs-html
├── dynamic
├── font
├── fpu
├── gui
├── host
├── instrument
├── iodev
├── memory
├── misc
├── patches
└── plex86
이 중 일부 프로젝트는 Bochs 보조 도구를 컴파일하여 생성한다. 예를 들어 bximage 를 컴파일하려면 bximage.exe가 생성되는데 이 도구는 가상 하드디스크를 생성하는데 사용된다.
Bochs.exe는 bochs 프로젝트가 컴파일되어 생성된 것으로 다른 여러 프로젝트에 의존한다.
따라서 bochs.exe 를 컴파일하려면 먼저 iodev, cpu, memory 등의 의존 프로젝트를 컴파일하여 해당 lib 파일을 생성해야 사용할 수 있다.
프로젝트 클래스 구조
모든 클래스는 로그 기능을 제공하는 logfunctions 클래스를 상속받는다. 이 클래스는 간단한 로그 기능을 완성하여 디버깅 시 관련 정보를 출력하기 편하다.
모든 클래스 중 핵심 클래스 세가지는 다음과 같다.
- BX_CPU: CPU 기술
- BX_MEM_C: 메모리 기술
- IO device 파생 클래스들: 각종 하드웨어 장치
그리고 PC 시스템 보드 를 기술하는 클래스 bx_pc_system_c 가 이들을 연결하여 완전한 PC 를 구성한다. 각 장치는 stub 클래스를 통해 통일된 인터페이스 (read/write) 를 제공한다.
통일된 인터페이스를 위해 Bochs는 각 장치에 대해 stub 클래스를 정의했다. stub 클래스의 가상 함수는 통일된 인터페이스를 제공하며 예를 들어 저장 장치 읽기/쓰기, I/O 읽기/쓰기 등이 있다. 이러한 가상 함수의 최종 구현은 각 구체적인 장치의 함수에서 완성된다.
프레임워크 구조 분석
Bochs 프로젝트의 중요 클래스
(1) VM 콘솔 인터페이스 클래스
class BOCHSAPI bx_gui_c : public logfunctions
bx_gui_c 에서 파생된 클래스
- bx_win32_gui_c
- bx_svga_gui_c
- bx_term_gui_c
- bx_sdl_gui_c
- bx_wx_gui_c
- bx_rfb_gui_c
- bx_nogui_gui_c
- bx_macintosh_gui_c
- bx_beos_gui_c
- bx_amigaos_gui_c
- bx_carbon_gui_c
- bx_x_gui_c
config.h 에서 설정하여 콘솔 인터페이스를 선택한다.
#define BX_WITH_X11 0
#define BX_WITH_BEOS 0
#define BX_WITH_WIN32 1 // ← 활성화됨
#define BX_WITH_MACOS 0
#define BX_WITH_CARBON 0
#define BX_WITH_NOGUI 0
#define BX_WITH_TERM 0
#define BX_WITH_RFB 0
#define BX_WITH_AMIGAOS 0
#define BX_WITH_SDL 0
#define BX_WITH_SVGA 0
#define BX_WITH_WX 0
(2) CPU 에뮬레이션
클래스 BX_CPU_C 는 CPU를 기술하는데 쓰인다.
class BOCHSAPI BX_CPU_C : public logfunctions
클래스 bxInstruction_c 는 하나의 명령어를 정의하며 기본 클래스가 없다.
(3) Memory 에뮬레이션
클래스 BX_MEM_C
class BOCHSAPI BX_MEM_C : public logfunctions // memory/memory.h
변수
#if BX_PROVIDE_CPU_MEMORY==1
#if BX_SMP_PROCESSORS==1
BOCHSAPI extern BX_MEM_C bx_mem; // 외부 전역 변수
#else
BOCHSAPI extern BX_MEM_C *bx_mem_array[BX_ADDRESS_SPACES]; // 다중 프로세서의 경우,
// 내부 그룹 사용
#endif /* BX_SMP_PROCESSORS */
#endif /* BX_PROVIDE_CPU_MEMORY==1 */
전역 변수
BOCHSAPI BX_CPU_C bx_cpu;
BOCHSAPI BX_MEM_C bx_mem;
(4) I/O device 에뮬레이션
- Class bx_devices_c : public logfunctions
- Class bx_devmodel_c : public logfunctions // iodev/iodev.h
파생 클래스 목록
- bx_busm_stub_c //bus mouse
- bx_cmos_stub_c
- bx_devmodel_c
- bx_dma_stub_c
- bx_floppy_stub_c
- bx_hard_drive_stub_c
- bx_keyb_stub_c
- bx_ne2k_stub_c
- bx_pci2isa_stub_c
- bx_pci_ide_stub_c
- bx_pci_stub_c
- bx_pic_stub_c
- bx_serial_stub_c
- bx_speaker_stub_c
- bx_usb_stub_c
- bx_vga_stub_c
진입 함수 main() 및 Win32 Gui 초기화
Main.cpp 는 매크로 정의를 통해 컴파일러 진입 함수를 선택한다. 기본값은 main이다.
진입합수 선택
#if defined(__WXMSW__) WinMain() // wxWidgets/win32 사용
#if !defined(__WXMSW__) main()
Main 함수
int main (int argc, char *argv[])
{
bx_startup_flags.argc = argc; // 전역 변수, 타입은 BOCHSAPI, 명령행 파라미터 기록용
bx_startup_flags.argv = argv;
#if BX_WITH_SDL && defined(WIN32)
// if SDL/win32, try to create a console window.
RedirectIOToConsole ();
#endif
return bxmain ();
}
bxmain() 함수
int bxmain () {
...
bx_init_siminterface (); // create the SIM object
if (bx_init_main (bx_startup_flags.argc, bx_startup_flags.argv) < 0)
return 0;
...
int status = SIM->configuration_interface (ci_name, CI_START);
if (status == CI_ERR_NO_TEXT_CONSOLE)
BX_PANIC (("Bochs needed the text console, but it was not usable"));
호출 bx_init_siminterface ();
void bx_init_siminterface ()
{
siminterface_log = new logfunctions ();
siminterface_log->put ("CTRL");
siminterface_log->settype(CTRLLOG);
if (SIM == NULL)
SIM = new bx_real_sim_c();
}
bx_init_main()
#define BX_USE_TEXTCONFIG 1 // 모드를 텍스트 설정 모드로 정의
class bx_real_sim_c : public bx_simulator_interface_c {
...
config_interface_callback_t ci_callback;
...
}
함수 ci_callback() 이 bx_real_sim_c::configuration_interface() 에서 호출되며 configuration_interface()는 두 곳에서 호출된다.
- bx_gui_c::config_handler 중에서 config_handler 가 bx_gui_c::init에서 호출됨
- bxmain()
begin_simulation()에서 bx_begin_simulation() 호출
함수 bx_begin_simulation()
1. load_and_init_display_lib() 호출 // GUI 객체 초기화
2. 만약 BX_DEBUGGER가 정의되어있으면 bx_dbg_main(argc, argv) 호출
3. bx_init_hardware() 호출 // 이 함수의 실행 전에 GUI가 초기화
4. bx_load32bitOSimagehack
5. SIM->set_init_done(1) // 초기화 완료 표시
6. ...
7. BX_CPU(0)->cpu_loop(1); //CPU 루프 진입
bx_init_hardware()
int bx_init_hardware()
{
// all configuration has been read, now initialize everything.
if (SIM->get_param_enum(BXP_BOCHS_START)->get ()==BX_QUICK_START) {
for (int level=0; level<N_LOGLEV; level++) {
int action = SIM->get_default_log_action (level);
#if !BX_USE_CONFIG_INTERFACE
if (action == ACT_ASK) action = ACT_FATAL;
#endif
io->set_log_action (level, action);
}
}
bx_pc_system.init_ips(bx_options.Oips->get ());
Bit32u memSize = bx_options.memory.Osize->get ()*1024*1024;
// 단일 프로세서 상황에서
#if BX_SMP_PROCESSORS==1
// 메모리 초기화
BX_MEM(0)->init_memory(memSize);
// 로드 BIOS and VGABIOS
BX_MEM(0)->load_ROM(bx_options.rom.Opath->getptr (), bx_options.rom.Oaddress->get (), 0);
BX_MEM(0)->load_ROM(bx_options.vgarom.Opath->getptr (), 0xc0000, 1);
// Then load the optional ROM images
...
// CPU 초기화
BX_CPU(0)->init (BX_MEM(0));
BX_CPU(0)->set_cpu_id(0);
// CPU 점검
BX_CPU(0)->sanity_checks();
// 뭘 하는지 모르겠음
BX_INSTR_INIT(0);
BX_CPU(0)->reset(BX_RESET_HARDWARE);
#if BX_DEBUGGER == 0
// 외부 장치 초기화
DEV_init_devices();
DEV_reset_devices(BX_RESET_HARDWARE);
bx_gui->init_signal_handlers ();
bx_pc_system.start_timers();
#endif
return(0);
}
win32 GUI의 시동과 초기화
plugin.h 중에 매크로 정의
#define PLUG_load_plugin(name,type) {lib##name##_LTX_plugin_init(NULL,type,0,NULL);}
libwin32_LTX_plugin_init 함수를 찾으려했지만 직접 정의된 곳은 없었다.
다음의 절차로 IMPLEMENT_GUI_PLUGIN_CODE(win32) 매크로가 컴파일 시점에 함수를 자동 생성한다.
(1) 플러그인 로드 매크로 (plugin.h)
#define PLUG_load_plugin(name, type) \
{ lib##name##_LTX_plugin_init(NULL, type, 0, NULL); }
이 매크로를 사용하면
PLUG_load_plugin(win32, PLUGTYPE_OPTIONAL)
다음과 같이 확장된다.
{ libwin32_LTX_plugin_init(NULL, PLUGTYPE_OPTIONAL, 0, NULL); }
2단계: GUI 플러그인 구현 매크로 (gui/gui.h)
#define IMPLEMENT_GUI_PLUGIN_CODE(gui_name) \
int lib##gui_name##_LTX_plugin_init(plugin_t *plugin, \
plugintype_t type, \
int argc, char *argv[]) { \
genlog->info("installing %s module as the Bochs GUI", #gui_name);\
theGui = new bx_##gui_name##_gui_c(); \
bx_gui = theGui; \
return(0); \
}
이 매크로는 함수 전체를 생성한다.
3단계: 실제 사용 (gui/win32.cpp)
IMPLEMENT_GUI_PLUGIN_CODE(win32)
이 한줄이 다음과 같이 확장된다.
int libwin32_LTX_plugin_init(plugin_t *plugin,
plugintype_t type,
int argc, char *argv[]) {
genlog->info("installing win32 module as the Bochs GUI");
theGui = new bx_win32_gui_c(); // Win32 GUI 객체 생성
bx_gui = theGui; // 전역 포인터에 할당
return(0);
}
다른 gui 정의도 같은 방식이다.
// gui/x11.cpp
IMPLEMENT_GUI_PLUGIN_CODE(x11)
→ libx11_LTX_plugin_init() 함수 생성
→ new bx_x11_gui_c() 호출
// gui/sdl.cpp
IMPLEMENT_GUI_PLUGIN_CODE(sdl)
→ libsdl_LTX_plugin_init() 함수 생성
→ new bx_sdl_gui_c() 호출
// gui/nogui.cpp
IMPLEMENT_GUI_PLUGIN_CODE(nogui)
→ libnogui_LTX_plugin_init() 함수 생성
→ new bx_nogui_gui_c() 호출
Bochs의 작업 방식
실제 CPU의 원리와 마찬가지로 Bochs 가상머신 CPU의 실행방식은
명령어 가져오기(fetch) -> 실행 (execute) -> 결과 출력 이다.
CPU 리셋 후 real mode에 진입하며
EIP = 0x0000FFF0;
CS의 cache 내의 Base = 0xFFFF0000,
두가지를 더하면 CS:EIP=FFFFFFF0이 된다. 이 위치는 BIOS ROM이 있는 위치이다. 사용자 프로그램은 이 곳에서부터 실행을 시작하며 CPU 루프에 진입 후 명령어 가져오기와 실행 작업 외에도 매번 이벤트 플래그 비트를 검사한다.
만약 리셋은 종료 명령이 없으면 CPU 루프는 계속 진행된다.

'System Programming > Emulator' 카테고리의 다른 글
| linux vm stack 개발 노트 #1 kvm 내부 구조 (0) | 2025.12.19 |
|---|---|
| KVM 가상화 환경 구성 (0) | 2024.04.01 |