--- nono/debugger/debugger.cpp 2026/04/29 17:05:45 1.1.1.19 +++ nono/debugger/debugger.cpp 2026/04/29 17:05:49 1.1.1.20 @@ -36,6 +36,7 @@ #include "debugger_hd64180.h" #include "debugger_m680x0.h" #include "debugger_m88xx0.h" +#include "event.h" #include "hostcom.h" #include "mainapp.h" #include "memdump.h" @@ -65,7 +66,7 @@ Debugger::Debugger() // ブレークポイントモニタ bpoint_monitor = gMonitorManager->Regist(ID_MONITOR_BREAKPOINT, this); bpoint_monitor->func = ToMonitorCallback(&Debugger::MonitorUpdateBpoint); - bpoint_monitor->SetSize(60, 9); + bpoint_monitor->SetSize(59, 9); // メモリダンプモニタ for (int i = 0, end = memdump_monitor.size(); i < end; i++) { @@ -186,56 +187,24 @@ Debugger::Init() } // -b [,][,] ならブレークポイント設定 - for (auto& str : gMainApp.debug_breakaddr) { - breakpoint_t bp; - std::string cpustr; - std::string addrstr; - std::string skipstr; - - // "," で分離 - auto arr = string_split(str, ','); - if (arr.size() < 2) { - // 1個ならアドレス (0 ってことはないはずだが) - addrstr = arr[0]; - } else if (arr.size() == 2) { - // 2個なら cpu か skip のどちらかが省略。 - // 1つ目が16進数っぽいかどうかで判定する。 - char *end; - strtoul(arr[0].c_str(), &end, 16); - if (end == &arr[0][0]) { - cpustr = arr[0]; - addrstr = arr[1]; - } else { - addrstr = arr[0]; - skipstr = arr[1]; - } - } else if (arr.size() == 3) { - cpustr = arr[0]; - addrstr = arr[1]; - skipstr = arr[2]; - } else { - warnx("\"%s\": Invalid breakpoint", str.c_str()); + for (const auto& str : gMainApp.debug_breakaddr) { + if (ParseOptBreakAddr(str) == false) { return false; } + } - // を取り出す - if (ParseVerbHex(addrstr.c_str(), &bp.addr)) { - bp.type = BreakpointType::Address; - } else if (addrstr[0] == 'v' - && ParseVector( - ParseCPU(cpustr), &(addrstr.c_str()[1]), (uint32 *)&bp.vec1)) { - bp.type = BreakpointType::Exception; - bp.vec2 = bp.vec1; - } else { - warnx("\"%s\": Invalid breakpoint address", str.c_str()); + // --bi [,][:][,] で命令ブレークポイント設定。 + for (auto& str : gMainApp.debug_breakinst) { + if (ParseOptBreakInst(str) == false) { return false; } - // を取り出す - if (skipstr.empty() == false) { - bp.skip = atoi(skipstr.c_str()); + } + + // --bv [,][-][,] でベクタブレークポイント設定。 + for (auto& str : gMainApp.debug_breakvec) { + if (ParseOptBreakVector(str) == false) { + return false; } - // 登録 - AddBreakpoint(bp, cpustr); } // --bi-exg なら命令ブレークポイントを設定 @@ -245,7 +214,8 @@ Debugger::Init() bp.type = BreakpointType::Instruction; bp.inst = 0xcf4f0000; bp.mask = 0xffff0000; - AddBreakpoint(bp, ""); + bp.md = curmd; + AddBreakpoint(bp); // 今登録されている命令ブレークの必要命令長を再計算 RecalcInstMask(); } @@ -346,7 +316,7 @@ Debugger::Close() void Debugger::Terminate() { - std::unique_lock lock(mtx); + std::lock_guard lock(mtx); request |= REQUEST_EXIT; cv_request.notify_one(); } @@ -424,7 +394,7 @@ void Debugger::RxCallback(uint32 arg) { // デバッガスレッドに通知 - std::unique_lock lock(mtx); + std::lock_guard lock(mtx); request |= REQUEST_RXCHAR; cv_request.notify_one(); } @@ -434,7 +404,7 @@ void Debugger::AcceptCallback(uint32 dummy) { // デバッガスレッドに通知 - std::unique_lock lock(mtx); + std::lock_guard lock(mtx); request |= REQUEST_ACCEPT; cv_request.notify_one(); } @@ -496,7 +466,7 @@ Debugger::Input(int c) case CmdAct::Quit: // アプリケーション自体を終了 LeavePrompt(false); - UIMessage::Post(UIMessage::APPEXIT); + gMainApp.GetUIMessage()->Post(UIMessage::APPEXIT); } } else if (c < ' ' || c == 0x07f) { // 他のコントロールコードはとりあえず無視 @@ -544,7 +514,7 @@ Debugger::LeavePrompt(bool trace) // 最後に VM スレッドで待機している Exec() を起こす { - std::unique_lock lock(mtx); + std::lock_guard lock(mtx); prompt_released = true; cv_prompt.notify_one(); } @@ -576,7 +546,7 @@ Debugger::Command() last_cmdbuf = cmdbuf; } - // 行を args に分解 + // 行を cmdargs に分解 ParseCmdbuf(); cmdbuf.clear(); @@ -584,7 +554,7 @@ Debugger::Command() // コマンドをテーブルから探す auto it = std::find_if(cmdtable.begin(), cmdtable.end(), - [=](auto x) { return strcmp(args[0].c_str(), x.name) == 0; }); + [=](auto x) { return strcmp(cmdargs[0].c_str(), x.name) == 0; }); if (it != cmdtable.end()) { auto cmd = *it; @@ -593,16 +563,16 @@ Debugger::Command() } // (コマンドテーブルになくて) "r" から始まっていればレジスタ表示系 - if (args[0][0] == 'r') { + if (cmdargs[0][0] == 'r') { // ShowRegister() は処理したら true を返す。 // "r" 系コマンドはすべて Stay。 - if (curmd->ShowRegister(cons, args)) { + if (curmd->ShowRegister(cons, cmdargs)) { return CmdAct::Stay; } } // 知らないコマンドも Stay 相当。 - fprintf(cons, "%s: unknown command\n", args[0].c_str()); + fprintf(cons, "%s: Unknown command.\n", cmdargs[0].c_str()); // この行は次回空エンターで再発行しないでいい。 last_cmdbuf.clear(); return CmdAct::Stay; @@ -896,7 +866,7 @@ Debugger::CheckAllBreakpoints(DebuggerMD // この時点ではまだコンソールを取得していない可能性があるので // (-D なしで起動した場合とか)、表示せず用意するだけ。 // コンソールが取得できたところで表示する。 - bpointmsg += string_format("breakpoint #%d (%s) reached\n", + bpointmsg += string_format("Breakpoint #%d (%s) reached.\n", i, desc.c_str()); } @@ -1002,14 +972,14 @@ Debugger::NotifyExceptionXP(int vector) Debugger::CmdAct Debugger::cmd_h() { - if (args.size() < 2) { + if (cmdargs.size() < 2) { // 引数なしなら一覧表示。 ShowHelpList(HelpListMain); return CmdAct::Stay; } // 引数があれば個別の詳細 - const std::string cmd1 = args[1]; + const std::string cmd1 = cmdargs[1]; // curmd の MD 分も併せて検索するため、ローカルに map を作成する。 // value のほうは実体コピーは不要なのでポインタ。 @@ -1024,7 +994,7 @@ Debugger::cmd_h() // 検索 const auto it1 = helpmap.find(cmd1); if (it1 == helpmap.end()) { - fprintf(cons, "invalid command name: %s\n", cmd1.c_str()); + fprintf(cons, "Invalid command name: %s\n", cmd1.c_str()); return CmdAct::Stay; } const auto& msg1 = *(it1->second); @@ -1041,7 +1011,7 @@ Debugger::cmd_h() // 再検索 (こっちは見付からないはずはない) const auto it2 = helpmap.find(cmd2); if (it2 == helpmap.end()) { - fprintf(cons, "Warning: '%s' in '%s' not found\n", + fprintf(cons, "Warning: '%s' in '%s' not found.\n", msg1.c_str(), cmd1.c_str()); return CmdAct::Stay; } @@ -1212,11 +1182,12 @@ Debugger::HelpDetails = { { "bv", R"**( Command: bv [,][-] [] - Sets an exception breakpoint on . must be specified in hex. - ranges from 0 to ff (255 in decimal) on m68k, and 0 to 1ff (511 - in decimal) on m88k. If is specified, it matches the range - from to (including themselves). - For example, "bv 1-1ff" on m88k means all exceptions but reset. + Sets an exception breakpoint on . accepts hexadecimal + number if prefixed with "$" or "0x", or decimal number if no prefix. + ranges from 0 to 255 on m68k, and 0 to 511 on m88k. + If is specified, it matches the range from to + (including themselves). For example, "bv 1-$1ff" on m88k means all + exceptions but reset. If is omitted, the current cpu is used. XXX skipcount @@ -1423,7 +1394,7 @@ Debugger::HelpDetails = { )**" }, }; -// cmdbuf を args... に分解する。 +// cmdbuf を cmdargs... に分解する。 void Debugger::ParseCmdbuf() { @@ -1431,7 +1402,7 @@ Debugger::ParseCmdbuf() int start; pos = 0; - args.clear(); + cmdargs.clear(); // 引数を空白で分解 for (;;) { @@ -1449,12 +1420,12 @@ Debugger::ParseCmdbuf() if (isspace((unsigned int)cmdbuf[pos])) break; } - args.push_back(cmdbuf.substr(start, pos - start)); + cmdargs.push_back(cmdbuf.substr(start, pos - start)); } // デバッグ表示 if (0) { - for (int i = 0 ; i < args.size(); i++) { - printf("args[%d]=|%s|\n", i, args[i].c_str()); + for (int i = 0 ; i < cmdargs.size(); i++) { + printf("cmdargs[%d]=|%s|\n", i, cmdargs[i].c_str()); } } } @@ -1471,12 +1442,12 @@ Debugger::CmdAct Debugger::cmd_b() { // 引数なしなら一覧表示 - if (args.size() < 2) { + if (cmdargs.size() < 2) { return cmd_b_list(); } // 引数取得 - if (args[1][0] == '#') { + if (cmdargs[1][0] == '#') { // # 形式なら、指定番号のブレークポイントを削除 cmd_b_delete(); return CmdAct::Stay; @@ -1492,13 +1463,14 @@ Debugger::cmd_bm() { // XXX m88k のみ未サポート if (curmd->arch != CPUArch::M88xx0) { - fprintf(cons, "bm not supported yet on m68k\n"); + fprintf(cons, "bm: Not supported yet on m68k.\n"); return CmdAct::Stay; } return cmd_b_set(BreakpointType::Memory); } // type が違うだけの各種ブレークポイント設定の共通部分。 +// すぐ下の ParseOptBreakAddr() と限りなく似ている。 Debugger::CmdAct Debugger::cmd_b_set(BreakpointType type) { @@ -1506,63 +1478,132 @@ Debugger::cmd_b_set(BreakpointType type) std::string cpustr; std::string addrstr; - if (args.size() < 2) { - fprintf(cons, "usage: %s [,] []\n", - args[0].c_str()); + if (cmdargs.size() < 2) { + fprintf(cons, "Usage: %s [,] []\n", + cmdargs[0].c_str()); return CmdAct::Stay; } // まず CPU を分離 - auto pos = args[1].find(','); + auto pos = cmdargs[1].find(','); if (pos == std::string::npos) { // CPU 指定なし - addrstr = args[1]; + addrstr = cmdargs[1]; } else { // CPU 指定あり - cpustr = args[1].substr(0, pos); - addrstr = args[1].substr(pos + 1); + cpustr = cmdargs[1].substr(0, pos); + addrstr = cmdargs[1].substr(pos + 1); } + // CPU + bp.md = ParseCPU(cpustr); + if (bp.md == NULL) { + fprintf(cons, "Invalid CPU Name.\n"); + return CmdAct::Stay; + } // アドレス if (!ParseAddr(addrstr.c_str(), &bp.addr)) { return CmdAct::Stay; } // あればスキップカウント - if (args.size() > 2) { - bp.skip = atoi(args[2].c_str()); + if (cmdargs.size() > 2) { + bp.skip = atoi(cmdargs[2].c_str()); } // 空いてるところにセット // (よく似たエントリがあっても干渉しない) bp.type = type; - int bi = AddBreakpoint(bp, cpustr); + int bi = AddBreakpoint(bp); if (bi == -1) { - fprintf(cons, "no free breakpoints\n"); + fprintf(cons, "Breakpoints full.\n"); } else { - fprintf(cons, "breakpoint #%d added\n", bi); + fprintf(cons, "Breakpoint #%d added.\n", bi); } return CmdAct::Stay; } +// コマンドラインオプション -b(--b) の引数 str からのブレークポイント(アドレス) +// を登録する。 +// 成功すれば true を返す。失敗すればエラーメッセージを表示して false を返す。 +// すぐ上の cmd_b_set() と限りなく似ている。 +bool +Debugger::ParseOptBreakAddr(const std::string& str) +{ + breakpoint_t bp; + std::string cpustr; + std::string addrstr; + std::string skipstr; + + // "," で分離。 + auto args = string_split(str, ','); + if (args.size() == 1) { + // 1個ならアドレスのみ。 + addrstr = args[0]; + } else if (args.size() == 2) { + // 2個なら cpu か skip のどちらかが省略されている…。 + // 先頭が CPU 文字列かどうかで判定する。 + if (IsCPUStr(args[0])) { + cpustr = args[0]; + addrstr = args[1]; + } else { + addrstr = args[0]; + skipstr = args[1]; + } + } else if (args.size() == 3) { + cpustr = args[0]; + addrstr = args[1]; + skipstr = args[2]; + } else { + warnx("--b \"%s\": Syntax error.", str.c_str()); + return false; + } + + // を取り出す。 + bp.md = ParseCPU(cpustr); + if (bp.md == NULL) { + warnx("--b \"%s\": Invalid CPU Name.", str.c_str()); + return false; + } + + // を取り出す。 + if (ParseVerbHex(addrstr.c_str(), &bp.addr) == false) { + warnx("--b \"%s\": Invalid address.", str.c_str()); + return false; + } + + // を取り出す。 + if (skipstr.empty() == false) { + bp.skip = atoi(skipstr.c_str()); + } + + // 登録。 + bp.type = BreakpointType::Address; + if (AddBreakpoint(bp) < 0) { + warnx("--b \"%s\": Breakpoints full.", str.c_str()); + return false; + } + return true; +} + // ブレークポイント個別削除 // (引数の先頭が '#' なことを判定したところで呼ばれる) // b # Debugger::CmdAct Debugger::cmd_b_delete() { - int n = atoi(&args[1][1]); + int n = atoi(&cmdargs[1][1]); if (n < 0 || n >= bpoint.size()) { - fprintf(cons, "invalid breakpoint number: #%d\n", n); + fprintf(cons, "Invalid breakpoint number: %d\n", n); return CmdAct::Stay; } auto& bp = bpoint[n]; if (bp.type == BreakpointType::Unused) { - fprintf(cons, "invalid breakpoint number: #%d\n", n); + fprintf(cons, "Breakpoint #%d already removed.\n", n); return CmdAct::Stay; } - fprintf(cons, "breakpoint #%d removed\n", n); + fprintf(cons, "Breakpoint #%d removed.\n", n); bp.type = BreakpointType::Unused; // 今登録されている命令ブレークの必要命令長を再計算 RecalcInstMask(); @@ -1574,153 +1615,344 @@ Debugger::cmd_b_delete() Debugger::CmdAct Debugger::cmd_bi() { - breakpoint_t bp; std::string cpustr; - std::string argstr; std::string inststr; - std::string maskstr; - int instlen; - int masklen; + std::string skipstr; + std::string errstr; - if (args.size() < 2) { - fprintf(cons, "usage: bi [:] []\n"); + if (cmdargs.size() < 2) { + fprintf(cons, "Usage: bi [:] []\n"); return CmdAct::Stay; } // CPU をまず分離 - auto pos = args[1].find(','); + auto pos = cmdargs[1].find(','); if (pos == std::string::npos) { // CPU 指定なし - argstr = args[1]; + inststr = cmdargs[1]; } else { // CPU 指定あり - cpustr = args[1].substr(0, pos); - argstr = args[1].substr(pos + 1); + cpustr = cmdargs[1].substr(0, pos); + inststr = cmdargs[1].substr(pos + 1); } - pos = argstr.find(':'); - if (pos == std::string::npos) { - // マスク指定なし - inststr = argstr; - instlen = inststr.size(); + // あればスキップカウント + if (cmdargs.size() > 2) { + skipstr = cmdargs[2]; + } + + // 命令ブレークポイントを登録。 + int bi = AddBreakInst(cpustr, inststr, skipstr, &errstr); + if (errstr.empty() == false) { + fprintf(cons, "%s\n", errstr.c_str()); + return CmdAct::Stay; + } + fprintf(cons, "Breakpoint #%d added.\n", bi); + return CmdAct::Stay; +} + +// --bi オプションの引数 str から命令ブレークポイントを登録する。 +// 成功すれば true を返す。失敗すればエラーメッセージを表示して false を返す。 +bool +Debugger::ParseOptBreakInst(const std::string& str) +{ + std::string cpustr; + std::string inststr; + std::string skipstr; + std::string errstr; + + // "," で分離。 + auto args = string_split(str, ','); + if (args.size() == 1) { + // 1個なら命令のみ。 + inststr = args[0]; + } else if (args.size() == 2) { + // 2個なら cpu か skip のどちらかが省略されている…。 + // 先頭が CPU 文字列かどうかで判定する。 + if (IsCPUStr(args[0])) { + cpustr = args[0]; + inststr = args[1]; + } else { + inststr = args[0]; + skipstr = args[1]; + } + } else if (args.size() == 3) { + cpustr = args[0]; + inststr = args[1]; + skipstr = args[2]; + } else { + warnx("--bi \"%s\": Syntax error.", str.c_str()); + return false; + } + + // 命令ブレークポイントを登録。 + AddBreakInst(cpustr, inststr, skipstr, &errstr); + if (errstr.empty() == false) { + warnx("--bi \"%s\": %s", str.c_str(), errstr.c_str()); + return false; + } + return true; +} + +// 命令ブレークポイントを登録する。cmd_bi と --bi の後半の共通部分。 +// 成功すれば *errstr は empty でブレークポイント番号(>=0)を返す。 +// 失敗すれば *errstr をセットして -1 を返す。 +int +Debugger::AddBreakInst(const std::string& cpustr, const std::string& argstr, + const std::string& skipstr, std::string *errstr) +{ + DebuggerMD *md; + breakpoint_t bp; + std::string inststr; + std::string maskstr; + int instlen; + int masklen; + + // CPU 部チェック + md = ParseCPU(cpustr); + if (md == NULL) { + *errstr = std::string("Invalid CPU Name."); + return CmdAct::Stay; + } + bp.md = md; + + // 命令部。 + // ニーモニックが指定できたら便利。 + inststr = md->ParseInst(argstr); + if (inststr.empty() == false) { masklen = -1; } else { - // マスク指定あり - inststr = argstr.substr(0, pos); - instlen = inststr.size(); - maskstr = argstr.substr(pos + 1); - masklen = maskstr.size(); + auto pos = argstr.find(':'); + if (pos == std::string::npos) { + // マスク指定なし。 + inststr = argstr; + masklen = -1; + } else { + // マスク指定あり。 + inststr = argstr.substr(0, pos); + maskstr = argstr.substr(pos + 1); + masklen = maskstr.size(); + } } + instlen = inststr.size(); - // 命令部チェック if (ParseVerbHex(inststr.c_str(), &bp.inst) == false) { - fprintf(cons, "%s: invalid instruction value\n", argstr.c_str()); - return CmdAct::Stay; + *errstr = string_format("Invalid instruction: %s", argstr.c_str()); + return -1; } - if (instlen % (curmd->inst_bytes * 2) != 0) { - fprintf(cons, "%s: invalid instruction length\n", argstr.c_str()); - return CmdAct::Stay; + if (instlen % (md->inst_bytes * 2) != 0) { + *errstr = string_format("Invalid instruction length: %s", + argstr.c_str()); + return -1; } // マスク部チェック bp.mask = 0xffffffff; if (masklen != -1) { if (ParseVerbHex(maskstr.c_str(), &bp.mask) == false) { - fprintf(cons, "%s: invalid mask value\n", argstr.c_str()); - return CmdAct::Stay; + *errstr = string_format("Invalid mask value: %s", argstr.c_str()); + return -1; } if (masklen != instlen) { - fprintf(cons, "%s: inst:mask must be the same length\n", - argstr.c_str()); - return CmdAct::Stay; + *errstr = std::string(": must be the same length"); + return -1; } } // 8バイト未満なら左詰め。 - if (curmd->inst_bytes < 4 && instlen < 8) { + if (md->inst_bytes < 4 && instlen < 8) { bp.inst <<= 32 - instlen * 4; bp.mask <<= 32 - instlen * 4; } - // あればスキップカウント - bp.skip = 0; - if (args.size() > 2) { - bp.skip = atoi(args[2].c_str()); + // を取り出す。 + if (skipstr.empty() == false) { + bp.skip = atoi(skipstr.c_str()); } // 空いてるところにセット bp.type = BreakpointType::Instruction; - int bi = AddBreakpoint(bp, cpustr); - if (bi == -1) { - fprintf(cons, "no free breakpoints\n"); - } else { - fprintf(cons, "breakpoint #%d added\n", bi); + int bi = AddBreakpoint(bp); + if (bi < 0) { + *errstr = std::string("Breakpoints full."); + return bi; } - // 今登録されている命令ブレークの必要命令長を再計算 + // 今登録されている命令ブレークの必要命令長を再計算。 RecalcInstMask(); - return CmdAct::Stay; + + return bi; } -// 例外ブレークポイントの設定 +// 例外ブレークポイントの設定。 // bv [,][-] [] Debugger::CmdAct Debugger::cmd_bv() { - breakpoint_t bp; - std::string argstr; std::string cpustr; - DebuggerMD *md; + std::string vecstr; + std::string skipstr; + std::string errstr; - if (args.size() < 2) { - fprintf(cons, "usage: bv [,][-] []\n"); + if (cmdargs.size() < 2) { + fprintf(cons, "Usage: bv [,][-] []\n"); return CmdAct::Stay; } // CPU をまず分離 - auto pos = args[1].find(','); + auto pos = cmdargs[1].find(','); if (pos == std::string::npos) { // CPU 指定なし - argstr = args[1]; + vecstr = cmdargs[1]; } else { // CPU 指定あり - cpustr = args[1].substr(0, pos); - argstr = args[1].substr(pos + 1); + cpustr = cmdargs[1].substr(0, pos); + vecstr = cmdargs[1].substr(pos + 1); + } + + if (cmdargs.size() > 2) { + skipstr = cmdargs[2]; } - // ベクタ名解釈のために必要 + + // ベクタブレークポイントを登録。 + int bi = AddBreakVector(cpustr, vecstr, skipstr, &errstr); + if (errstr.empty() == false) { + fprintf(cons, "%s\n", errstr.c_str()); + return CmdAct::Stay; + } + fprintf(cons, "Breakpoint #%d added.\n", bi); + + // この CPU 側に、すでに来ている例外をクリア。 + // ブレークポイント設定の有無に関わらず例外が起きたら CPU 側から常に + // 通知されている。これをクリアするのは CheckAllBreakpoints() で、これは + // 命令間(命令前)に呼ばれるやつ、なのでこうなる。 + // 1. 例外が起きると md->bv_vector がセットされる + // 2. 例外ブレークポイントを設定していないとこれがクリアされない + // 3. bv コマンドで例外ブレークを新たに設定すると、次の命令境界で + // 1.のベクタが反応してしまう。 + // 命令ごととかにクリアしてもいいかも知れないが、ここでブレークポイントを + // 設定したのだから、それ以前の事象には反応すべきでない、という意味では + // ここでもいいか? + + // bp.md は AddBreakpoint() で bpoint 登録時にセットされるので + // インデックスから bp をもう一度読み込む。 + auto bp = bpoint[bi]; + bp.md->bv_vector = -1; + + return CmdAct::Stay; +} + +// --bv オプションの引数 str からベクタブレークポイントを登録する。 +// 成功すれば true を返す。失敗すればエラーメッセージを表示して false を返す。 +bool +Debugger::ParseOptBreakVector(const std::string& str) +{ + std::string cpustr; + std::string vecstr; + std::string skipstr; + std::string errstr; + + // "," で分離。 + auto args = string_split(str, ','); + if (args.size() == 1) { + // 1個ならベクタのみ。 + vecstr = args[0]; + } else if (args.size() == 2) { + // 2個なら cpu か skip のどちらかが省略されている…。 + // 先頭が CPU 文字列かどうかで判定する。 + if (IsCPUStr(args[0])) { + cpustr = args[0]; + vecstr = args[1]; + } else { + vecstr = args[0]; + skipstr = args[1]; + } + } else if (args.size() == 3) { + cpustr = args[0]; + vecstr = args[1]; + skipstr = args[2]; + } else { + warnx("--bv \"%s\": Syntax error.", str.c_str()); + return false; + } + + // ベクタブレークポイントを登録。 + AddBreakVector(cpustr, vecstr, skipstr, &errstr); + if (errstr.empty() == false) { + warnx("--bv \"%s\": %s", str.c_str(), errstr.c_str()); + return false; + } + return true; +} + +// ベクタブレークポイントを登録する。cmd_bv と --bv の後半の共通部分。 +// 成功すれば *errstr は empty でブレークポイント番号(>=0)を返す。 +// 失敗すれば *errstr をセットして -1 を返す。 +int +Debugger::AddBreakVector(const std::string& cpustr, const std::string& vecstr, + const std::string& skipstr, std::string *errstr) +{ + uint32 maxvector; + breakpoint_t bp; + DebuggerMD *md; + + // CPU 部。ベクタ解釈にも使う。 md = ParseCPU(cpustr); + if (md == NULL) { + *errstr = std::string("Invalid CPU Name."); + return CmdAct::Stay; + } + bp.md = md; - pos = argstr.find('-'); + // ベクタ数 (最大値) を取得。 + if (md->arch == CPUArch::HD64180) { + // XXX あってる? + maxvector = 256; + } else { + maxvector = pVectorTable->Size(); + } + + // ベクタを取り出す。 + auto pos = vecstr.find('-'); if (pos == std::string::npos) { // ベクタ番号が1つなら vec1, vec2 を同値にしておく。 - if (ParseVector(md, argstr.c_str(), (uint32 *)&bp.vec1) == false) { - fprintf(cons, "%s: invalid vector number\n", argstr.c_str()); - return CmdAct::Stay; + if (ParseVector(md, vecstr.c_str(), (uint32 *)&bp.vec1) == false) { + *errstr = string_format("Invalid vector number: %s", + vecstr.c_str()); + return -1; } bp.vec2 = bp.vec1; } else { // ベクタ番号(範囲指定 [vec1, vec2]) - std::string str1 = argstr.substr(0, pos); - std::string str2 = argstr.substr(pos + 1); + std::string vec1 = vecstr.substr(0, pos); + std::string vec2 = vecstr.substr(pos + 1); - if (ParseVector(md, str1.c_str(), (uint32 *)&bp.vec1) == false) { - fprintf(cons, "%s: invalid first vector number\n", argstr.c_str()); - return CmdAct::Stay; + // "-vec2" なら vec1 = 0、 + // "vec1-" なら vec2 は最大値 (機種による)、とする。 + + if (vec1.empty()) { + bp.vec1 = 0; + } else if (ParseVector(md, vec1.c_str(), (uint32 *)&bp.vec1) == false) { + *errstr = string_format("Invalid first vector number: %s", + vecstr.c_str()); + return -1; } - if (ParseVector(md, str2.c_str(), (uint32 *)&bp.vec2) == false) { - fprintf(cons, "%s: invalid last vector number\n", argstr.c_str()); - return CmdAct::Stay; + if (vec2.empty()) { + bp.vec2 = maxvector - 1; + } else if (ParseVector(md, vec2.c_str(), (uint32 *)&bp.vec2) == false) { + *errstr = string_format("Invalid last vector number: %s", + vecstr.c_str()); + return -1; } } // 範囲チェック - auto vectortable = pVectorTable.get(); - if (bp.vec1 < 0 || bp.vec1 >= vectortable->Size()) { - fprintf(cons, "$%x: invalid vector number\n", bp.vec1); - return CmdAct::Stay; - } - if (bp.vec2 < 0 || bp.vec2 >= vectortable->Size()) { - fprintf(cons, "$%x: invalid last vector number\n", bp.vec2); - return CmdAct::Stay; + if (bp.vec1 < 0 || bp.vec1 >= maxvector) { + *errstr = string_format("Out of range: $%x", bp.vec1); + return -1; + } + if (bp.vec2 < 0 || bp.vec2 >= maxvector) { + *errstr = string_format("Out of range: $%x", bp.vec2); + return -1; } // 大小が逆なら入れ替える? @@ -1731,51 +1963,55 @@ Debugger::cmd_bv() bp.vec2 = tmp; } - // あればスキップカウント - bp.skip = 0; - if (args.size() > 2) { - bp.skip = atoi(args[2].c_str()); + // を取り出す。 + if (skipstr.empty() == false) { + bp.skip = atoi(skipstr.c_str()); } // 空いてるところにセット bp.type = BreakpointType::Exception; - int bi = AddBreakpoint(bp, cpustr); - if (bi == -1) { - fprintf(cons, "no free breakpoints\n"); - return CmdAct::Stay; + int bi = AddBreakpoint(bp); + if (bi < 0) { + *errstr = std::string("Breakpoints full."); } - fprintf(cons, "breakpoint #%d added\n", bi); - - // この CPU 側に、すでに来ている例外をクリア。 - // ブレークポイント設定の有無に関わらず例外が起きたら CPU 側から常に - // 通知されている。これをクリアするのは CheckAllBreakpoints() で、これは - // 命令間(命令前)に呼ばれるやつ、なのでこうなる。 - // 1. 例外が起きると md->bv_vector がセットされる - // 2. 例外ブレークポイントを設定していないとこれがクリアされない - // 3. bv コマンドで例外ブレークを新たに設定すると、次の命令境界で - // 1.のベクタが反応してしまう。 - // 命令ごととかにクリアしてもいいかも知れないが、ここでブレークポイントを - // 設定したのだから、それ以前の事象には反応すべきでない、という意味では - // ここでもいいか? - - // bp.md は AddBreakpoint() で bpoint 登録時にセットされるので - // インデックスから bp をもう一度読み込む。 - bp = bpoint[bi]; - bp.md->bv_vector = -1; - - return CmdAct::Stay; + return bi; } // ベクタ名かベクタ番号をパースする。 +// ベクタ番号は10進数で表記されることが多いので、10進/16進どちらも対応する。 +// - "$" か "0x" から始まれば16進数。 +// - それ以外で [0-9] から始まれば10進数。"0" プレフィックスを8進数にはしない。 +// - それ以外ならベクタ名とみなす。 bool Debugger::ParseVector(DebuggerMD *md, const char *arg, uint32 *valp) { - // 数値変換出来るか。 - if (ParseVerbHex(arg, valp)) { + assert(arg); + + if (arg[0] == '$') { + // 1文字目以降が16進数。 + return ParseVerbHex(arg + 1, valp); + } + if (strncmp(arg, "0x", 2) == 0) { + // 2文字目以降が16進数。 + return ParseVerbHex(arg + 2, valp); + } + if ('0' <= arg[0] && arg[0] <= '9') { + // 先頭から10進数。 + uint32 val; + char *end; + errno = 0; + val = strtoul(arg, &end, 10); + if (*end != '\0') { + return false; + } + if (errno == ERANGE) { + return false; + } + *valp = val; return true; } - // 出来なければ、機種ごとにベクタ名と比較する。 + // 数値に出来なければ、機種ごとにベクタ名と比較する。 if (md->arch == CPUArch::HD64180) { static std::vector table = { "trap", @@ -1819,15 +2055,15 @@ Debugger::MonitorUpdateBpoint(Monitor *, { // 0 1 2 3 4 5 6 // 0123456789012345678901234567890123456789012345678901234567890 - // No CPU Type Parameter Matched Skip - // #0 xp addr $01234567 123456789 123456789/123456789 - // #1 main inst 00000000/00000000 - // #2 main excp $00-$00 + // No CPU Type Parameter Matched Skip + // #0 xp addr $01234567 123456789 123456789/123456789 + // #1 mpu inst 00000000:00000000 + // #2 mpu excp $00-$00 monitor.Clear(); - monitor.Print(0, 0, "No CPU Type Parameter"); - monitor.Print(31, 0, "Matched"); - monitor.Print(42, 0, "Skip"); + monitor.Print(0, 0, "No CPU Type Parameter"); + monitor.Print(30, 0, "Matched"); + monitor.Print(41, 0, "Skip"); for (int i = 0; i < bpoint.size(); i++) { const auto& bp = bpoint[i]; @@ -1835,64 +2071,69 @@ Debugger::MonitorUpdateBpoint(Monitor *, monitor.Print(0, y, "#%d", i); - // 種別ごとの表示 - switch (bp.type) { - case BreakpointType::Unused: + if (bp.type == BreakpointType::Unused) { continue; + } + monitor.Print(3, y, "%s", bp.md ? bp.md->GetName().c_str() : "?"); + // 種別ごとの表示 + switch (bp.type) { case BreakpointType::Address: - monitor.Print(3, y, "%-4s addr $%08x", - bp.md->GetName().c_str(), bp.addr); + monitor.Print(7, y, "addr $%08x", bp.addr); break; + case BreakpointType::Memory: - monitor.Print(3, y, "%-4s mem $%08x", - bp.md->GetName().c_str(), bp.addr); + monitor.Print(7, y, "mem $%08x", bp.addr); break; case BreakpointType::Exception: - monitor.Print(3, y, "%-4s excp $%02x", - bp.md->GetName().c_str(), bp.vec1); + { + uint vd; // ベクタの桁数 + if (bp.md->arch == CPUArch::M88xx0) { + vd = 3; + } else { + vd = 2; + } + monitor.Print(7, y, "excp $%0*x", vd, bp.vec1); if (bp.vec2 != bp.vec1) { - monitor.Print(16, y, "-$%02x", bp.vec2); + monitor.Print(13 + vd, y, "-$%0*x", vd, bp.vec2); } break; + } case BreakpointType::Instruction: - monitor.Print(3, y, "%-4s inst", bp.md->GetName().c_str()); - if (bp.md->inst_bytes == 4) { - if (bp.mask == 0xffffffff) { - monitor.Print(13, y, "%08x", bp.inst); - } else { - monitor.Print(13, y, "%08x:%08x", bp.inst, bp.mask); - } - } else { - if (bp.mask == 0xffffffff) { - monitor.Print(13, y, "%08x", bp.inst); - } else if (((bp.inst | bp.mask) & 0x0000ffff) != 0) { - monitor.Print(13, y, "%08x:%08x", bp.inst, bp.mask); - } else if (bp.mask == 0xffff0000) { - monitor.Print(13, y, "%04x", bp.inst >> 16); - } else { - monitor.Print(13, y, "%04x:%04x", - bp.inst >> 16, bp.mask >> 16); + monitor.Puts(7, y, "inst"); + for (int n = bp.md->inst_bytes; n <= 4; n += bp.md->inst_bytes) { + // 左詰め n バイト分のマスク。 + int bits = (4 - n) * 8; + uint imask = 0xffffffffU << bits; + // n が 4 だと ~imask の式は使えないが無条件で入る。 + if (n == 4 || ((bp.inst | bp.mask) & ~imask) == 0) { + if (bp.mask == imask) { + monitor.Print(12, y, "%0*x", n * 2, (bp.inst >> bits)); + } else { + monitor.Print(12, y, "%0*x:%0*x", + n * 2, (bp.inst >> bits), + n * 2, (bp.mask >> bits)); + } + break; } } break; default: - monitor.Print(3, y, "%-4s type=%d", - (bp.md ? bp.md->GetName().c_str() : "?"), (int)bp.type); + monitor.Print(7, y, "type=%d", (int)bp.type); continue; } // マッチ回数 - monitor.Print(31, y, "%d", bp.matched); + monitor.Print(30, y, "%d", bp.matched); // スキップ if (bp.skip < 0) { - monitor.Print(42, y, "forever"); + monitor.Print(41, y, "forever"); } else if (bp.skip > 0) { - monitor.Print(42, y, "%d / %d", (bp.skip - bp.skipremain), bp.skip); + monitor.Print(41, y, "%d / %d", (bp.skip - bp.skipremain), bp.skip); } } } @@ -1904,7 +2145,7 @@ Debugger::cmd_bx() for (auto& bp : bpoint) { bp.type = BreakpointType::Unused; } - fprintf(cons, " All breakpoints disabled\n"); + fprintf(cons, "All breakpoints are removed.\n"); // 今登録されている命令ブレークの必要命令長を再計算 RecalcInstMask(); @@ -1912,6 +2153,19 @@ Debugger::cmd_bx() return CmdAct::Stay; } +// CPU 文字列なら true を返す。 +bool +Debugger::IsCPUStr(const std::string& cpustr) const +{ + if (cpustr.empty()) { + return false; + } + if (ParseCPU(cpustr) == NULL) { + return false; + } + return true; +} + // CPU 文字列から対応する MD を返す。 // 一致しなければ NULL を返す。 DebuggerMD * @@ -1921,26 +2175,27 @@ Debugger::ParseCPU(const std::string& cp return curmd; } else if (cpustr == "xp") { return md_xp.get(); - } else if (cpustr == "main") { + } else if (cpustr == "mpu") { return md_mpu.get(); } return NULL; } // ブレークポイントを設定。 -// new_bp のうち cpu, matched, skipremain はこちらで初期化する。 -// それ以外を埋めてから呼ぶこと。 +// new_bp のうち matched, skipremain はこちらで初期化する。 +// それ以外の type、md と type ごとに必要なパラメータを埋めてから呼ぶこと。 // 設定できればその番号、できなければ -1 を返す。 int -Debugger::AddBreakpoint(const breakpoint_t& new_bp, const std::string& cpustr) +Debugger::AddBreakpoint(const breakpoint_t& new_bp) { - DebuggerMD *md = ParseCPU(cpustr); - for (int i = 0; i < bpoint.size(); i++) { auto& bp = bpoint[i]; if (bp.type == BreakpointType::Unused) { bp = new_bp; - bp.md = md; + // 本当は assert レベルだが、とは言えここで死なれるとつらいので。 + if (bp.md == NULL) { + bp.md = curmd; + } bp.matched = 0; if (bp.skip > 0) { bp.skipremain = bp.skip; @@ -2015,8 +2270,8 @@ Debugger::cmd_hist_common(BranchHistory& // 向きは bottom_to_top = true が新しいほうを下とする方向。 int maxlines = 20; bool bottom_to_top = true; - if (args.size() > 1) { - maxlines = atoi(args[1].c_str()); + if (cmdargs.size() > 1) { + maxlines = atoi(cmdargs[1].c_str()); if (maxlines < 0) { bottom_to_top = false; maxlines = -maxlines; @@ -2057,10 +2312,10 @@ Debugger::cmd_hist_common(BranchHistory& Debugger::CmdAct Debugger::cmd_c() { - if (args.size() > 1) { + if (cmdargs.size() > 1) { // 引数があれば uint32 addr; - if (!ParseAddr(args[1].c_str(), &addr)) { + if (!ParseAddr(cmdargs[1].c_str(), &addr)) { return CmdAct::Stay; } step_type = StepType::Addr; @@ -2076,14 +2331,14 @@ Debugger::cmd_c() Debugger::CmdAct Debugger::cmd_ct() { - if (args.size() > 1) { + if (cmdargs.size() > 1) { // 引数があれば - if (!ParseTime(args[1].c_str(), &ct_timespan)) { + if (!ParseTime(cmdargs[1].c_str(), &ct_timespan)) { return CmdAct::Stay; } } if (ct_timespan == 0) { - fprintf(cons, "time must be specified\n"); + fprintf(cons, "Time must be specified.\n"); return CmdAct::Stay; } step_type = StepType::Time; @@ -2097,14 +2352,14 @@ Debugger::cmd_ct() // // 引数がなければ sa->addr には書き込まずに true で帰る (ので、呼び出し側が // 事前に適切なデフォルト値をセットしておくと、それがそのまま使われる)。 -// 引数が1つ(以上)あれば args[1] をアドレスとしてパースする。 +// 引数が1つ(以上)あれば cmdargs[1] をアドレスとしてパースする。 // アドレスには空間修飾子を前置可能、省略の場合はいずれも現在値を使う。 // エラーならメッセージを表示して false を返す。 // sa は in/out パラメータ。 bool Debugger::GetAddr(busaddr *sa, bool default_data) { - if (args.size() < 2) { + if (cmdargs.size() < 2) { // 引数なしなら、呼び出し側が sa にセットした前回値をそのまま使う。 return true; } @@ -2112,7 +2367,7 @@ Debugger::GetAddr(busaddr *sa, bool defa // 引数ありならパースする。アドレスが明示的に指定されたので、 // ここから先の省略箇所は前回値ではなくデフォルト値で補完する。 - std::string& str = args[1]; + std::string& str = cmdargs[1]; uint32 addr; // ':' があればその前はアドレス空間修飾子 @@ -2157,7 +2412,8 @@ Debugger::GetAddr(busaddr *sa, bool defa } // 有効な値は 1,2,5,6 のみ if (num == 0 || num == 3 || num == 4 || num == 7) { - fprintf(cons, "%c: invalid address space\n", ch); + fprintf(cons, "Invalid function code %d.\n", num); + return false; } mod_num = num; mod_super = mod_num >> 2; // FC2 @@ -2174,14 +2430,14 @@ Debugger::GetAddr(busaddr *sa, bool defa fprintf(cons, "not yet\n"); } } else { - fprintf(cons, "%c: unknown address space modifier\n", ch); + fprintf(cons, "Unknown address space modifier: %c\n", ch); return false; } continue; error: - fprintf(cons, "%s: address space modifier '%c' conflicts\n", - mod.c_str(), ch); + fprintf(cons, "Address space modifiers conflict: \"%s\"\n", + mod.c_str()); return false; } @@ -2201,8 +2457,7 @@ Debugger::GetAddr(busaddr *sa, bool defa // "6" -> Data (CPU#0) // "7" -> Program (CPU#0) if (mod_num < 6) { - fprintf(cons, "%d: CMMU#%d not installed\n", - mod_num, mod_num); + fprintf(cons, "CMMU %d not installed.\n", mod_num); return false; } mod_prog = mod_num - 6; @@ -2287,7 +2542,7 @@ Debugger::ChangeMD(DebuggerMD *newmd) { if (newmd != curmd) { curmd = newmd; - fprintf(cons, "Target cpu switched to '%s'\n", + fprintf(cons, "Target cpu is switched to '%s'.\n", curmd->GetName().c_str()); } } @@ -2334,8 +2589,8 @@ Debugger::cmd_d_common(busaddr access_mo saddr |= access_mode; // 2つ目の引数があれば行数 - if (args.size() > 2) { - row = strtol(args[2].c_str(), NULL, 10); + if (cmdargs.size() > 2) { + row = strtol(cmdargs[2].c_str(), NULL, 10); } // アドレス幅を制限 (XP用) (ここ?) @@ -2363,7 +2618,7 @@ Debugger::cmd_d_common(busaddr access_mo Debugger::CmdAct Debugger::cmd_disp() { - if (args.size() < 2) { + if (cmdargs.size() < 2) { // 表示 bool first = true; for (const auto& r : disp_regs) { @@ -2378,7 +2633,7 @@ Debugger::cmd_disp() char buf[256]; char *p; char *last; - strlcpy(buf, args[1].c_str(), sizeof(buf)); + strlcpy(buf, cmdargs[1].c_str(), sizeof(buf)); disp_regs.clear(); for (p = strtok_r(buf, ",", &last); p; p = strtok_r(NULL, ",", &last)) { disp_regs.push_back(std::string(p)); @@ -2393,8 +2648,8 @@ Debugger::cmd_disp() Debugger::CmdAct Debugger::cmd_L() { - if (args.size() < 2) { - fprintf(cons, "L [=][,[=]]...\n"); + if (cmdargs.size() < 2) { + fprintf(cons, "Usage: L [=][,[=]]...\n"); return CmdAct::Stay; } @@ -2402,11 +2657,11 @@ Debugger::cmd_L() // help が混ざる場合の "L foo=1,help" と "L foo=1 help" を同じ動作に // するため。 std::string str; - for (int i = 1, ac = args.size(); i < ac; i++) { + for (int i = 1, ac = cmdargs.size(); i < ac; i++) { if (str.empty() == false) { str += ','; } - str += args[i]; + str += cmdargs[i]; } // で、再分解 @@ -2466,8 +2721,8 @@ Debugger::cmd_m_common(busaddr access_mo saddr |= access_mode; // 2つ目の引数があれば行数 - if (args.size() > 2) { - row = strtol(args[2].c_str(), NULL, 10); + if (cmdargs.size() > 2) { + row = strtol(cmdargs[2].c_str(), NULL, 10); } // アドレス幅を制限 (XP用) (ここ?) @@ -2546,11 +2801,11 @@ Debugger::cmd_n_common(bool trace) { t_enable = trace ? curmd : NULL; - if (args.size() > 1) { + if (cmdargs.size() > 1) { int count; - count = strtol(args[1].c_str(), NULL, 10); + count = strtol(cmdargs[1].c_str(), NULL, 10); if (count < 1) { - fprintf(cons, " invalid step count: %d\n", count); + fprintf(cons, "Invalid step count: %d\n", count); return CmdAct::Stay; } @@ -2603,7 +2858,7 @@ Debugger::SetNBreakpoint() Debugger::CmdAct Debugger::cmd_q() { - fprintf(cons, "quit debugger console.\n"); + fprintf(cons, "Quit debugger console.\n"); return CmdAct::Quit; } @@ -2619,7 +2874,7 @@ Debugger::cmd_reset() Debugger::CmdAct Debugger::cmd_show() { - if (args.size() != 2) { + if (cmdargs.size() != 2) { std::vector list; // 登録されているモニタだけを列挙 @@ -2633,14 +2888,14 @@ Debugger::cmd_show() // 一覧を表示 std::string msg = MonitorManager::MakeListString(list); - fprintf(cons, "usage: show \n"); + fprintf(cons, "Usage: show \n"); fprintf(cons, "%s", msg.c_str()); return CmdAct::Stay; } std::vector candidates; std::vector cand_names; - std::string name = args[1]; + std::string name = cmdargs[1]; for (auto mon : gMonitorManager->GetList()) { uint id = mon->GetId(); @@ -2664,7 +2919,7 @@ Debugger::cmd_show() if (candidates.empty()) { // 一致しない - fprintf(cons, "unknown monitor name: \"%s\"\n", name.c_str()); + fprintf(cons, "Unknown monitor name: \"%s\"\n", name.c_str()); } else if (candidates.size() == 1) { // 確定した Monitor *mon = candidates[0]; @@ -2676,7 +2931,7 @@ Debugger::cmd_show() candstr += ' '; candstr += cname; } - fprintf(cons, "ambiguous monitor name \"%s\": candidates are%s\n", + fprintf(cons, "Ambiguous monitor name \"%s\". Candidates are:%s\n", name.c_str(), candstr.c_str()); } @@ -2793,11 +3048,11 @@ Debugger::cmd_s_common(bool trace) { t_enable = trace ? curmd : NULL; - if (args.size() > 1) { + if (cmdargs.size() > 1) { int count; - count = strtol(args[1].c_str(), NULL, 10); + count = strtol(cmdargs[1].c_str(), NULL, 10); if (count < 1) { - fprintf(cons, " invalid step count: %d\n", count); + fprintf(cons, "Invalid step count: %d\n", count); return CmdAct::Stay; } @@ -2875,33 +3130,31 @@ Debugger::ParseVerbHex(const char *arg, bool Debugger::ParseAddr(const char *arg, uint32 *addrp) { - uint32 addr; + uint64 addr; char *end; if (arg[0] == '%') { // '%' から始まればレジスタ - uint64 r; - r = curmd->GetRegAddr(arg + 1); - if ((int64)r < 0) { - fprintf(cons, "invalid register name\n"); + addr = curmd->GetRegAddr(arg + 1); + if ((int64)addr < 0) { + fprintf(cons, "Invalid register name.\n"); return false; } - addr = (uint32)r; } else { /* そうでなければ番地指定 */ errno = 0; addr = strtoul(arg, &end, 16); - if (arg[0] == '\0' || end[0] != '\0') { - fprintf(cons, "invalid address: '%s'\n", arg); + if (end == arg || *end != '\0') { + fprintf(cons, "Invalid address: '%s'\n", arg); return false; } - if (errno == ERANGE) { - fprintf(cons, "out of range: %s\n", arg); + if (addr > 0xffffffff) { + fprintf(cons, "Out of range: %s\n", arg); return false; } } - *addrp = addr; + *addrp = (uint32)addr; return true; } @@ -2916,11 +3169,11 @@ Debugger::ParseTime(const char *arg, uin errno = 0; f = strtod(arg, &end); if (end == arg) { - fprintf(cons, "invalid time: %s\n", arg); + fprintf(cons, "Invalid time: %s\n", arg); return false; } if (errno == ERANGE || f < 0 || std::isinf(f) || std::isnan(f)) { - fprintf(cons, "out of range: %s\n", arg); + fprintf(cons, "Out of range: %s\n", arg); return false; } @@ -2935,7 +3188,7 @@ Debugger::ParseTime(const char *arg, uin } else if (strcmp(end, "sec") == 0 || strcmp(end, "_sec") == 0) { t = f * 1_sec; } else { - fprintf(cons, "unknown time suffix: %s\n", arg); + fprintf(cons, "Unknown time suffix: %s\n", end); return false; }