cygwinを利用した仮想マシンのプログラムのリモートデバッグ

64bitなcygwinでビルドしたから、仮想マシンの32bitなReactOS用のアプリケーションおよびReactOSのDLLをgdbデバッグする方法です。

前提

ホストOS:windows10(64bit)
ホスト環境:cygwin(64bit)
ホストデバッガ:gdb(cygwin用)
ホストのipアドレス:192.168.44.1

仮想マシン:VMware Workstation 12 player
ゲストOS:ReactOS(32bit)
ゲストのipアドレス:192.168.44.129
ゲストデバッガ:gdbserver(windows用)

デバッグ情報付きのReactOSのDLLの準備

ReactOSのCドライブのルート(\)に対応する場所を、ホストの/path/to/sysroot/とした場合、ReactOS上のC:\ReactOS\Sytem32\にあるdllについては、そのdllに対応するデバッグ情報付きのdllを、cygwin上の/path/to/sysroot/ReactOS/System32/に配置します。ドライブレター(C:)に対応するパスはなくても問題はありません。もし、ドライブレターに対応するパスが必要なら、/path/to/sysroot/c/ReactOS/System32/ のように、/cを含めたパスに、デバッグ情報付きのdllを配置します。

gdbの操作

ここでは、cygwinデバッグ情報付きでコンパイルしたhello.exeをReactOS上でデバッグします。

ゲストOSでの操作
ReactOS上でgdbserverを起動し、任意のポートで起動します。ここでは、3333番のポートで待ち受けることにします。

C:\ gdbserver --multi localhost:3333

ホストOSでの操作
cygwin上で、gdbを起動します。

~/ gdb

以下は、gdbでの操作になります。
ReactOSでルートとして利用するcygwinでのディレクトリを指定します。
ReactOSのgdbserverに接続します。cygwinからファイルの転送を行うため、targetには、extended-remoteを指定します。また、ReactOSIPアドレス(ここでは、192.168.44.129)と先ほど指定したポート番号を指定します。
ReactOSのgdbserverに対して、hello.exeを転送します。
ReactOSのgdbserverに対して、実行ファイルとして、hello.exeを指定します。
gdbに対して、実行ファイルとして、hello.exeを読み込みます。
ブレークポイントをmainに設定します。このときに表示されるソースは、cygwinのクロスコンパイラで使用するcrtのソースになります。
実行すると、ブレークポイントで停止します。

(gdb) set sysroot /path/to/sysroot/

(gdb) target extended-remote 192.168.44.129:3333
Remote debugging using 192.168.44.129:3333

(gdb) remote put hello.exe hello.exe
Successfully sent file "hello.exe".

(gdb) set remote exec-file hello.exe

(gdb) file hello.exe
Reading symbols from hello.exe...done.

(gdb) b main
Breakpoint 1 at 0x402a26: file /usr/src/debug/mingw64-i686-runtime-5.0.2-1/crt/crt0_c.c, line 18.

(gdb) r
Starting program: /path/to/hello/hello.exe

Breakpoint 1, main (flags=1, cmdline=0x231dd8, inst=0x2318b8)
    at /usr/src/debug/mingw64-i686-runtime-5.0.2-1/crt/crt0_c.c:18
18        return (int) WinMain (__mingw_winmain_hInstance, NULL,

cygwinでcmakeを利用したwindowsアプリケーションのビルド

前提

64bitなcygwin環境で、64bitのwindows用のアプリケーションと32bitのwindows用のアプリケーションを作成するための基本的な方法です。

cygwin環境で利用して、windows用のプログラムを作成するため、クロスコンパイラを利用します。そのため、事前に、64bitのwindows用のコンパイラx86_64-w64-mingw32-gcc)と、32bitのwindows用のコンパイラi686-w64-mingw32-gcc)を用意してきます。

