--- nono/lib/mainapp.cpp 2026/04/29 17:04:57 1.1.1.9 +++ nono/lib/mainapp.cpp 2026/04/29 17:05:12 1.1.1.13 @@ -4,16 +4,55 @@ // Licensed under nono-license.txt // +// +// CLI/GUI 共通のメイン部分 +// + #include "mainapp.h" +#include "config.h" +#include "hostnet.h" +#include "logger.h" +#include "monitor.h" #include "mystring.h" -#include "netdriver_selector.h" +#include "mythread.h" +#include "vm_luna.h" +#include "vm_x68k.h" #include #include #include +#if defined(__linux__) +#include +#endif // グローバルインスタンス MainApp gMainApp; +// コンストラクタ +MainApp::MainApp() +{ +} + +// デストラクタ +MainApp::~MainApp() +{ + // 逆順に解放 + + pVM.reset(); +#if 0 // 確認用 + if (objects.empty() == false) { + printf("~MainApp: undead objects are:"); + for (const auto *obj : objects) { + printf(" %s", obj->GetName().c_str()); + } + printf("\n"); + } +#endif + + pConfig.reset(); + pMonitorManager.reset(); + logger.reset(); +} + // ヘルプメッセージ void MainApp::ShowHelp(bool all) const @@ -43,10 +82,12 @@ MainApp::ShowHelp(bool all) const p("-B benchmark mode"); p("-C output log to console"); p("-d debugger prompt on startup"); - p("-D debugger on console"); + 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("-H human68k console emulation"); + p("--perf performance measure mode"); } } @@ -55,14 +96,18 @@ MainApp::ShowHelp(bool all) const // 0x80 から始めておく。 enum { OPT_fontsize = 0x80, + OPT_load_only, OPT_help, + OPT_perf, OPT_show_config, OPT_show_config_all, OPT_show_hostnet, }; static struct option longopts[] = { { "fontsize", required_argument, NULL, OPT_fontsize }, + { "load-only", required_argument, NULL, OPT_load_only }, { "help", no_argument, NULL, OPT_help }, + { "perf", no_argument, NULL, OPT_perf }, { "show-config", no_argument, NULL, OPT_show_config }, // --show-config-all は開発用なのでヘルプには載せない { "show-config-all",no_argument, NULL, OPT_show_config_all }, @@ -81,6 +126,12 @@ MainApp::Init1(bool is_cli_, int ac, cha { is_cli = is_cli_; +#if defined(__linux__) + // Linux ではケーパビリティが設定されていると coredump しないので + // 明示的に許可する必要がある + prctl(PR_SET_DUMPABLE, 1, 0, 0, 0); +#endif + if (!ParseOpt(ac, av)) { return EXIT_FAILURE; } @@ -88,10 +139,16 @@ MainApp::Init1(bool is_cli_, int ac, cha // ログ機構は引数処理後なるはや // CLI ならログは常に標準出力へ(も)出力。 // GUI なら -C で指定。 - gLogger.UseStdout(IsCLI() ? true : log_to_console); + logger.reset(new Logger()); + logger->UseStdout(IsCLI() ? true : log_to_console); + + // モニタマネージャ (VM 作成より前、もしかしたら Config より前?) + pMonitorManager.reset(new MonitorManager()); + gMonitorManager = pMonitorManager.get(); // 設定を作成。コンストラクタで初期値を用意 - gConfig.reset(new Config()); + pConfig.reset(new Config()); + gConfig = pConfig.get(); // 引数で指定されたディレクトリの設定ファイルがあれば読み込んで設定を更新 ConfigFile file(vmfile); @@ -106,16 +163,16 @@ MainApp::Init1(bool is_cli_, int ac, cha return EXIT_FAILURE; } - // 1. VM 種別を決定 + // 0. VM 種別を決定 // VM 種別文字列から vmtype を決定 const ConfigItem& item = gConfig->Find("vmtype"); std::string vmstr = string_tolower(item.AsString()); if (vmstr == "x68030") { - vmtype = VMTYPE_X68030; + vmtype = VMType::X68030; } else if (vmstr == "luna") { - vmtype = VMTYPE_LUNA1; + vmtype = VMType::LUNA1; } else if (vmstr == "luna88k") { - vmtype = VMTYPE_LUNA88K; + vmtype = VMType::LUNA88K; } else { if (item.GetFrom() == ConfigItem::FromInitial) { // 未指定の時 @@ -127,32 +184,32 @@ MainApp::Init1(bool is_cli_, int ac, cha return EXIT_FAILURE; } - // 2. VM 作成 + // 1. VM 作成 switch (vmtype) { - case VMTYPE_X68030: - gVM.reset(new VM_X68030()); + case VMType::X68030: + pVM.reset(new VM_X68030()); break; - case VMTYPE_LUNA1: - gVM.reset(new VM_LUNA1()); + case VMType::LUNA1: + pVM.reset(new VM_LUNA1()); break; - case VMTYPE_LUNA88K: - gVM.reset(new VM_LUNA88K()); + case VMType::LUNA88K: + pVM.reset(new VM_LUNA88K()); break; default: __unreachable(); } + gVM = pVM.get(); - // 3. 動的なコンストラクション + // 2. 動的なコンストラクション if (!gVM->Create()) { - gVM.reset(); + pVM.reset(); return EXIT_FAILURE; } - // デバッガ (ここでもモニタを登録する) - debugger_create(); + // 3. ログの処理 // 設定内容を表示して終了 // (VM コンストラクタで変数の増減があるのでそれより後、 @@ -160,39 +217,17 @@ MainApp::Init1(bool is_cli_, int ac, cha gConfig->Fix(); if (show_config) { gConfig->Show(show_config - 1); - gVM.reset(); + pVM.reset(); return EXIT_SUCCESS; } - // 4. ログレベルを設定。コンストラクト後すぐに行う。 + // ログレベルを設定。コンストラクト後すぐに行う。 // -L help もここで処理。 if (!ParseLogopt()) { - gVM.reset(); + pVM.reset(); return EXIT_FAILURE; } - // 5. ホストネットワークドライバの作成。 - // - // ホストネットワークドライバを作成する時にはログ表示が欲しいが、通常の - // フローではオブジェクトが出揃った後でログレベルを割り当てるので、 - // にわたま問題になってしまう。そこでこれだけ特別対応する。 - // 手順 3. のコンストラクションで EthernetDevice が NetDriverSelector を - // 作成し、手順 4. で通常通りにログレベルの割り当てを受ける。このログを - // 使ってここ(手順5)で実際の NetDriver を作成し、同時にログレベルも引き - // 継ぐことにする。 - if ((bool)gNetDriverSelector) { - // 実際の NetDriver を作成 - NetDriver *newdriver = gNetDriverSelector->InitDriver(); - if (newdriver == NULL) { - gVM.reset(); - return EXIT_FAILURE; - } - // 成功すればセット - gNetDriver.reset(newdriver); - - // NetDriverSelector はこれ以降まったく使わないがとりあえず放置。 - } - return PASS; } @@ -203,19 +238,21 @@ MainApp::Init1(bool is_cli_, int ac, cha bool MainApp::Init2() { - // VM 初期化。 - // これ以降はスレッドを開始したかもしれないので false を返す際には - // VM をデストラクトすること。 + // 5. VM 初期化。 if (!gVM->Init()) { - gVM.reset(); return false; } - // デバッガは VM オブジェクトリストに入っていない - debugger_init(); - // 起動時設定の適用 + // メインスレッド名を設定 + PTHREAD_SETNAME("Main"); + + // 6. スレッド開始 + if (!gVM->StartThread()) { + return false; + } + + // 7. 起動時設定の適用 if (!gVM->Apply()) { - gVM.reset(); return false; } @@ -269,7 +306,10 @@ MainApp::ParseOpt(int ac, char *av[]) break; case 'D': - debug_on_console = true; + // -D は -V debugger-driver=stdio と等価。 + config_options.push_back("debugger-driver=stdio"); + // エラー表示用に -D で指定されたことを覚えておく。 + opt_D = true; break; case 'f': @@ -281,10 +321,7 @@ MainApp::ParseOpt(int ac, char *av[]) break; case 'L': - if (logopt[0] != '\0') { - strlcat(logopt, ",", sizeof(logopt)); - } - strlcat(logopt, optarg, sizeof(logopt)); + AddLogopt(optarg); break; case 'M': @@ -312,6 +349,9 @@ 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) { @@ -338,6 +378,28 @@ MainApp::ParseOpt(int ac, char *av[]) ShowHelp(true); return false; + case OPT_perf: + // パフォーマンス確認用。 + // -Vprom-image=PROM.DAT はパスの問題があるので各自で追加指定 + // する必要がある。 + config_options.emplace_back(".rtc-force-fixed=1"); + config_options.emplace_back("clock-sync=virtual"); + config_options.emplace_back("ethernet-macaddr=02:00:00:00:00:01"); + config_options.emplace_back("hostcom-driver=none"); + config_options.emplace_back("hostnet-driver=none"); + config_options.emplace_back("luna-dipsw1=11110111"); + config_options.emplace_back("spc0-id0-writeignore=1"); + config_options.emplace_back("spc0-id1-writeignore=1"); + config_options.emplace_back("spc0-id2-writeignore=1"); + config_options.emplace_back("spc0-id3-writeignore=1"); + config_options.emplace_back("spc0-id4-writeignore=1"); + config_options.emplace_back("spc0-id5-writeignore=1"); + config_options.emplace_back("spc0-id6-writeignore=1"); + // -f -C + fast_mode = true; + log_to_console = true; + break; + case OPT_show_config: show_config = 1; break; @@ -421,6 +483,16 @@ MainApp::ShowVersion() const NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER, NONO_DATE); } +// ログレベル指定文字列を logopt に追加する。 +void +MainApp::AddLogopt(const char *opt) +{ + if (logopt.empty() == false) { + logopt += ','; + } + logopt += opt; +} + // ログレベル指定文字列を処理する。 // str はログレベル指定文字列を ',' で連結した "foo=1,bar=2" 形式の文字列で、 // これを分解してそれぞれ担当するオブジェクトのログレベルにセットする。 @@ -431,7 +503,7 @@ MainApp::ParseLogopt() std::vector items; // 分解して.. - items = string_split(logopt, ','); + items = string_split(logopt.c_str(), ','); // "help" があればヘルプを表示して終了 for (const auto& item : items) { @@ -470,7 +542,7 @@ MainApp::SetLogopt(const std::vectorGetName().c_str(), o->loglevel); } } @@ -508,36 +580,77 @@ MainApp::SetLogopt1(const std::string& i return false; } + // 今の所、値域は -1 〜 9 ということにしておく。 + if (level < -1) { + level = -1; + } else if (level > 9) { + level = 9; + } + // "all" なら全部にセット if (name == "all") { - for (auto obj : gObjects) { + for (auto obj : gMainApp.GetObjects()) { if (obj->GetName().empty() == false) { - obj->loglevel = level; + obj->SetLogLevel(level); + } + } + return true; + } + // "fdd" なら "fdd*" 全部にする + if (name == "fdd") { + for (auto obj : gMainApp.GetObjects()) { + if (strncasecmp(obj->GetName().c_str(), "fdd", 3) == 0) { + obj->SetLogLevel(level); } } return true; } - std::vector found; // 候補オブジェクト - std::vector candidates; // 候補名 - - // 前方一致する名前(とそのオブジェクト)を列挙する - for (auto *obj : gObjects) { + // エイリアスリストを作る + using aliaslist_t = std::vector>; + aliaslist_t alias_list; + for (const auto& obj : gMainApp.GetObjects()) { const std::vector& aliases = obj->GetAliases(); for (const auto& a : aliases) { - // 完全一致したら即採用 - // (前方一致しつつ完全一致したらこの時点で採用して打ち切るため) - if (strcasecmp(a.c_str(), name.c_str()) == 0) { - obj->loglevel = level; - return true; - } + alias_list.emplace_back(a, obj); + } + } - // 前方一致したら覚えておく - if (strncasecmp(a.c_str(), name.c_str(), name.size()) == 0) { - candidates.emplace_back(a); - found.push_back(obj); + // エイリアスを完全一致のみのものと部分一致も許容するものに分ける + aliaslist_t exact_alias; + aliaslist_t partial_alias; + for (const auto& a0 : alias_list) { + bool exact = false; + for (const auto& a1 : alias_list) { + if (a0.first == a1.first) { + continue; } + if (starts_with_ignorecase(a0.first, a1.first)) { + exact = true; + break; + } + } + if (exact) { + exact_alias.push_back(a0); + } else { + partial_alias.push_back(a0); + } + } + + // 完全一致をまず調べる + for (const auto& a : exact_alias) { + if (strcasecmp(a.first.c_str(), name.c_str()) == 0) { + a.second->SetLogLevel(level); + return true; + } + } + + aliaslist_t found; + for (const auto& a : partial_alias) { + // 前方一致したら覚えておく + if (starts_with_ignorecase(a.first, name)) { + found.push_back(a); } } @@ -549,16 +662,15 @@ MainApp::SetLogopt1(const std::string& i // 1つだけなら確定 if (found.size() == 1) { - Object *obj = found[0]; - obj->loglevel = level; + found[0].second->SetLogLevel(level); return true; } // 複数あれば候補文字列を作成 *errmsg = string_format("Ambiguous logname \"%s\": candidates are", name.c_str()); - for (const auto& cand : candidates) { - *errmsg += string_format(" \"%s\"", cand.c_str()); + for (const auto& cand : found) { + *errmsg += string_format(" \"%s\"", cand.first.c_str()); } return false; } @@ -571,7 +683,7 @@ MainApp::GetLogNames() std::vector sortobj; // エイリアスを持つオブジェクトだけ抜き出す - for (const auto& obj : gObjects) { + for (const auto& obj : gMainApp.GetObjects()) { if (obj->GetAliases().empty() == false) { sortobj.emplace_back(obj); } @@ -579,10 +691,10 @@ MainApp::GetLogNames() // aliases の一語目で sortobj をソートする std::sort(sortobj.begin(), sortobj.end(), - [](const auto& a, const auto *b) { + [](const auto a, const auto b) { const auto& sa = a->GetAliases()[0]; const auto& sb = b->GetAliases()[0]; - return strcmp(sa.c_str(), sb.c_str()) < 0; + return sa < sb; } ); @@ -591,7 +703,7 @@ MainApp::GetLogNames() const auto& aliases = obj->GetAliases(); std::string str; - str += aliases[0]; + str = aliases[0]; if (aliases.size() > 1) { str += " (alias:"; for (int i = 1, sz = aliases.size(); i < sz; i++) { @@ -657,20 +769,42 @@ MainApp::SearchFile(const std::string& n return path; } -// 現在の VM が指定の feature を持っているか? +// 現在の VM が指定のケーパビリティを持っているか? bool -MainApp::HasVMFeature(vmf_t feature) const +MainApp::Has(VMCap cap) const { - uint32 vmf = 1 << GetVMType(); - return (vmf & feature) != 0; + uint32 vmcap = 1U << (int)GetVMType(); + return (vmcap & (uint32)cap) != 0; } // コンパイルされている host netdriver の一覧を表示。 void MainApp::ShowHostnet() const { - auto list = NetDriverSelector::GetDrivers(); + auto list = HostNetDevice::GetDrivers(); for (const auto& name : list) { printf(" %s\n", name.c_str()); } } + +// オブジェクトを追加 +void +MainApp::AddObject(Object *obj) +{ + obj->logger = GetLogger(); + + objects.push_back(obj); +} + +// オブジェクトを削除 +// XXX マルチスレッドな状況からは呼ばれないはず? +void +MainApp::DeleteObject(Object *obj) +{ + for (auto it = objects.begin(); it != objects.end(); ++it) { + if (*it == obj) { + objects.erase(it); + break; + } + } +}