• Zoran
    gdb 还有哪些类似于 enable pretty-printer 这样实用的功能呢

  • GPT4
    gdb 提供了许多有用的功能,以帮助您更有效地调试程序。除了 pretty-printers 之外,还有其他一些实用的功能,如下所示:

  1. User-Defined Commands:用户可以创建自定义命令,将多个 gdb 命令组合在一起以简化调试过程。使用 define 命令创建自定义命令,并使用 end 结束命令定义。

    1
    2
    3
    4
    5
    define my_cmd
    command_1
    command_2
    ...
    end
  2. Command Historygdb 会保存您输入的命令历史,以便您可以通过上下键方便地访问先前使用的命令。

  3. Reverse Debugginggdb 支持反向调试,允许您回退到先前的程序状态。使用 reverse-* 命令(如 reverse-step, reverse-next, reverse-continue)在程序的执行历史中向后导航。

  4. Conditional Breakpoints:您可以设置带有条件表达式的断点,只有当该表达式为真时才会触发断点。例如,您可以使用以下命令在 i 变量等于 5 时设置一个条件断点:

    1
    break my_function if i == 5
  5. Watchpoints:使用 watchpoints 可以监视特定变量或内存地址的更改。当被监视的变量值发生更改时,gdb 将停止程序的执行。使用 watch 命令设置一个 watchpoint:

    1
    watch variable_name
  6. Call Stack Inspection:使用 bt(backtrace)命令,您可以检查程序的调用堆栈,查看函数调用的顺序和当前的堆栈帧。您还可以使用 frame 命令在不同堆栈帧之间切换。

  7. Thread Debugginggdb 支持多线程程序的调试。使用 info threads 命令查看所有可用线程,使用 thread 命令在不同线程之间切换。

  8. Attach and Detach:您可以使用 gdb 附加到一个正在运行的进程进行调试。使用 attach 命令附加到进程,然后在完成调试后使用 detach 命令从进程中分离。

  9. Remote Debugginggdb 可以通过网络远程调试程序。要实现这一点,您需要在目标设备上运行 gdbserver,然后使用 gdbtarget remote 命令连接到目标设备。

  10. Python Scripting:从 gdb 7.0 开始,您可以使用 Python 脚本扩展 gdb 的功能。这使得您可以编写更复杂的自定义命令

GDB 奇技淫巧

Zoran:

  • 我希望设置一个全局的 gdb 配置, 比如每次 gdb 调试的时候都首先 tui enable, b main, 应该怎么做

ChatGPT:

  • 您可以创建一个名为 .gdbinit 的文件,该文件包含您希望在启动 gdb 时自动执行的命令。将此文件放置在您的主目录下,或者在您运行 gdb 的当前工作目录下。gdb 在启动时会首先查找这两个位置的 .gdbinit 文件。

下面是一个 .gdbinit 文件的示例,该文件自动执行 tui enableb main 命令:

1
2
3

tui enable
b main

要创建这个文件,请打开一个文本编辑器,将上述内容粘贴进去,然后将文件保存为 .gdbinit。将此文件放置在您的主目录(~/.gdbinit)或您的当前工作目录下。

现在,当您使用 gdb 调试程序时,它将首先启用 TUI 模式并在 main 函数上设置断点。

如果您希望创建针对特定项目的配置文件,可以将 .gdbinit 文件放在项目目录下。在这种情况下,当您在该目录下运行 gdb 时,将使用该目录下的 .gdbinit 文件。请注意,从 gdb 7.0 开始,为了安全起见,gdb 默认不允许自动加载当前工作目录中的 .gdbinit 文件。要允许这一行为,您需要在主目录的 .gdbinit 文件中添加以下行:

1
2

set auto-load local-gdbinit on

这将允许 gdb 在启动时自动加载当前工作目录下的 .gdbinit 文件。

Zoran:

  • gdb 还有哪些类似于 enable pretty-printer 这样实用的功能呢

ChatGPT:

  • gdb 提供了许多有用的功能,以帮助您更有效地调试程序。除了 pretty-printers 之外,还有其他一些实用的功能,如下所示:
  1. User-Defined Commands :用户可以创建自定义命令,将多个 gdb 命令组合在一起以简化调试过程。使用 define 命令创建自定义命令,并使用 end 结束命令定义。
1
2
3
4
5
6