1つのアプリケーションについて、64bit用と32bit用にビルドし、また、それぞれをさらにデバッグ用とリリース用にビルドするつもりのため、4種類のビルドを管理することになります。
このため、これらのビルドを行いやすくするため、メタビルドシステムのcmakeを使います。
cmakeの出力を実際にビルドするビルドシステムは、通常はmakeですが、ここではNinjaを利用してビルドすることにします。
cmake、Ninjaについても、事前に用意しておきます。

ディレクトリの構成

case01ディレクトリの下に、ビルド用のディレクトリと、ソース用のディレクトリを作ります。
ビルド用のディレクトリとして、ここでは、64_debug、64_release、32_debug、32_releaseを作ります。
ソースは、sample01ディレクトリの下に置きます。

case01
├── 64_debug (64bitのデバッグ用のディレクトリ)
├── 64_release (64bit用のリリース用のディレクトリ)
├── 32_debug (32bit用のデバッグ用のディレクトリ)
├── 32_release (32bit用のリリース用のディレクトリ)
└── sample01 (このディレクトリ以下にソース等を置きます)
    ├── CMakeLists.txt (cmakeの全体の設定)
    ├── mingw64.cmake (64bit用のアプリケーションについての、cmakeの設定)
    ├── mingw32.cmake (32bit用のアプリケーションについての、cmakeの設定)
    └── hello
        ├── CMakeLists.txt(cmakeのこのディレクトリでの設定)
        ├── menu.rc (リソース)
        ├── main.c (ソース)
        └── hello.h (ヘッダ)

各ファイルの説明

case01/sample01/CMakeLists.txt

cmakeの設定ファイル
リソースコンパイラの有効化等の全体の設定や、cmakeを利用する下位のディレクトリの設定等を行います。

cmake_minimum_required(VERSION 2.8.12)

#windowsのリソースを有効化
ENABLE_LANGUAGE(RC)

#プロジェクト名
project (sample01_project)

#cmakeで利用する下位のディレクトリを設定
add_subdirectory(hello)
case01/sample01/mingw64.cmake

cmakeの設定ファイル
64bit用のビルドで利用するクロスコンパイラの設定を行います。

# クロスコンパイルのターゲットのシステム名
SET(CMAKE_SYSTEM_NAME Windows)

# クロスコンパイラ等の設定
SET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
SET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
SET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)

#Cフラグの設定
#ビルドタイプに共通の設定
set(CMAKE_C_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
#Debugビルドに利用する設定
set(CMAKE_C_FLAGS_DEBUG "-g3 -gdwarf-2")
set(CMAKE_CXX_FLAGS_DEBUG "-g3 -gdwarf-2")

# ビルド環境(cygwin)での、ターゲット用の環境の場所
SET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)

# cmakeのFIND_XXX() コマンドのデフォルト設定
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
case01/sample01/mingw32.cmake

cmakeの設定ファイル
32bit用のビルドで利用するクロスコンパイラの設定を行います。
コンパイラ等がちがいますが、Cフラグ等は同じです。

# クロスコンパイルのターゲットのシステム名
SET(CMAKE_SYSTEM_NAME Windows)

# クロスコンパイラ等の設定
SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)

#Cフラグの設定
#ビルドタイプに共通の設定
set(CMAKE_C_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
#Debugビルドに利用する設定
set(CMAKE_C_FLAGS_DEBUG "-g3 -gdwarf-2")
set(CMAKE_CXX_FLAGS_DEBUG "-g3 -gdwarf-2")

# ビルド環境(cygwin)での、ターゲット用の環境の場所
SET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)

# cmakeのFIND_XXX() コマンドのデフォルト設定
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
case01/sample01/hello/CMakeLists.txt

cmakeの設定ファイル
helloディレクトリで生成する実行ファイルの設定を行います。

#gccを使って、windows用のアプリケーションをコンパイルするときには、gccのオプションに -mwindowsが必要です。
#これを指定するために、add_excutableに、WIN32 を足します。
#hello.exeは、 WIN32、main.c、menu.rcから生成します
add_executable(hello WIN32 main.c menu.rc)
case01/sample01/hello/main.c

hello.exeのソース
「こんにちわ、世界!」を表示するアプリケーションです。

