俺と Virtual PC と検出

(おもに OS/2 ゲスト向けに)動作環境が VirtualPC 内かどうか検出したくなったので、このへん見つつとりあえず Win32 用の検出コード書こうとしたら mingwgcc は構造化例外構文をサポートしてなかったでござる。なんということでしょう、OS/2 にたどり着くどころか Win32 の時点で悲しみが大きすぎる。

しかたないので API 経由で例外ハンドラを一時的に突っ込んでみた。うまくいったらおなぐさみ。

とりあえず Win32 と OS/2 用バイナリとか : undervpc_20091104.zip

しかしアレです、基本的にはたかだか4バイトの未定義命令を呼び出したいだけなのに、調べないといけないことが多すぎて(gccインラインアセンブラの書き方、構造化例外の低レベル API)いやになった。しかも正しいのかどうか自信が持てないという。

一応ソース つ

/*
  Detect running under VirtualPC.
  reference: http://www.codeproject.com/KB/system/VmDetect.aspx

  compiler: gcc only.
  target:   Win32 (x86 32bit) and OS/2 (32bit) only.
  License:  "You can use/modify/redistribute it freely BUT NO WARRANTY!".
*/

#if defined(_WIN32)
#include <windows.h>
#elif defined(__OS2__)
#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_DOSEXCEPTIONS
#include <os2.h>
#else
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


struct vpc_backdoor_regpack {
  unsigned r_eax;
  unsigned r_ebx;
  unsigned r_ecx;
  unsigned r_edx;
};

extern int  is_under_vpc(void);
extern int  vpc_invoke_backdoor(struct vpc_backdoor_regpack *rp_return, const struct vpc_backdoor_regpack *rp_in);


int vpc_invoke_backdoor(struct vpc_backdoor_regpack *rp_return, const struct vpc_backdoor_regpack *rp_in)
{
  unsigned ri_eax, ri_ebx, ri_ecx, ri_edx;
  unsigned ro_eax, ro_ebx, ro_ecx, ro_edx;
  int result = 0;
  ri_eax = rp_in->r_eax;
  ri_ebx = rp_in->r_ebx;
  ri_ecx = rp_in->r_ecx;
  ri_edx = rp_in->r_edx;
  __asm__ __volatile__ ("; \
    movl %4, %%eax; movl %5, %%ebx; movl %6, %%ecx; movl %7, %%edx; \
    .byte 0x0f, 0x3f, 0x07, 0x0b; \
    movl %%eax, %0; movl %%ebx, %1; movl %%ecx, %2; movl %%edx, %3 \
  "
  : "=m"(ro_eax), "=m"(ro_ebx), "=m"(ro_ecx), "=m"(ro_edx)
  : "m"(ri_eax), "m"(ri_ebx), "m"(ri_ecx), "m"(ri_edx)
  : "%eax", "%ebx", "%ecx", "%edx", "cc"
  );
  rp_return->r_eax = ro_eax;
  rp_return->r_ebx = ro_ebx;
  rp_return->r_ecx = ro_ecx;
  rp_return->r_edx = ro_edx;
  return result;
}

#if defined(_WIN32)
static LONG CALLBACK isvpc_except_w32(LPEXCEPTION_POINTERS ep)
{
  if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION &&
      *(LPDWORD)(ep->ExceptionRecord->ExceptionAddress) == 0x0b073f0fU)
  {
    ep->ContextRecord->Ebx = 0xffffffff;
    ep->ContextRecord->Eip += 4;
    return EXCEPTION_CONTINUE_EXECUTION;
  }
  return EXCEPTION_CONTINUE_SEARCH;
}
#elif defined(__OS2__)
static ULONG _System isvpc_except_os2(PEXCEPTIONREPORTRECORD ep, PEXCEPTIONREGISTRATIONRECORD er, PCONTEXTRECORD ctx, PVOID pv)
{
  if (ep->ExceptionNum == XCPT_ILLEGAL_INSTRUCTION &&
      *(ULONG *)(ep->ExceptionAddress) == 0x0b073f0fUL)
  {
    ctx->ctx_RegEbx = 0xffffffffUL;
    ctx->ctx_RegEip += 4;
    return XCPT_CONTINUE_EXECUTION;
  }
  return XCPT_CONTINUE_SEARCH;
}
#endif


int  is_under_vpc(void)
{
#if defined(_WIN32)
  LPTOP_LEVEL_EXCEPTION_FILTER topexp;
#elif defined(__OS2__)
  EXCEPTIONREGISTRATIONRECORD xcpt = { 0, isvpc_except_os2 };
#endif
  struct vpc_backdoor_regpack rp;
  
  memset(&rp, 0, sizeof(rp));
#if defined(_WIN32)
  { /* DO_ATOMIC_ENTER */
    topexp = SetUnhandledExceptionFilter(isvpc_except_w32);
    vpc_invoke_backdoor(&rp, &rp);
    SetUnhandledExceptionFilter(topexp);
  } /* DO_ATOMIC_LEAVE */
#elif defined(__OS2__)
  { /* DO_ATOMIC_ENTER */
    DosSetExceptionHandler(&xcpt);
    vpc_invoke_backdoor(&rp, &rp);
    DosUnsetExceptionHandler(&xcpt);
  } /* DO_ATOMIC_LEAVE */
#else
# error unsupported target.
#endif

  return rp.r_ebx == 0;
}


#if defined(MAKE_COMMAND)
int
main(int argc, char **argv)
{
  int opt_err = 0, opt_help = 0, opt_check = 0, opt_version = 0, opt_quiet = 0;
  int i;
  int isvpc;
  
  for(i=1; i<argc; ++i) {
    if (strcmp(argv[i], "/?")==0 || strcmp(argv[i], "-?")==0 || strcmp(argv[i], "-h")==0 || strcmp(argv[i], "--help")==0) opt_help = 1;
    else if (strcmp(argv[i], "-V")==0 || strcmp(argv[i], "--version")==0) opt_version = 1;
    else if (strcmp(argv[i], "-q")==0 || strcmp(argv[i], "--quiet")==0) opt_quiet = 1;
    else if (strcmp(argv[i], "-c")==0 || strcmp(argv[i], "--check")==0) opt_quiet = 1;
    else {
      opt_err = 1;
      break;
    }
  }
  if (opt_version || opt_help || opt_err) {
    fprintf(opt_err ? stderr : stdout, "undervpc version 0.00 ("__DATE__")\n");
    if (opt_help || opt_err) {
      fprintf(opt_err ? stderr : stdout,
              "Usage: undervpc [OPTION]\n"
              "Check whether the environment is running under Virtual PC\n"
              "\n"
              "Options:\n"
              "  -c, --check       Return exit code "
                                   "(0=under VPC, 1=not under VPC).\n"
              "  -V, --version     Print version.\n"
              "  -q, --quiet       Do not print the result.\n"
              "  -h, --help        Print this help.\n" );
    }
    return opt_err ? 127 : 0;
  }
  
  isvpc = is_under_vpc();
  if (!opt_quiet) {
    printf("%s,I'm%srunning under VPC.\n",
           isvpc ? "Yes" : "No",
           isvpc ? " " : " not ");
  }
  
  return opt_check ? (!isvpc) : 0;
}
#endif

ちなみに cygwin だとこの方法はダメでした(ハンドラ勝手に弄れないようになってるんすかねえ…)。