--- nono/lib/mainapp.cpp 2026/04/29 17:05:18 1.1.1.15 +++ nono/lib/mainapp.cpp 2026/04/29 17:05:22 1.1.1.16 @@ -9,6 +9,7 @@ // #include "mainapp.h" +#include "accel_avx2.h" #include "config.h" #include "hostnet.h" #include "logger.h" @@ -18,10 +19,12 @@ #include "sram.h" #include "vm_luna.h" #include "vm_news.h" +#include "vm_virt68k.h" #include "vm_x68k.h" #include #include #include +#include #include #include #if defined(__linux__) @@ -62,6 +65,15 @@ MainApp::~MainApp() logger.reset(); } +// VM を解放。 +void +MainApp::Dispose() +{ + if ((bool)pVM) { + pVM->Dispose(); + } +} + // ヘルプメッセージ void MainApp::ShowHelp(bool all) const @@ -83,19 +95,20 @@ MainApp::ShowHelp(bool all) const p("--show-hostnet show list of hostnet drivers"); p("-v show version"); p("-V = overwrite config option"); - p("-X [arg..] load and execute host binary (a.out or ELF)"); + p("-X load and execute host binary (a.out or ELF)"); + p("--initrd initial ramdisk (only for virt68k)"); if (all) { printf("\n(options for developers)\n"); p("-b [,][,]"); p(" set breakpoint. is either 'main'(default) or 'xp'"); + p("--bi-exg set instruction breakpoint on 'exg sp,sp'"); p("-B benchmark mode"); p("-C output log to console"); p("-d debugger prompt on startup"); p("-D same as '-V debugger-driver=stdio'"); p("-H human68k console emulation"); p("-L =[,..] set loglevel (-Lhelp displays names)"); - p("--load-only load host binary (a.out or ELF) but not execute"); p("-M [,..] monitors to display at startup (-Mhelp displays names)"); p(" memdump[=[.]] fmt := B/W/L/M(MMU)/I(Disasm)/Z(XPDisasm)"); p("-S MSX-DOS console emulation"); @@ -108,20 +121,22 @@ MainApp::ShowHelp(bool all) const // 0x80 から始めておく。 enum { OPTstart = 0x80 - 1, + OPT_bi_exg, OPT_create_sram, OPT_fontsize, - OPT_load_only, OPT_help, + OPT_initrd, OPT_perf, OPT_show_config, OPT_show_config_all, OPT_show_hostnet, }; static struct option longopts[] = { + { "bi-exg", no_argument, NULL, OPT_bi_exg }, { "create-sram", no_argument, NULL, OPT_create_sram }, { "fontsize", required_argument, NULL, OPT_fontsize }, - { "load-only", required_argument, NULL, OPT_load_only }, { "help", no_argument, NULL, OPT_help }, + { "initrd", required_argument, NULL, OPT_initrd }, { "perf", no_argument, NULL, OPT_perf }, { "show-config", no_argument, NULL, OPT_show_config }, // --show-config-all は開発用なのでヘルプには載せない @@ -143,6 +158,22 @@ MainApp::Init1(bool is_cli_, int ac, cha is_cli = is_cli_; + rv = Init1a(ac, av); + if (rv != MainApp::PASS) { + return rv; + } + + rv = Init1b(); + return rv; +} + +// VM の初期化、ステージ1 の前半。 +// Config.Fix まで。 +int +MainApp::Init1a(int ac, char *av[]) +{ + int rv; + #if defined(__linux__) // Linux ではケーパビリティが設定されていると coredump しないので // 明示的に許可する必要がある @@ -174,7 +205,7 @@ MainApp::Init1(bool is_cli_, int ac, cha // 見付からないことはないはずだが、一応。 if (passwd != NULL) { std::string filename = std::string(passwd->pw_dir) + "/.nono.cfg"; - ConfigFile dotfile(filename); + ConfigFile dotfile(filename, ConfigItem::FromHome); if (dotfile.Load()) { if (gConfig->Update(dotfile) == false) { return EXIT_FAILURE; @@ -184,7 +215,7 @@ MainApp::Init1(bool is_cli_, int ac, cha // c2. VM ディレクトリに設定ファイルがあれば読み込んで設定を更新。 // vmfile が存在するかどうかはここで調べるまで分からない。 - ConfigFile cfgfile(vmfile); + ConfigFile cfgfile(vmfile, ConfigItem::FromConfig); if (cfgfile.Load()) { if (gConfig->Update(cfgfile) == false) { return EXIT_FAILURE; @@ -198,6 +229,17 @@ MainApp::Init1(bool is_cli_, int ac, cha } } + // c4. exec-file, exec-arg をここでメンバ変数 exec_file, exec_arg に代入。 + // (c3 までに確定していて、1. VM コンストラクト時には必要) + const ConfigItem& itemexec = gConfig->Find("exec-file"); + const std::string& execstr = itemexec.AsString(); + if (execstr.empty() == false) { + // 歴史的経緯で exec_file は const char *。 + exec_file = strdup(NormalizePath(execstr).c_str()); + } + const ConfigItem& itemarg = gConfig->Find("exec-arg"); + exec_arg = itemarg.AsString(); + // MSX-DOS モードならここでいくつかパラメータを強制的に変更。 if (msxdos_mode) { // LUNA-I 最小構成でいい @@ -209,6 +251,11 @@ MainApp::Init1(bool is_cli_, int ac, cha gConfig->Update("hostnet-driver=none", "-S"); } + // ホスト CPU の機能チェック。 + if (CheckCPU() == false) { + return EXIT_FAILURE; + } + // 0. VM 種別を決定 // VM 種別文字列から vmtype を決定 const ConfigItem& item = gConfig->Find("vmtype"); @@ -221,6 +268,8 @@ MainApp::Init1(bool is_cli_, int ac, cha vmtype = VMType::LUNA88K; } else if (vmstr == "news") { vmtype = VMType::NEWS; + } else if (vmstr == "virt68k") { + vmtype = VMType::VIRT68K; } else { if (item.GetFrom() == ConfigItem::FromInitial) { // 未指定の時 @@ -260,39 +309,90 @@ MainApp::Init1(bool is_cli_, int ac, cha pVM.reset(new VM_NEWS()); break; + case VMType::VIRT68K: + pVM.reset(new VM_Virt68k()); + break; + default: - PANIC("corrupted vmtype=%s", vmstr.c_str()); + PANIC("vmtype=%s not configured", vmstr.c_str()); } - gVM = pVM.get(); // 2. 動的なコンストラクション - if (!gVM->Create()) { + if (!pVM->Create()) { pVM.reset(); return EXIT_FAILURE; } - // 3. ログの処理 - - // 設定内容を表示して終了 + // 3. 設定を確定。 // (VM コンストラクタで変数の増減があるのでそれより後、 // Create() でも SCSI パラメータを減らすので、それより後) if (gConfig->Fix() == false) { pVM.reset(); return EXIT_SUCCESS; } + + // Fix() は配列から削除を行うため、これより前に取得していた ConfigItem + // のポインタや参照は無効になっている可能性がある。Fix() をまたいで + // ポインタや参照を使ってしまわないようここで一旦フォーカスを抜ける。 + // なお Fix() 後は要素の削除は出来ないため、以降はこの問題は発生しない。 + + return PASS; +} + +// VM の初期化、ステージ1 の後半。 +// Config.Fix 以降。 +int +MainApp::Init1b() +{ + // 3.1 設定が確定したので --show-config なら設定内容を表示して終了。 if (show_config) { gConfig->Show(show_config - 1); pVM.reset(); return EXIT_SUCCESS; } - // ログレベルを設定。コンストラクト後すぐに行う。 + // 3.2 ログレベルを設定。コンストラクト後すぐに行う。 // -L help もここで処理。 if (!ParseLogopt()) { pVM.reset(); return EXIT_FAILURE; } + // 3.3 -X のチェック。 + // -L help より後にしないとヘルプが表示できない。 + const ConfigItem& itemexec = gConfig->Find("exec-file"); + if (exec_file == NULL) { + // -X が必要なケースで指定されてなければエラー。 + + // 実行ファイル指定が必須の VM。 + if (vmtype == VMType::NEWS || vmtype == VMType::VIRT68K) { + if (itemexec.GetFrom() == ConfigItem::FromInitial) { + warnx("vmtype=%s requires -X option (or exec-file).", + vmstr.c_str()); + } else { + itemexec.Err("filename must be specified."); + } + return EXIT_FAILURE; + } + + // Human モード、MSX-DOS モードでも実行ファイル名が必要。 + if (human_mode) { + warnx("-H option needs -X"); + return EXIT_FAILURE; + } + if (msxdos_mode) { + warnx("-S option needs -X"); + return EXIT_FAILURE; + } + } else { + // 実行ファイルのファイル名を間違えたくらいならここでエラーに出来る。 + int r = access(exec_file, R_OK); + if (r != 0) { + itemexec.Err("%s", strerror(errno)); + return EXIT_FAILURE; + } + } + return PASS; } @@ -304,7 +404,7 @@ bool MainApp::Init2() { // 5. VM 初期化。 - if (!gVM->Init()) { + if (!pVM->Init()) { return false; } @@ -312,12 +412,12 @@ MainApp::Init2() PTHREAD_SETNAME("Main"); // 6. スレッド開始 - if (!gVM->StartThread()) { + if (!pVM->StartThread()) { return false; } // 7. 起動時設定の適用 - if (!gVM->Apply()) { + if (!pVM->Apply()) { return false; } @@ -407,18 +507,30 @@ MainApp::ParseOpt(int ac, char *av[]) break; case 'X': - load_and_exec = true; - FALLTHROUGH; - case OPT_load_only: - exec_file = optarg; - for (int i = optind; i < ac; i++) { - if (i != optind) { - exec_arg += " "; + { + // 相対パスはカレントディレクトリ起点とする。 + char cwd[PATH_MAX]; + getcwd(cwd, sizeof(cwd)); + std::string path = NormalizePath(optarg, std::string(cwd)); + std::string line = "exec-file=" + path; + config_options.emplace_back(line, "-X"); + + // Human68k モードなら後続を引数にする。 + // UNIX モードでは親和性が悪いのでしない。 + if (human_mode) { + std::string arg; + arg = "exec-arg="; + for (int i = optind; i < ac; i++) { + if (i != optind) { + arg += " "; + } + arg += av[i]; } - exec_arg += av[i]; + optind = ac; + config_options.emplace_back(arg, "-X"); } - optind = ac; break; + } case 'v': ShowVersion(); @@ -428,6 +540,10 @@ MainApp::ParseOpt(int ac, char *av[]) config_options.emplace_back(optarg, "-V"); break; + case OPT_bi_exg: + debug_breakinst_exg = true; + break; + case OPT_create_sram: create_sram = true; break; @@ -443,28 +559,42 @@ MainApp::ParseOpt(int ac, char *av[]) ShowHelp(true); return EXIT_SUCCESS; + case OPT_initrd: + { + // 相対パスはカレントディレクトリを起点とする。 + char cwd[PATH_MAX]; + getcwd(cwd, sizeof(cwd)); + std::string path = NormalizePath(optarg, std::string(cwd)); + std::string line = "exec-initrd=" + path; + config_options.emplace_back(line, "--initrd"); + break; + } + case OPT_perf: + { // パフォーマンス確認用。 // -Vprom-image=PROM.DAT はパスの問題があるので各自で追加指定 // する必要がある。 - config_options.emplace_back(".rtc-force-fixed=1", "--perf"); - config_options.emplace_back("clock-sync=virtual", "--perf"); + std::string where = "--perf -V"; + config_options.emplace_back(".rtc-force-fixed=1", where); + config_options.emplace_back("clock-sync=virtual", where); config_options.emplace_back("ethernet-macaddr=02:00:00:00:00:01", - "--perf"); - config_options.emplace_back("hostcom-driver=none", "--perf"); - config_options.emplace_back("hostnet-driver=none", "--perf"); - config_options.emplace_back("luna-dipsw1=11110111", "--perf"); - config_options.emplace_back("spc0-id0-writeignore=1", "--perf"); - config_options.emplace_back("spc0-id1-writeignore=1", "--perf"); - config_options.emplace_back("spc0-id2-writeignore=1", "--perf"); - config_options.emplace_back("spc0-id3-writeignore=1", "--perf"); - config_options.emplace_back("spc0-id4-writeignore=1", "--perf"); - config_options.emplace_back("spc0-id5-writeignore=1", "--perf"); - config_options.emplace_back("spc0-id6-writeignore=1", "--perf"); - config_options.emplace_back("fast-mode=1", "--perf"); + where); + config_options.emplace_back("hostcom-driver=none", where); + config_options.emplace_back("hostnet-driver=none", where); + config_options.emplace_back("luna-dipsw1=11110111", where); + config_options.emplace_back("spc0-id0-writeignore=1", where); + config_options.emplace_back("spc0-id1-writeignore=1", where); + config_options.emplace_back("spc0-id2-writeignore=1", where); + config_options.emplace_back("spc0-id3-writeignore=1", where); + config_options.emplace_back("spc0-id4-writeignore=1", where); + config_options.emplace_back("spc0-id5-writeignore=1", where); + config_options.emplace_back("spc0-id6-writeignore=1", where); + config_options.emplace_back("fast-mode=1", where); // -C log_to_console = true; break; + } case OPT_show_config: show_config = 1; @@ -532,30 +662,31 @@ MainApp::ParseOpt(int ac, char *av[]) } } - // Human モード、MSX-DOS モードでは実行ファイル名が必要 - if (human_mode) { - if (exec_file == NULL) { - warnx("-H option needs -X"); - return EXIT_FAILURE; - } - } - if (msxdos_mode) { - if (exec_file == NULL) { - warnx("-S option needs -X"); - return EXIT_FAILURE; - } - } + return MainApp::PASS; +} - // 実行ファイルは、ファイル名を間違えたくらいならここでエラーに出来る。 - if (exec_file) { - int r = access(exec_file, R_OK); - if (r != 0) { - warn("-X %s", exec_file); - return EXIT_FAILURE; - } +// CPU の機能とかをチェック。 +bool +MainApp::CheckCPU() +{ +#if defined(HAVE_AVX2) + // AVX2 + detect_avx2 = ::DetectAVX2(); + + const ConfigItem& item_avx2 = gConfig->Find("host-avx2"); + std::string cfg_avx2 = item_avx2.AsString(); + + if (cfg_avx2 == "auto") { + enable_avx2 = detect_avx2; + } else if (cfg_avx2 == "no") { + enable_avx2 = false; + } else { + item_avx2.Err("must be either \"auto\" or \"no\"."); + return false; } - - return MainApp::PASS; +#endif + // AVX2 以外は何もしない。設定のチェックもしないほうがいい。 + return true; } // バージョンを表示 @@ -698,6 +829,15 @@ MainApp::SetLogopt1(const std::string& i } return true; } + // "gfpic" も "gfpic*" 全部にする + if (name == "gfpic") { + for (auto obj : gMainApp.GetObjects()) { + if (strncasecmp(obj->GetName().c_str(), "gfpic", 5) == 0) { + obj->SetLogLevel(level); + } + } + return true; + } // エイリアスリストを作る using aliaslist_t = std::vector>; @@ -817,24 +957,23 @@ MainApp::GetLogNames() // ファイルが存在すればその時点で戻り値とする。ファイルが存在したけど // 何らか不都合があったとしても (例えばファイルサイズが 0 だとか // パーミッションが足りないとか) 次を試すとかはしない。 -// 2. name が '~' から始まっていればホームディレクトリに展開。 -// 3. name が相対パスなら VM ディレクトリからの相対パスとする。 -// 4. name は絶対パスのはずなので、そのまま使用する。 +// 2. 通常手順(相対パスは VM ディレクトリ起点) でパスを展開。 +// ファイルの有無は不問。 std::string MainApp::SearchFile(const std::string& name) const { - std::string path; - struct stat st; - // 1. パス区切りを含んでなければ、ファイル名のみ if (name.find('/') == std::string::npos) { + std::string path; + struct stat st; + // 1a. VM ディレクトリ path = GetVMDir() + name; if (stat(path.c_str(), &st) == 0) { return path; } - // 2. 親ディレクトリ + // 1b. 親ディレクトリ path = GetVMDir() + "../" + name; if (stat(path.c_str(), &st) == 0) { return path; @@ -844,22 +983,51 @@ MainApp::SearchFile(const std::string& n return ""; } - // 2. 先頭の '~' を $HOME に展開 - path = name; - if (path[0] == '~' && path[1] == '/') { - const char *home = getenv("HOME"); - if (home == NULL) { - home = ""; - } - path = string_format("%s%s", home, path.c_str() + 1); + // 2. 通常手順でパスを展開。 + return NormalizePath(name); +} + +// path を必要なら絶対パスに展開して返す。 +// 相対パスは VM ディレクトリを起点とする。 +std::string +MainApp::NormalizePath(const std::string& path) const +{ + return NormalizePath(path, GetVMDir()); +} + +// path を必要なら絶対パスに展開して返す。 +// 相対パスは basedir を起点とする。 +std::string +MainApp::NormalizePath(const std::string& path, + const std::string& basedir) const +{ + // 絶対パスならそのまま返す。 + if (path[0] == '/') { + return path; } - // 3. 相対パスなら、VM ディレクトリからの相対 - if (path[0] != '/') { - path = GetVMDir() + path; + std::string newpath; + + if (path[0] == '~') { + // 先頭が '~' ならホームディレクトリに展開。 + + struct passwd *passwd = getpwuid(getuid()); + // 見付からないことはないはずだが、一応。 + if (passwd != NULL) { + newpath = std::string(passwd->pw_dir); + } + newpath += &path[1]; + } else { + // basedir からの相対パスとして展開。 + + newpath = basedir; + if (newpath.empty() || newpath.back() != '/') { + newpath += '/'; + } + newpath += path; } - return path; + return newpath; } // 初回起動時用に SRAM.DAT を作成する。 @@ -912,6 +1080,17 @@ MainApp::Has(VMCap cap) const return (vmcap & (uint32)cap) != 0; } +// VM 機種名を返す。 +const char * +MainApp::GetVMName() const +{ + if ((bool)pVM) { + return pVM->GetVMName(); + } else { + return NULL; // not configured + } +} + // コンパイルされている host netdriver の一覧を表示。 void MainApp::ShowHostnet() const