// このソースがUNICODE用のアプリケーション用のソースであること(utf8)を定義します。
// これにより、ソースコードがutf8で書かれていることを定義したことになります。
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#include "hello.h"

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);
int MyTextOut(HWND hWnd);

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, int showCmd)
{
  WNDCLASSEX wc;
  HWND hWnd;
  MSG msg;
  
  wc.cbSize = sizeof(wc);
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInst;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hIconSm = wc.hIcon;
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName = L"HELLO";
  wc.lpszClassName = L"HelloWindowClass";
  
  if(RegisterClassEx(&wc) == 0)
    { return -1; }
  
  hWnd = CreateWindow(wc.lpszClassName,
		      L"こんにちわウインドウ",
		      WS_OVERLAPPEDWINDOW,
		      CW_USEDEFAULT,
		      CW_USEDEFAULT,
		      CW_USEDEFAULT,
		      CW_USEDEFAULT,
		      NULL,
		      NULL,
		      hInst,
		      NULL
		      );
  if(hWnd == NULL)
    {	return -1; }
  
  ShowWindow(hWnd, SW_SHOW);
  UpdateWindow(hWnd);
  
  while(1) {
    BOOL ret = GetMessage(&msg, NULL, 0, 0);
    if(ret == 0 || ret == -1) {
      break;
    } else {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  
  return 0;
}


LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg) {
  case WM_COMMAND:
    switch(LOWORD(wParam)) {
    case IDM_END:
      SendMessage(hWnd, WM_CLOSE, 0, 0L);
      break;
    }
    break;
  case WM_PAINT:
    MyTextOut(hWnd);
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    return 0;
  }
  
  return DefWindowProc(hWnd, msg, wParam, lParam);
}

int MyTextOut(HWND hWnd)
{
  HDC hdc;
  PAINTSTRUCT pPaint;
  LPCWSTR lpString = L"こんにちわ 世界!";
  
  hdc = BeginPaint(hWnd, &pPaint);
  TextOut(hdc, 10, 10, lpString, lstrlen(lpString));
  EndPaint(hWnd, &pPaint);
  
  return 0; 
}
case01/sample01/hello/menu.rc

hell.exeで利用するリソース
メニューファイルのリソースを設定します。

// utf8でリソースファイルを書いていることを、コンパイラに伝えます。
#pragma code_page(65001)

#include <windows.h>
#include "hello.h"    //IDM_ENDの値を定義する

//    メニュー
HELLO MENU PRELOAD FIXED
BEGIN
  POPUP "ファイル(&F)"
  BEGIN
    MENUITEM "終了(&X)", IDM_END
  END
END
case01/sample01/hello/hello.h

hello.exe等で利用するヘッダ
定数等を設定します。

#ifndef _HELLO_H
#define _HELLO_H

// menu用の定数の定義
#define IDM_END        100

#endif

ビルド

まず、64bitのwindows用のデバッグビルドを行う場合について説明し、その後、32bitのwindows用のデバッグビルドを行う場合について説明します。
他のタイプのビルドを行う場合には、適宜読み替えてください。

64bitのwindows用のデバッグビルド

ここでは、64bitのwindows用のデバッグビルドを行う場合について説明します。
他のタイプのビルドを行う場合には、適宜読み替えてください。

cmakeの実行(ビルドの準備)

ninjaでビルドするための設定ファイルをcmakeで作成します。
このときに、64_debugディレクトリで、cmakeを2回実行します。
1回目のcmakeの実行は、利用するコンパイラやビルドシステムを指定するためのものです。
2回目のcmakeの実行は、リリースタイプとして、Debugを指定するためのものです。
1回目や2回目で行ったコンパイラ、ビルドシステム、リリースタイプの指定は、ビルドを行っているディレクトリにキャッシュされています。そのため、ソースの修正を行って再度ビルドしなおすときには、これらについて、指定しなおす必要はありません。

1回目のcmakeの実行