define my_cmd
command_1
command_2
...
end
  1. Command Historygdb 会保存您输入的命令历史,以便您可以通过上下键方便地访问先前使用的命令。
  2. Reverse Debugginggdb 支持反向调试,允许您回退到先前的程序状态。使用 reverse-* 命令(如 reverse-step, reverse-next, reverse-continue)在程序的执行历史中向后导航。
  3. Conditional Breakpoints :您可以设置带有条件表达式的断点,只有当该表达式为真时才会触发断点。例如,您可以使用以下命令在 i 变量等于 5 时设置一个条件断点:
1
2

break my_function if i == 5
  1. Watchpoints :使用 watchpoints 可以监视特定变量或内存地址的更改。当被监视的变量值发生更改时,gdb 将停止程序的执行。使用 watch 命令设置一个 watchpoint:
1
2

watch variable_name
  1. Call Stack Inspection :使用 bt(backtrace)命令,您可以检查程序的调用堆栈,查看函数调用的顺序和当前的堆栈帧。您还可以使用 frame 命令在不同堆栈帧之间切换。
  2. Thread Debugginggdb 支持多线程程序的调试。使用 info threads 命令查看所有可用线程,使用 thread 命令在不同线程之间切换。
  3. Attach and Detach :您可以使用 gdb 附加到一个正在运行的进程进行调试。使用 attach 命令附加到进程,然后在完成调试后使用 detach 命令从进程中分离。
  4. Remote Debugginggdb 可以通过网络远程调试程序。要实现这一点,您需要在目标设备上运行 gdbserver,然后使用 gdbtarget remote 命令连接到目标设备。
  5. Python Scripting :从 gdb 7.0 开始,您可以使用 Python 脚本扩展 gdb 的功能。这使得您可以编写更复杂的自定义命

Zoran:

  • (gdb) reverse-step
    Target native does not support this command.
    这是什么原因

ChatGPT:

  • gdb 的反向调试功能并非在所有平台上都受支持。在您遇到的情况下,当您尝试执行 reverse-step 命令时,gdb 返回了 “Target native does not support this command.” 这意味着您所使用的平台不支持反向调试功能。

反向调试需要特定的硬件或模拟器支持,因此在许多平台上不可用。通常,在使用基于硬件虚拟化技术(如 QEMU)或使用特定处理器(如支持 Branch Trace Store 的 Intel 处理器)进行调试时,反向调试才可用。

如果您仍希望尝试使用反向调试,可以考虑以下替代方法:

  1. 使用 QEMU 进行模拟。QEMU 是一个通用的开源计算机模拟器和虚拟化器,支持将目标系统运行在虚拟环境中。您可以将程序在 QEMU 上运行,然后使用 QEMU 的 gdbserver 功能连接到 gdb。在这种配置下,您应该能够使用反向调试。
  2. 将程序的执行分解成更小的部分,然后在适当的位置设置断点以便于逐步分析。这样,即使您无法使用反向调试,也可以通过在关键点设置断点来近似反向执行的效果。

需要注意的是,这些方法仍可能无法为您提供与实际反向调试相同的功能。但它们至少可以帮助您更好地理解程序的执行过程。

Zoran:

  • 有哪些 gdb 的奇技淫巧呢

ChatGPT:

  • gdb 提供了许多强大的功能,这里列出了一些奇技淫巧,可以帮助您更有效地使用 gdb 进行调试:
  1. 查找函数的源代码位置 :使用 info functions 命令加上正则表达式,找到与之匹配的函数及其源代码位置。例如:
1
2

info functions .*main

这将列出所有包含 “main” 的函数及其在源代码中的位置。 2. 从当前位置运行到指定行 :使用 until 命令从当前位置运行到指定行。这在你想跳过某个范围内的代码时非常有用。

1
2

until 42

这将运行程序,直到执行到第 42 行。 3. 执行指定次数的命令 :在命令前加上一个数字,可以执行多次该命令。例如,要执行 5 次 next 命令:

1
2

5 next
  1. 更改运行时的变量值 :您可以在调试过程中更改变量的值。这对于测试代码在不同条件下的行为非常有用。
1
2

set variable my_variable = 42
  1. 在命令中使用文件名和行号 :您可以在某些命令中使用文件名和行号,例如设置断点。这在您知道要在特定源文件的特定行上设置断点时非常有用。
1
2

