|
|
1.1 root 1: //
2: // nono
1.1.1.3 ! root 3: // Copyright (C) 2020 nono project
! 4: // Licensed under nono-license.txt
1.1 root 5: //
6:
7: #include "mainapp.h"
1.1.1.2 root 8: #include "mystring.h"
9: #include <getopt.h>
10: #include <sys/stat.h>
11:
12: // グローバルインスタンス
13: MainApp gMainApp;
14:
15: // ヘルプメッセージ
16: void
17: MainApp::ShowHelp(bool all) const
18: {
19: ShowVersion();
20:
21: #define p(msg) printf(" " msg "\n")
22:
23: printf("usage: %s [<options>]\n", getprogname());
24: p("-A <file> load and execute host binary (a.out or ELF)");
25: p("-c <dir> vm directory");
26: p("-f fast mode");
27: if (IsGUI())
28: p("--fontsize <height> fontsize in monitors {12,16,24} (default:12)");
29: p("-h show brief help message");
30: p("--help show all help message including for developers");
31: if (IsGUI())
32: p("-s,--scale <scale> screen scale, default 1.0");
33: p("--show-config show configuration variables");
34: p("-v show version");
35: p("-V <key>=<val> overwrite config option");
36:
37: if (all) {
38: printf("\n(options for developers)\n");
39: p("-b <addr> breakpoint");
40: p("-B <rom#> benchmark mode");
41: p("-C output log to console");
42: p("-d debugger prompt on startup");
43: p("-D debugger on console");
44: p("-L <name>=<lv>[,..] set loglevel (-Lhelp displays names)");
45: p("-M <name>[,..] monitors to display at startup (-Mhelp displays names)");
46: p("-X <human68k executable> human68k console emulation");
47: }
48: }
49:
50: // long option は enum と struct option をアルファベット順に並べる。
51: // enum は getopt() の1文字のオプションと衝突しなければいいので適当に
52: // 0x80 から始めておく。
53: enum {
54: OPT_fontsize = 0x80,
55: OPT_help,
56: OPT_show_config,
1.1.1.3 ! root 57: OPT_show_config_all,
1.1.1.2 root 58: };
59: static struct option longopts[] = {
60: { "fontsize", required_argument, NULL, OPT_fontsize },
61: { "help", no_argument, NULL, OPT_help },
62: { "show-config", no_argument, NULL, OPT_show_config },
1.1.1.3 ! root 63: // --show-config-all は開発用なのでヘルプには載せない
! 64: { "show-config-all",no_argument, NULL, OPT_show_config_all },
1.1.1.2 root 65: { NULL, 0, NULL, 0 },
66: };
67:
68: // 起動時の処理、VM の実行開始前まで。
69: // 所々 CLI と GUI で処理が違う。
70: bool
71: MainApp::Init(bool is_cli_, int ac, char *av[])
72: {
73: is_cli = is_cli_;
74:
75: if (!ParseOpt(ac, av)) {
76: return false;
77: }
1.1 root 78:
1.1.1.2 root 79: // ログ機構は引数処理後なるはや
80: // CLI ならログは常に標準出力へ(も)出力。
81: // GUI なら -C で指定。
1.1.1.3 ! root 82: gLogger.UseStdout(IsCLI() ? true : log_to_console);
1.1.1.2 root 83:
84: // 設定を作成。コンストラクタで初期値を用意
85: gConfig.reset(new Config());
86:
87: // 引数で指定されたディレクトリの設定ファイルを読み込んで設定を更新
88: ConfigFile file(vmdir + "nono.cfg");
89: if (file.Load() == false) {
90: return false;
91: }
92: if (gConfig->Update(file) == false) {
93: return false;
94: }
95: // 最後にコマンドライン引数で更新
96: if (gConfig->Update(config_options) == false) {
97: return false;
98: }
1.1 root 99:
1.1.1.2 root 100: // VM 種別を決定
101: if (IsCLI() && human68k_file) {
102: vmtype = VMTYPE_RXZ;
103: } else {
104: // VM 種別文字列から vmtype を決定
1.1.1.3 ! root 105: const ConfigItem& item = gConfig->Find("vmtype");
1.1.1.2 root 106: std::string vmstr = string_tolower(item.AsString());
107: if (vmstr == "x68030") {
108: vmtype = VMTYPE_X68030;
109: } else if (vmstr == "luna") {
1.1.1.3 ! root 110: vmtype = VMTYPE_LUNA1;
1.1.1.2 root 111: } else if (vmstr == "luna88k") {
112: vmtype = VMTYPE_LUNA88K;
113: } else {
114: if (item.GetFrom() == ConfigItem::FromInitial) {
115: // 未指定の時
116: warnx("vmtype must be specified");
117: } else {
118: // ユーザ由来の時
119: item.Err("invalid vmtype");
120: }
121: return false;
122: }
123: }
1.1 root 124:
1.1.1.2 root 125: // VM 作成
126: switch (vmtype) {
127: case VMTYPE_X68030:
128: gVM.reset(new VM_X68030());
129: break;
130:
1.1.1.3 ! root 131: case VMTYPE_LUNA1:
1.1.1.2 root 132: gVM.reset(new VM_LUNA());
133: break;
134:
135: case VMTYPE_RXZ:
136: gVM.reset(new VM_RXZ());
137: break;
138:
139: case VMTYPE_LUNA88K:
140: gVM.reset(new VM_LUNA88K());
141: break;
1.1 root 142:
1.1.1.2 root 143: default:
144: __unreachable();
145: }
146:
147: // 設定内容表示
148: // (MPU コンストラクタでクロック数の初期値を決めるため、その後で表示)
1.1.1.3 ! root 149: gConfig->Fix();
1.1.1.2 root 150: if (show_config) {
1.1.1.3 ! root 151: gConfig->Show(show_config - 1);
1.1.1.2 root 152: }
153:
154: // 動的なコンストラクション
155: if (!gVM->Create()) {
156: return false;
157: }
158:
159: // ログレベルを設定。コンストラクト後すぐに行う。
160: // -L help もここで処理。
161: if (!ParseLogopt()) {
162: return false;
163: }
164:
165: return true;
166: }
167:
168: // VM の実行部分。
169: // Init() で引数を受け付けてから Start() でデバッガスレッドなどを起動するが、
170: // -Mhelp とかが指定された場合はここでスレッド開始する前にプロセスを終了する
171: // 必要があるので、分けてある。
172: bool
173: MainApp::Start()
174: {
175: // VM 初期化。
176: // これ以降はスレッドを開始したかもしれないので false を返す際には
177: // VM をデストラクトすること。
178: if (!gVM->Init()) {
179: gVM.reset();
180: return false;
181: }
182: // デバッガは VM オブジェクトリストに入っていない
183: debugger_init();
184:
185: // 起動時設定の適用
186: if (!gVM->Apply()) {
187: gVM.reset();
188: return false;
189: }
190:
191: return true;
192: }
193:
194: // コマンドライン引数を処理する。
195: // 知らない引数とかがあればこちらで usage を表示して false を返す。
196: bool
197: MainApp::ParseOpt(int ac, char *av[])
198: {
199: int b;
200: int c;
1.1.1.3 ! root 201: uint32 u;
1.1.1.2 root 202:
203: fontsize = 12;
204: screen_scale = 1.0;
205:
206: while ((c = getopt_long(ac, av, "A:b:B:c:CdDfhL:M:s:vV:X:",
207: longopts, NULL)) != -1) {
208: switch (c) {
209: case 'A':
210: host_file = optarg;
211: break;
212:
213: case 'b':
1.1.1.3 ! root 214: u = (uint32)strtoul(optarg, NULL, 16);
! 215: debug_breakaddr.push_back(u);
1.1.1.2 root 216: break;
217:
218: case 'B':
219: b = atoi(optarg);
220: if (b < 2 || b > 6) {
221: fprintf(stderr, "-B <benchmark_mode>: 2..6\n");
222: return false;
223: }
224: benchmark_mode = b;
225: break;
226:
227: case 'c':
228: vmdir = std::string(optarg);
229: break;
230:
231: case 'C':
232: log_to_console = true;
233: break;
234:
235: case 'd':
236: debug_on_start = true;
237: break;
238:
239: case 'D':
240: debug_on_console = true;
241: break;
242:
243: case 'f':
244: fast_mode = true;
245: break;
246:
247: case 'L':
248: if (logopt[0] != '\0') {
249: strlcat(logopt, ",", sizeof(logopt));
250: }
251: strlcat(logopt, optarg, sizeof(logopt));
252: break;
253:
254: case 'M':
255: if (!monitor_opt.empty()) {
256: monitor_opt += ",";
257: }
258: monitor_opt += optarg;
259: break;
260:
261: case 's':
262: if (IsGUI()) {
263: char *end;
264: errno = 0;
265: screen_scale = strtod(optarg, &end);
266: if (end == optarg || end[0] != '\0' || errno == ERANGE) {
267: warnx("-s: invalid argument");
268: return false;
269: }
270: // 上限は適当
271: if (screen_scale <= 0.0 || screen_scale >= 10.0) {
272: warnx("-s: invalid scale");
273: return false;
274: }
275: }
276: break;
277:
278: case 'X':
279: human68k_file = optarg;
280: for (int i = optind; i < ac; i++) {
281: if (i != optind) {
282: strlcat(human68k_arg, " ", sizeof(human68k_arg));
283: }
284: strlcat(human68k_arg, av[i], sizeof(human68k_arg));
285: }
286: optind = ac;
287: break;
288:
289: case 'v':
290: ShowVersion();
291: exit(0);
292:
293: case 'V':
294: config_options.push_back(optarg);
295: break;
296:
297: case OPT_fontsize:
298: fontsize = atoi(optarg);
299: break;
300:
301: case OPT_help:
302: ShowHelp(true);
303: return false;
304:
305: case OPT_show_config:
1.1.1.3 ! root 306: show_config = 1;
! 307: break;
! 308:
! 309: case OPT_show_config_all:
! 310: show_config = 2;
1.1.1.2 root 311: break;
312:
313: case 'h':
314: default:
315: ShowHelp(false);
316: return false;
317: }
318: }
319:
320: // vmdir に '/' を付けておく
321: if (vmdir.empty()) {
322: vmdir = ".";
323: }
324: if (vmdir.back() != '/') {
325: vmdir += '/';
326: }
327:
328: // CLI 版で -Mhelp つけても黙って起動するのはさすがにどうかと思う。
329: // とは言え GUI 版のコマンドラインを CLI 版用に書き換えた時に -M を
330: // 消して回らないと起動できないのも面倒なので -M オプション自体は
331: // 無意味だけど許容する。うーん。
332: if (IsCLI()) {
333: if (monitor_opt == "help") {
334: warnx("-Mhelp is not available on CLI");
335: return false;
336: }
337: if (!monitor_opt.empty()) {
338: warnx("-M option is ignored on CLI");
339: return true;
340: }
341: }
342:
343: return true;
344: }
345:
346: // バージョンを表示
347: void
348: MainApp::ShowVersion() const
349: {
350: // ここは実行ファイル名によらず nono にする
351: fprintf(stderr, "nono version %d.%d.%d (%s)\n",
352: NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER, NONO_DATE);
353: }
354:
355:
356: // 引数 logopt のログ指定文字列をパースする。
1.1 root 357: // arg は "foo=1,bar=2" 形式の文字列で、これを分解して
358: // それぞれ担当するオブジェクトのログレベルにセットする。
359: bool
1.1.1.2 root 360: MainApp::ParseLogopt()
1.1 root 361: {
362: char *buf;
363: char *last;
364: char *p;
365: bool rv;
366:
367: // "help" (完全一致) なら識別子一覧を表示。
1.1.1.2 root 368: if (strcmp(logopt, "help") == 0) {
1.1 root 369: for (auto& obj : gObjects) {
370: if (obj->logname != "?") {
371: printf("%s\n", obj->logname.c_str());
372: }
373: }
374: // エイリアスとか
375: printf("sch -> scheduler\n");
1.1.1.3 ! root 376: if (gMainApp.HasVMFeature(VMF_LUNA)) {
1.1 root 377: printf("scc -> sio\n");
378: }
379: printf("all\n");
380: return false;
381: }
382:
383: rv = false;
1.1.1.2 root 384: buf = strdup(logopt);
1.1 root 385: for (p = strtok_r(buf, ",", &last);
386: p;
387: p = strtok_r(NULL, ",", &last))
388: {
389: const char *name;
390: char *v;
391: int val;
392:
393: name = p;
394:
395: v = strchr(p, '=');
396: if (v) {
397: *v++ = '\0';
398: val = atoi(v);
399: } else {
400: val = 1;
401: }
402: // ここで name は変数名、val は値(省略されたら1)
403:
404: if (strlen(name) < 1) {
405: printf("invalid logname '%s'\n", name);
406: goto abort;
407: }
408:
409: // 短縮形とかエイリアスとか
410: if (strcmp(name, "sch") == 0)
411: name = "scheduler";
1.1.1.3 ! root 412: if (gMainApp.HasVMFeature(VMF_LUNA)) {
1.1 root 413: if (strcmp(name, "scc") == 0)
414: name = "sio";
415: }
416:
417: // 比較
418: if (strcmp(name, "all") == 0) {
419: // "all" なら none 以外の全部にセット
420: for (auto& obj : gObjects) {
421: obj->loglevel = val;
422: }
423: } else {
1.1.1.3 ! root 424: // それ以外は一致するキーを探してセット。
! 425: // 複数のオブジェクトが同じ logname を持ってもよい (CMMUとか)。
1.1 root 426: std::string sname = name;
427: bool found = false;
428: for (auto& obj : gObjects) {
429: if (obj->logname == sname) {
430: obj->loglevel = val;
431: found = true;
432: }
433: }
434: // 見つからない場合はエラー
435: if (!found) {
436: printf("invalid logname '%s'\n", name);
437: goto abort;
438: }
439: }
440: }
441:
442: rv = true;
443: abort:
444: free(buf);
445: return rv;
446: }
1.1.1.2 root 447:
448: // 関連するファイルのパスを取得。
1.1.1.3 ! root 449: // 1. name がファイル名のみなら、VM ディレクトリとその親ディレクトリを検索。
! 450: // ファイルが存在すればその時点で戻り値とする。ファイルが存在したけど
! 451: // 何らか不都合があったとしても (例えばファイルサイズが 0 だとか
! 452: // パーミッションが足りないとか) 次を試すとかはしない。
! 453: // 2. name が '~' から始まっていればホームディレクトリに展開。
! 454: // 3. name が相対パスなら VM ディレクトリからの相対パスとする。
! 455: // 4. name は絶対パスのはずなので、そのまま使用する。
1.1.1.2 root 456: std::string
457: MainApp::SearchFile(const std::string& name) const
458: {
459: std::string path;
460: struct stat st;
461:
1.1.1.3 ! root 462: // 1. パス区切りを含んでなければ、ファイル名のみ
! 463: if (name.find('/') == std::string::npos) {
! 464: // 1a. VM ディレクトリ
! 465: path = GetVMDir() + name;
! 466: if (stat(path.c_str(), &st) == 0) {
! 467: return path;
! 468: }
! 469:
! 470: // 2. 親ディレクトリ
! 471: path = GetVMDir() + "../" + name;
! 472: if (stat(path.c_str(), &st) == 0) {
! 473: return path;
! 474: }
! 475:
! 476: // どちらもなければエラー
! 477: return "";
! 478: }
1.1.1.2 root 479:
1.1.1.3 ! root 480: // 2. 先頭の '~' を $HOME に展開
! 481: path = name;
! 482: if (path[0] == '~' && path[1] == '/') {
! 483: const char *home = getenv("HOME");
! 484: if (home == NULL) {
! 485: home = "";
! 486: }
! 487: path = string_format("%s%s", home, path.c_str() + 1);
1.1.1.2 root 488: }
489:
1.1.1.3 ! root 490: // 3. 相対パスなら、VM ディレクトリからの相対
! 491: if (path[0] != '/') {
! 492: path = GetVMDir() + path;
1.1.1.2 root 493: }
494:
1.1.1.3 ! root 495: return path;
! 496: }
! 497:
! 498: // 現在の VM が指定の feature を持っているか?
! 499: bool
! 500: MainApp::HasVMFeature(vmf_t feature) const
! 501: {
! 502: uint32 vmf = 1 << GetVMType();
! 503: return (vmf & feature) != 0;
1.1.1.2 root 504: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.