64_debug $ cmake -DCMAKE_TOOLCHAIN_FILE=../sample01/mingw64.cmake -G Ninja ../sample01/
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/x86_64-w64-mingw32-gcc.exe
-- Check for working C compiler: /usr/bin/x86_64-w64-mingw32-gcc.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/x86_64-w64-mingw32-g++.exe
-- Check for working CXX compiler: /usr/bin/x86_64-w64-mingw32-g++.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/case01/64_debug

2回目のcmakeの実行

64_debug $ cmake -DCMAKE_BUILD_TYPE=Debug ../sample01/
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/case01/64_debug
ninjaの実行(ビルド)

実際にビルドを行います。
ninjaは、ビルドの進行状況を隠してくれますが、ビルドの詳細を確認したい場合には、ninja -v で実行します。
Debugビルドで、-Wextraを指定したことにより、自動的に-Wunused-parameterが指定されるため、警告が発生しています。これは、WinMainで仮引数を利用していないことによる警告ですが、意図したものであるため、無視してよいものです。

64_debug $ ninja -v
[1/3] x86_64-w64-mingw32-windres  -O coff    /path/to/case01/sample01/hello/menu.rc hello/CMakeFiles/hello.dir/menu.rc.res
[2/3] /usr/bin/x86_64-w64-mingw32-gcc.exe    -Wall -Wextra -g3 -gdwarf-2 -MD -MT hello/CMakeFiles/hello.dir/main.c.obj -MF hello/CMakeFiles/hello.dir/main.c.obj.d -o hello/CMakeFiles/hello.dir/main.c.obj   -c /path/to/case01/sample01/hello/main.c
/path/to/case01/sample01/hello/main.c: 関数 ‘WinMain’ 内:
/path/to/case01/sample01/hello/main.c:12:47: 警告: 仮引数 ‘hPrevInst’ が未使用です [-Wunused-parameter]
 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, int showCmd)
                                               ^
/path/to/case01/sample01/hello/main.c:12:64: 警告: 仮引数 ‘pCmdLine’ が未使用です [-Wunused-parameter]
 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, int showCmd)
                                                                ^
/path/to/case01/sample01/hello/main.c:12:78: 警告: 仮引数 ‘showCmd’ が未使用です [-Wunused-parameter]
 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR pCmdLine, int showCmd)
                                                                              ^
[3/3] : && /usr/bin/x86_64-w64-mingw32-gcc.exe  -Wall -Wextra -g3 -gdwarf-2  -mwindows hello/CMakeFiles/hello.dir/main.c.obj hello/CMakeFiles/hello.dir/menu.rc.res  -o hello/hello.exe -Wl,--out-implib,hello/libhello.dll.a -Wl,--major-image-version,0,--minor-image-version,0  -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32 && :
実行

case01/64_debug/helloディレクトリに、hello.exeが生成されていますので、実行してください。

64_debug $ ./hello/hello.exe

ファイルのタイプを確認すると、64bit用になっています。

64_debug $ file ./hello/hello.exe
./hello/hello.exe: PE32+ executable (GUI) x86-64, for MS Windows
32bitのwindows用のデバッグビルド

ここでは、32bitのwindows用のデバッグビルドを行う場合について説明します。

cmakeの実行(ビルドの準備)

64bitの時と同様に、cmakeを2回実行します。
先ほどは、64_debugディレクトリで実行しましたが、今回は、32_debugディレクトリで実行してください。
また、指定するツールチェインは、mingw32.cmakeになります。

cmake、ninjaの実行

32_debug $ cmake -DCMAKE_TOOLCHAIN_FILE=../sample01/mingw32.cmake -G Ninja ../sample01/
32_debug $ cmake -DCMAKE_BUILD_TYPE=Debug ../sample01/
32_debug $ ninja
実行

case01/32_debug/helloディレクトリに、hello.exeが生成されていますので、実行してください。

32_debug $ ./hello/hello.exe

また、先ほどと同様に、ファイルのタイプを確認すると、先ほどと違い、32bit用になっています。

32_debug $ file ./hello/hello.exe
./hello/hello.exe: PE32 executable (GUI) Intel 80386, for MS Windows