break main.c:42
  1. 忽略断点的前 N 次触发 :使用 ignore 命令设置断点的忽略计数。例如,要忽略断点 1 的前 5 次触发:
1
2

ignore 1 5
  1. 将输出重定向到文件 :在命令后添加 > file_name 将该命令的输出重定向到指定的文件中。例如,将 info registers 的输出保存到名为 registers.txt 的文件中:
1
2

info registers > registers.txt
  1. 查找内存泄漏 :使用 gdbmalloc_history 命令找到在程序执行期间未释放的内存分配。这可以帮助您识别内存泄漏。
  2. ** 为断点添加自动执行的命令** :使用 commands 命令为断点分配一个或多个自动执行的命令。当断点被触发时,这些命令将自动执行。这对于在断点触发时自动更改变量或显示特定信息非常有用。
1
2
3
4
5

commands 1
print some_variable
continue
end

在这个例子中,当断点 1 触发时,它将打印 some_variable 的值

Zoran:

  • 继续

ChatGPT:

  • 的值并继续执行。这种自动执行的命令对于观察程序状态变化非常有用,而无需每次都手动输入命令。
  1. ** 命令自动显示变量** :display 命令允许您在每次程序停止时自动显示一个或多个表达式的值。这可以让您更轻松地观察特定变量在执行过程中的变化。
1
2

display my_variable

这将在每次程序停止时显示 my_variable 的值。 2. ** 保存断点信息** :如果您在一个调试会话中设置了许多断点,并希望在其他调试会话中重用这些断点,您可以使用 save breakpoints 命令将断点信息保存到一个文件中。

1
2

save breakpoints breakpoints.txt

要在另一个调试会话中加载这些断点,使用 source 命令:

1
2

source breakpoints.txt
  1. 显示汇编与源代码混合视图 :使用 layout asmlayout src 命令在 TUI 模式下显示汇编与源代码的混合视图。这使您可以在同一界面中查看源代码和对应的汇编代码。
  2. 跟踪系统调用 :使用 catch syscall 命令捕获和跟踪系统调用。您可以使用这个命令来跟踪程序如何与操作系统进行交互。
1
2

catch syscall open

上述命令会在程序尝试打开文件(使用 open 系统调用)时停止执行。

这些奇技淫巧可以让您更高效地使用 gdb 进行调试。然而,请注意,根据您的 gdb 版本和所使用的平台,部分功能可能不可用或需要特定配置才能正常工作。尝试这些技巧时,请务必参考您所使用的 gdb 版本的官方文档。

Zoran:

  • 还有哪些技巧呢

ChatGPT:

  • 这里还有一些其他 gdb 技巧,以帮助您在调试过程中提高效率:
  1. 使用别名 :您可以为频繁使用的命令创建别名,以便快速调用。使用 define 命令创建一个别名:
1
2
3
4

define pname
print variable_name
end

这将创建一个名为 pname 的别名,执行时将输出 variable_name 的值。 2. 列出源代码 :使用 list 命令列出当前源文件的内容。您可以指定要显示的行号范围或函数名。例如,要列出 main 函数附近的代码:

1
2

list main
  1. 在命令中使用通配符 :在一些命令中,您可以使用通配符(*)匹配多个项目。例如,您可以一次性删除多个断点:
1
2

delete breakpoints * # 删除所有断点
  1. 查找并显示符号信息 :使用 info symbol 命令可以查找并显示特定内存地址的符号信息。这有助于识别内存地址对应的函数或变量。
1
2

info symbol 0x400000
  1. ** 搜索命令和帮助信息** :如果您无法记住某个命令的名称,可以使用 apropos 命令搜索相关命令和帮助信息。
1
2

apropos break
  1. 比较内存中的数据 :使用 cmp 命令可以比较两个内存地址范围中的数据。例如,比较地址 0x4000000x500000 处的 4 字节:
1
2

cmp 0x400000 0x500000 4
  1. 检查内存区域的值 :使用 x(examine)命令检查内存区域的值。您可以指定要显示的格式和单位。例如,查看以十六进制格式显示的 4 个字(32 位):
1
2

x/4xw 0x400000
  1. 跳转到指定行执行 :使用 jump 命令可以将程序的执行跳转到指定行。这可以帮助您快速跳过或返回到特定代码段。
1
2

jump 42
  1. 设置命令执行时的超时gdb 提供了 timeout 命令,允许您为命令设置一个超时值。当命令执行超过指定时间时,gdb 会自动停止该命令。这在调试慢速程序或处理器密集型任务时非常有用。
1
2

timeout 5 next

这个命令将在 5 秒后超时,无论 `next

Zoran:

  • 继续

ChatGPT:

  • 无论 next 命令是否已经完成。
  1. 监视变量值的变化 :使用 watch 命令在变量值发生变化时自动中断程序。这对于跟踪可能出现错误的变量非常有用。
1
2

watch my_variable

这将在 my_variable 的值发生变化时中断程序。 2. 跟踪函数参数和返回值 :使用 info argsinfo locals 命令查看当前函数的参数和局部变量。使用 return 命令查看或修改函数的返回值。

1
2
3
4

info args
info locals
return
  1. 启用和禁用断点 :您可以使用 enabledisable 命令临时启用或禁用断点,而无需删除它们。这在调试过程中管理断点非常有用。
1
2
3

disable 1
enable 1
  1. 设置条件断点 :您可以设置一个条件断点,当满足特定条件时才会触发。例如,仅在 my_variable 值大于 10 时触发断点:
1
2

break my_function if my_variable > 10
  1. 记录和回放程序的执行gdb 支持记录程序的执行过程,之后您可以回放这个过程进行分析。使用 record 开始记录,然后使用 stop 停止记录。在记录过程中,您可以使用 next, step, finish, continue 等命令。回放时,使用 reverse-* 命令,如 reverse-nextreverse-step 等。
1
2
3

record
stop

这些技巧可以帮助您更高效地使用 gdb 调试程序。然而,请注意,根据您的 gdb 版本和所使用的平台,部分功能可能不可用或需要特定配置才能正常工作。尝试这些技巧时,请务必参考您所使用的 gdb 版本的官方文档。

Zoran:

  • 这是我的 gdbinit 文件:
    tui enable
    enable pretty-printer

每次执行 gdb 调试的时候, 都会先弹出这样的其提示, 这是什么原因:
None No process In: L?? PC: ??
–Type for more, q to quit, c to continue without paging–

ChatGPT:

  • 这个提示来自 gdb,表示 .gdbinit 文件中的命令在启动时被执行。在这种情况下,tui enableenable pretty-printer 在执行时会输出一些信息。

问题出在 gdb 启动时没有加载任何程序或进程,因此在执行 .gdbinit 中的命令时,tui enable 试图在没有进程上下文的情况下启用 TUI 模式,从而导致这个提示。

为了解决这个问题,您可以在 .gdbinit 文件中添加一条 if 语句,以确保只在加载了程序或进程的情况下执行 tui enableenable pretty-printer 命令:

1
2
3
4
5

if $argc > 0
tui enable
enable pretty-printer
end

这将确保只有在 gdb 加载了一个程序或进程时,才会执行 tui enableenable pretty-printer。这样一来,您就不会再看到该提示。

Zoran:

  • 为什么使用这个脚本之后, 我使用 gdb hello, 来调试命令也没有调用 tui enable 呢

ChatGPT:

  • 抱歉,我在上面的回答中犯了一个错误。使用 $argc 是不正确的,因为 $argc 表示程序的命令行参数个数,而不是 gdb 自己的命令行参数个数。

要在 .gdbinit 文件中正确检查 gdb 是否已加载程序,您应该使用下面的脚本:

1
2
3
4
5
6
7
8

# 在 .gdbinit 中检查程序是否已加载
python
import gdb
if len(gdb.execute('info files', to_string=True).strip()) > 0:
gdb.execute('tui enable')
gdb.execute('enable pretty-printer')
end

这段脚本使用 Python API,通过执行 info files 命令来检查是否已经加载了程序。当 gdb.execute('info files', to_string=True).strip() 的长度大于 0 时,说明已经加载了程序。此时,脚本将执行 tui enableenable pretty-printer 命令。

Zoran:

  • gdb 如何加载 python 脚本文件呢

ChatGPT:

  • 在 GDB 中,可以使用source命令加载并执行 Python 脚本文件。假设您有一个名为myscript.py的 Python 脚本文件,您可以在 GDB 中这样加载它:
1
2

source myscript.py

当您执行此命令时,GDB 将加载myscript.py文件并执行其中的 Python 代码。

此外,您还可以在.gdbinit文件中添加source命令,以便在启动 GDB 时自动加载 Python 脚本。例如,要在启动时自动加载名为myscript.py的脚本,请将以下行添加到.gdbinit文件中:

1
2

source myscript.py

现在,每当您启动 GDB 时,它都会自动加载并执行myscript.py脚本中的 Python 代码。

Zoran:

  • (gdb) source /home/zoran/.gdbinit.py
    Traceback (most recent call last):
    File “/home/zoran/.gdbinit.py”, line 5, in
    end
    NameError: name ‘end’ is not defined
    (gdb)
    这是我的 gdbinit.py 文件:
    ❯ cat $HOME/.gdbinit.py
    ───────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    │ File: /home/zoran/.gdbinit.py
    ───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    1 │ import gdb
    2 │ if len(gdb.execute(‘info files’, to_string=True).strip()) > 0:
    3 │ gdb.execute(‘tui enable’)
    4 │ gdb.execute(‘enable pretty-printer’)
    5 │ end
    ───────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

ChatGPT:

  • 抱歉,我的回答中犯了一个错误。Python 中没有 end 关键字。在 Python 中,您应该使用缩进(空格或制表符)来表示代码块。在您的例子中,您只需删除第 5 行中的 end

修改后的 .gdbinit.py 文件应如下所示:

1
2
3
4
5

import gdb
if len(gdb.execute('info files', to_string=True).strip()) > 0:
gdb.execute('tui enable')
gdb.execute('enable pretty-printer')

现在,当您在 GDB 中使用 source 命令加载此脚本时,应该不会出现错误。

Zoran:

  • gdb 的 set pretty prin on 和 enable pretty-printer 有什么区别

ChatGPT:

  • 在 GDB 中,set print pretty onenable pretty-printer 具有不同的目的,尽管它们看起来类似。
  1. set print pretty on:这个命令用于启用或禁用 GDB 的 “漂亮打印” 功能,用于控制输出的格式。这个设置会改变 GDB 如何显示复杂数据结构(例如结构体、联合体和数组)的默认显示方式。当这个选项被启用(on)时,GDB 将尝试以更易读的方式显示这些数据结构。例如,每个字段都会显示在新行上,并且会添加适当的缩进。您可以使用 set print pretty off 关闭此功能。

示例:

1
2
3
4
5
6
7
8
9
10
11

(gdb) set print pretty on
(gdb) p my_struct
$1 = {
field1 = 42,
field2 = {
sub_field1 = 3.14,
sub_field2 = "hello, world"
},
field3 = 'a'
}
  1. enable pretty-printer:这个命令用于启用或禁用已加载的自定义 GDB pretty-printer。这些 pretty-printer 是用 Python 编写的,允许您自定义如何显示特定类型的数据。这对于查看 C++ STL 容器(如 std::vectorstd::map 等)和自定义数据结构非常有用。默认情况下,这些 pretty-printer 是启用的,您可以使用 disable pretty-printer 命令关闭它们。

示例:

1
2
3
4

(gdb) enable pretty-printer
(gdb) p my_vector
$1 = std::vector of length 3, capacity 4 = {1, 2, 3}

总之,set print pretty on 控制 GDB 默认的格式化输出,而 enable pretty-printer 控制自定义的 pretty-printer(如 C++ STL 容器)。它们具有不同的用途,可以根据需要启用或禁用。

Zoran:

  • 如何使用 gdb 命令查看选项是否是开启的呢

ChatGPT:

  • 要在 GDB 中查看特定选项的当前状态,您可以使用 show 命令。下面是一些示例,展示如何使用 show 命令检查不同选项的状态:
  1. 查看 pretty printing 的状态:
1
2

(gdb) show print pretty
  1. 查看 auto-loading 是否启用:
1
2

(gdb) show auto-load
  1. 查看当前使用的语言:
1
2

(gdb) show language
  1. 查看十六进制显示设置:
1
2

(gdb) show print hex

对于某些设置,可能需要在 show 命令后加上子命令来查看具体的状态,例如查看 pretty-printer 的状态:

1
2

(gdb) show pretty-printer

此命令将列出已加载的 pretty-printer 以及它们当前是否启用。您可以从输出中判断各个 pretty-printer 的启用状态。

在 GDB 中,您可以使用 help show 命令查看可用的 show 命令。请注意,可用的设置取决于您使用的 GDB 版本和编译选项。您也可以参阅 GDB 的官方文档以获取更详细的信息。

Zoran:

  • “set print elements 0”,
    这段代码有什么用

ChatGPT:

  • 在 GDB 中,set print elements 命令用于控制在输出字符串或数组时打印的最大字符数。通过设置一个限制,您可以防止 GDB 输出过长的字符串或数组,从而避免控制台被大量数据淹没。

set print elements 0 这个命令将最大输出字符数设置为 0,表示没有限制,GDB 将打印完整的字符串或数组。这在查看较长的字符串或数组时可能很有用,但请注意,输出大量数据可能导致 GDB 的响应速度变慢。

您可以使用 set print elements <number> 命令将最大输出字符数设置为一个正整数。例如,如果要限制输出的字符数为 100,可以执行以下命令:

1
2

(gdb) set print elements 100

此设置后,GDB 在输出字符串或数组时将最多只打印前 100 个字符。要恢复默认设置(通常为 200),您可以执行:

1
2

(gdb) set print elements 200

要查看当前的最大输出字符数设置,您可以使用 show print elements 命令:

1
2

(gdb) show print elements

Zoran:

  • 我构造了这样一个 NFA 的代码, 我希望通过 gdb 可以将构造的 NFA 自动机的图的状态导出, 可能的方式应该是如何做到呢:
    #pragma once
    #include <Util.hpp>
    #include
    #include <color.h>
    #include
    #include <fmt/format.h>
    #include
    #include
    #include
    #include
    #include
    #include
    #include

/**

  • @class NFA
  • @brief Thompson algorithm’s NFA
  • */
    class NFA
    {
    public:
    using state = uint32_t;
    using size_t = uint32_t;
    using str = std::string;
    using priority_t = uint32_t;
    using str_view = std::string_view;

public:
NFA() = default;
/**
_ @brief The wrapper of parse function
_
_ @tparam Args see parse function
_ @param args parse method’s arguments
*/
template <typename… Args>
explicit NFA(Args&&… args) noexcept;
NFA(NFA&&) = default;
NFA(const NFA&) = default;
NFA& operator= (NFA&&) = default;
NFA& operator= (const NFA&) = default;
~NFA() = default;

public:
void toDiagram(
const str& filename, const std::ios_base::openmode flag = std::ios::app) const noexcept;
void parse(const str RE, str&& info = “”, NFA::priority_t priority = 1) noexcept;
void clear() noexcept;
friend std::ostream& operator<< (std::ostream& os, const NFA& nfa) noexcept;
NFA& operator+ (NFA& rhs) noexcept;

bool match(const str_view& str) const noexcept;

public:
static NFA::str stateInfo() noexcept;

private:
static state new_state()
{
return _size++;
}

private:
std::map<std::pair<state, char>, state> transition {};
std::map<state, std::set> epsilon_transition {};
state _start {};
state _end {};

private:
static std::map<state, std::pair<priority_t, str>> state_info;
static size_t _size;
str RE {};
str postfix {};
str pre_process {};
};

inline NFA::size_t NFA::_size {};
inline std::map<NFA::state, std::pair<NFA::priority_t, NFA::str>> NFA::state_info {};

/*

  • INFO :
  • Thompson algorithm
  • meta characters : ( ) | _ + ?
    _/
    template <typename… Args>
    inline NFA::NFA(Args&&… args) noexcept
    {
    parse(std::forward(args)…);
    }

inline std::ostream& operator<< (std::ostream& os, const NFA& nfa) noexcept
{
using namespace Color;

os << Green << "start : " << End << nfa._start << '\n';
os << Green << "end : " << End << nfa._end << '\n';
os << Green << "transition : \n";
for (const auto& [key, value] : nfa.transition) {
    os << Blue << key.first << End << " -" << Blue << key.second << End << "-> " << value
       << '\n';
}


os << Green << "epsilon transition : \n" << End;
for (const auto& [key, value] : nfa.epsilon_transition) {
    os << Yellow << (key) << End << " -"
       << "epsilon"
       << "-> ";
    for (const auto& v : value) {
        os << v << ' ';
    }
    os << '\n';
}
return os;

}

inline void
NFA::toDiagram(const std::string& filename, const std::ios_base::openmode flag) const noexcept
{
using os = std::ofstream;
os out { filename, flag };
assert(out.is_open());

out << fmt::format(
    "## RE: {}\n"
    "### Preprocess : {}\n"
    "### Postfix : {}\n"
    "```dot\n"
    "digraph G