5.2GDB常用命令
- 以下以 test_file.c 作为源程序例子的名字,test_file.exe 作为可执行文件例子的名字, 以param_1 作为参数的例子的名字。
- (gdb) 表示是在 gdb 调试模式下运行
- 一般常用的方法有两种,即打断点调试 和单步调试。
- list(l): 列出源代码
- quit(q): 退出 gdb 调试模式
- 进入 gdb 之后,输入 help 可以查看所有命令的使用说明
查看源码
list [函数名][行数]
打断点调试
(1)设置断点:
- a、break + [源代码行号][源代码函数名][内存地址]
- b、break ... if condition ...可以是上述任一参数,condition是条件。例如在循环体中可以设置break ... if i = 100 来设置循环次数
删除断点
(gdb) clear location:参数 location 通常为某一行代码的行号或者某个具体的函数名。当 location 参数为某个函数的函数名时,表示删除位于该函数入口处的所有断点。
(gdb) delete [breakpoints] [num]:breakpoints 参数可有可无,num 参数为指定断点的编号,其可以是 delete 删除某一个断点,而非全部。
禁用断点
disable [breakpoints] [num...]:breakpoints 参数可有可无;num... 表示可以有多个参数,每个参数都为要禁用断点的编号。如果指定 num...,disable 命令会禁用指定编号的断点;反之若不设定 num...,则 disable 会禁用当前程序中所有的断点。
激活断点
- enable [breakpoints] [num...]激活用 num... 参数指定的多个断点,如果不设定 num...,表示激活所有禁用的断点
- enable [breakpoints] once num… 临时激活以 num... 为编号的多个断点,但断点只能使用 1 次,之后会自动回到禁用状态
- enable [breakpoints] count num... 临时激活以 num... 为编号的多个断点,断点可以使用 count 次,之后进入禁用状态
- enable [breakpoints] delete num… 激活 num.. 为编号的多个断点,但断点只能使用 1 次,之后会被永久删除。
break(b): 打的是普通断点,打断点有两种形式
(gdb) break location // b location,location 代表打断点的位置
(gdb) break ... if cond // b .. if cond,代表如果 cond 条件为true,则在 “...” 处打断点
通过借助 condition 命令为不同类型断点设置条件表达式,只有当条件表达式成立(值为 True)时,相应的断点才会触发从而使程序暂停运行。
tbreak: tbreak 命令可以看到是 break 命令的另一个版本,tbreak 和 break 命令的用法和功能都非常相似,唯一的不同在于,使用 tbreak 命令打的断点仅会作用 1 次,即使程序暂停之后,该断点就会自动消失。
rbreak: 和 break 和 tbreak 命令不同,rbreak 命令的作用对象是 C、C++ 程序中的函数,它会在指定函数的开头位置打断点。
- (gdb) tbreak regex
- regex 代表一个正则表达式,会在匹配到的函数的内部的开头位置打断点
- tbreak 命令打的断点和 break 命令打断点的效果是一样的,会一直存在,不会自动消失。
watch: 此命令打的是观察断点,可以监控某个变量或者表达式的值。只有当被监控变量(表达式)的值发生改变,程序才会停止运行。
- (gdb) watch cond
- cond 代表的就是要监控的变量或者表达式
rwatch 命令:只要程序中出现读取目标变量(表达式)的值的操作,程序就会停止运行;
awatch 命令:只要程序中出现读取目标变量(表达式)的值或者改变值的操作,程序就会停止运行。
catch: 捕捉断点的作用是,监控程序中某一事件的发生,例如程序发生某种异常时、某一动态库被加载时等等,一旦目标时间发生,则程序停止执行。
(2)观察断点:
- a、watch + [变量][表达式] 当变量或表达式值改变时即停住程序。
- b、rwatch + [变量][表达式] 当变量或表达式被读时,停住程序。
- c、awatch + [变量][表达式] 当变量或表达式被读或被写时,停住程序。
(3)设置捕捉点:
catch + event 当event发生时,停住程序。
event可以是下面的内容:
- a、throw 一个C++抛出的异常。(throw为关键字)
- b、catch 一个C++捕捉到的异常。(catch为关键字)
- c、exec 调用系统调用exec时。(exec为关键字,目前此功能只在HP-UX下有用)
- d、fork 调用系统调用fork时。(fork为关键字,目前此功能只在HP-UX下有用)
- e、vfork 调用系统调用vfork时。(vfork为关键字,目前此功能只在HP-UX下有用)
- f、load 或 load 载入共享库(动态链接库)时。(load为关键字,目前此功能只在HP-UX下有用)
- g、unload 或 unload 卸载共享库(动态链接库)时。(unload为关键字,目前此功能只在HP-UX下有用)
(4)捕获信号:
handle + [argu] + signals
signals:是Linux/Unix定义的信号,SIGINT表示中断字符信号,也就是Ctrl+C的信号,SIGBUS表示硬件故障的信号;SIGCHLD表示子进程状态改变信号; SIGKILL表示终止程序运行的信号,等等。
argu:
- nostop 当被调试的程序收到信号时,GDB不会停住程序的运行,但会打出消息告诉你收到这种信号。
- stop 当被调试的程序收到信号时,GDB会停住你的程序。
- print 当被调试的程序收到信号时,GDB会显示出一条信息。
- noprint 当被调试的程序收到信号时,GDB不会告诉你收到信号的信息。
- pass or noignore 当被调试的程序收到信号时,GDB不处理信号。这表示,GDB会把这个信号交给被调试程序会处理。
- nopass or ignore 当被调试的程序收到信号时,GDB不会让被调试程序来处理这个信号。
(5)线程中断:
break [linespec] thread [threadno] [if ...]
linespec 断点设置所在的源代码的行号。如: test.c:12表示文件为test.c中的第12行设置一个断点。
threadno 线程的ID。是GDB分配的,通过输入info threads来查看正在运行中程序的线程信息。
if ... 设置中断条件。
查看信息:
(1)查看数据:
print variable 查看变量
print *array@len 查看数组(array是数组指针,len是需要数据长度)
可以通过添加参数来设置输出格式:
/ 按十六进制格式显示变量。 /d 按十进制格式显示变量。 /u 按十六进制格式显示无符号整型。 /o 按八进制格式显示变量。 /t 按二进制格式显示变量。 /a 按十六进制格式显示变量。 /c 按字符格式显示变量。 /f 按浮点数格式显示变量。
(2)查看内存
examine /n f u + 内存地址(指针变量)
- n 表示显示内存长度
- f 表示输出格式(见上)
- u 表示字节数制定(b 单字节;h 双字节;w 四字节;g 八字节;默认为四字节)
如:x /10cw pFilePath (pFilePath为一个字符串指针,指针占4字节) x 为examine命令的简写。
(3)查看栈信息
backtrace [-n][n]
- n 表示只打印栈顶上n层的栈信息。
- -n 表示只打印栈底上n层的栈信息。
- 不加参数,表示打印所有栈信息。
单步调试
run(r)
continue(c)
next(n)
- 命令格式: (gdb) next count:count 表示单步执行多少行代码,默认为 1 行
- 其最大的特点是当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next 指令都会一步执行完。也就是说,对于调用的函数来说,next 命令只会将其视作一行代码
step(s)
- (gdb) step count:参数 count 表示一次执行的行数,默认为 1 行。
- 通常情况下,step 命令和 next 命令的功能相同,都是单步执行程序。不同之处在于,当 step 命令所执行的代码行中包含函数时,会进入该函数内部,并在函数第一行代码处停止执行。
until(u)
- (gdb) until:不带参数的 until 命令,可以使 GDB 调试器快速运行完当前的循环体,并运行至循环体外停止。注意,until 命令并非任何情况下都会发挥这个作用,只有当执行至循环体尾部(最后一行代码)时,until 命令才会发生此作用;反之,until 命令和 next 命令的功能一样,只是单步执行程序
(gdb) until location:参数 location 为某一行代码的行号
查看变量的值
print(p)
- p num_1:参数 num_1 用来代指要查看或者修改的目标变量或者表达式
- 它的功能就是在 GDB 调试程序的过程中,输出或者修改指定变量或者表达式的值
isplay
- (gdb) display expr
- (gdb) display/fmt expr
- expr 表示要查看的目标变量或表达式;参数 fmt 用于指定输出变量或表达式的格式
- (gdb) undisplay num...
- (gdb) delete display num...
- 参数 num... 表示目标变量或表达式的编号,编号的个数可以是多个
- (gdb) disable display num...
- 禁用自动显示列表中处于激活状态下的变量或表达式
- (gdb) enable display num...
- 也可以激活当前处于禁用状态的变量或表达式
- 和 print 命令一样,display 命令也用于调试阶段查看某个变量或表达式的值
- 它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会
GDB handle 命令: 信号处理
→(gdb) handle signal mode其中,signal 参数表示要设定的目标信号,它通常为某个信号的全名(SIGINT)或者简称(去除‘SIG’后的部分,如 INT);如果要指定所有信号,可以用 all 表示。
mode 参数用于明确 GDB 处理该目标信息的方式,其值可以是如下几个:
- ostop:当信号发生时,GDB 不会暂停程序,其可以继续执行,但会打印出一条提示信息,告诉我们信号已经发生;
- stop:当信号发生时,GDB 会暂停程序执行。
- noprint:当信号发生时,GDB 不会打印出任何提示信息;
- print:当信号发生时,GDB 会打印出必要的提示信息;
- nopass(或者 ignore):GDB 捕获目标信号的同时,不允许程序自行处理该信号;
- pass(或者 noignore):GDB 调试在捕获目标信号的同时,也允许程序自动处理该信号。
可以在 gdb 模式下,通过 info signals 或者 info signals <signal_name> (例如 info signals SIGINT) 查看不同 signal 的信息。
GDB frame和backtrace命令:查看栈信息
(gdb) frame spec 该命令可以将 spec 参数指定的栈帧选定为当前栈帧。spec 参数的值,常用的指定方法有 3 种:
- 通过栈帧的编号指定。0 为当前被调用函数对应的栈帧号,最大编号的栈帧对应的函数通常就是 main() 主函数;
- 借助栈帧的地址指定。栈帧地址可以通过 info frame 命令(后续会讲)打印出的信息中看到;
- 通过函数的函数名指定。注意,如果是类似递归函数,其对应多个栈帧的话,通过此方法指定的是编号最小的那个栈帧。
(gdb) info frame 我们可以查看当前栈帧中存储的信息
该命令会依次打印出当前栈帧的如下信息:
- 当前栈帧的编号,以及栈帧的地址;
- 当前栈帧对应函数的存储地址,以及该函数被调用时的代码存储的地址
- 当前函数的调用者,对应的栈帧的地址;
- 编写此栈帧所用的编程语言;
- 函数参数的存储地址以及值;
- 函数中局部变量的存储地址;
- 栈帧中存储的寄存器变量,例如指令寄存器(64位环境中用 rip 表示,32为环境中用 eip 表示)、堆栈基指针寄存器(64位环境用 rbp 表示,32位环境用 ebp 表示)等。
除此之外,还可以使用 info args
命令查看当前函数各个参数的值;使用 info locals
命令查看当前函数中各局部变量的值。
(gdb) backtrace [-full] [n] 用于打印当前调试环境中所有栈帧的信息
其中,用 [ ] 括起来的参数为可选项,它们的含义分别为:
- n:一个整数值,当为正整数时,表示打印最里层的 n 个栈帧的信息;n 为负整数时,那么表示打印最外层 n 个栈帧的信息;
- -full:打印栈帧信息的同时,打印出局部变量的值。
GDB编辑和搜索源码
GDB edit命令:编辑文件
- (gdb) edit [location]
- (gdb) edit [filename] : [location]
- location 表示程序中的位置。这个命令表示激活文件的指定位置,然后进行编辑。
- 如果遇到报错 "bash: /bin/ex: 没有那个文件或目录", 因为 GDB 的默认编辑器是 ex , 则需要指定编辑器,如 export EDITOR=/usr/bin/vim or export EDITOR=/usr/bin/vi
GDB search命令:搜索文件
- search <regexp>
- reverse-search <regexp>
- 第一项命令格式表示从当前行的开始向前搜索,后一项表示从当前行开始向后搜索。其中 regexp 就是正则表达式,正则表达式描述了一种字符串匹配的模式,可以用来检查一个串中是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串。很多的编程语言都支持使用正则表达式。
5.3GDB调试程序用法
一般来说,GDB主要帮忙你完成下面四个方面的功能:
1、启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
2、可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
3、当程序被停住时,可以检查此时你的程序中所发生的事。
4、动态的改变你程序的执行环境。
从上面看来,GDB和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。让我们一一看来。
一个调试示例:
源程序:tst.c 1 #include <stdio.h> 2 3 int func(int n) 4 { 5 int sum=0,i; 6 for(i=0; i<n; i++) 7 { 8 sum+=i; 9 } 10 return sum; 11 } 12 13 14 main() 15 { 16 int i; 17 long result = 0; 18 for(i=1; i<=100; i++) 19 { 20 result += i; 21 } 22 23 printf("result[1-100] = %d /n", result ); 24 printf("result[1-250] = %d /n", func(250) ); 25 }
编译生成执行文件:(Linux下)
hchen/test> cc -g tst.c -o tst
使用GDB调试:
hchen/test> gdb tst <---------- 启动GDB GNU gdb 5.1.1 Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-SUSE-linux"... (gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。 1 #include <stdio.h> 2 3 int func(int n) 4 { 5 int sum=0,i; 6 for(i=0; i<n; i++) 7 { 8 sum+=i; 9 } 10 return sum; (gdb) <-------------------- 直接回车表示,重复上一次命令 11 } 12 13 14 main() 15 { 16 int i; 17 long result = 0; 18 for(i=1; i<=100; i++) 19 { 20 result += i; (gdb) break 16 <-------------------- 设置断点,在源程序第16行处。 Breakpoint 1 at 0x8048496: file tst.c, line 16. (gdb) break func <-------------------- 设置断点,在函数func()入口处。 Breakpoint 2 at 0x8048456: file tst.c, line 5. (gdb) info break <-------------------- 查看断点信息。 Num Type Disp Enb Address What 1 breakpoint keep y 0x08048496 in main at tst.c:16 2 breakpoint keep y 0x08048456 in func at tst.c:5 (gdb) r <--------------------- 运行程序,run命令简写 Starting program: /home/hchen/test/tst Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。 17 long result = 0; (gdb) n <--------------------- 单条语句执行,next命令简写。 18 for(i=1; i<=100; i++) (gdb) n 20 result += i; (gdb) n 18 for(i=1; i<=100; i++) (gdb) n 20 result += i; (gdb) c <--------------------- 继续运行程序,continue命令简写。 Continuing. result[1-100] = 5050 <----------程序输出。 Breakpoint 2, func (n=250) at tst.c:5 5 int sum=0,i; (gdb) n 6 for(i=1; i<=n; i++) (gdb) p i <--------------------- 打印变量i的值,print命令简写。 $1 = 134513808 (gdb) n 8 sum+=i; (gdb) n 6 for(i=1; i<=n; i++) (gdb) p sum $2 = 1 (gdb) n 8 sum+=i; (gdb) p i $3 = 2 (gdb) n 6 for(i=1; i<=n; i++) (gdb) p sum $4 = 3 (gdb) bt <--------------------- 查看函数堆栈。 #0 func (n=250) at tst.c:5 #1 0x080484e4 in main () at tst.c:24 #2 0x400409ed in __libc_start_main () from /lib/libc.so.6 (gdb) finish <--------------------- 退出函数。 Run till exit from #0 func (n=250) at tst.c:5 0x080484e4 in main () at tst.c:24 24 printf("result[1-250] = %d /n", func(250) ); Value returned is $6 = 31375 (gdb) c <--------------------- 继续运行。 Continuing. result[1-250] = 31375 <----------程序输出。 Program exited with code 027. <--------程序退出,调试结束。 (gdb) q <--------------------- 退出gdb。 hchen/test>
好了,有了以上的感性认识,还是让我们来系统地认识一下gdb吧。
基本gdb命令:
GDB常用命令 格式 含义 简写 list List [开始,结束] 列出文件的代码清单 l prit Print 变量名 打印变量内容 p break Break [行号或函数名] 设置断点 b continue Continue [开始,结束] 继续运行 c info Info 变量名 列出信息 i next Next 下一行 n step Step 进入函数(步入) S display Display 变量名 显示参数 file File 文件名(可以是绝对路径和相对路径) 加载文件 run Run args 运行程序 r
5.4GDB实战
下面是一个使用了上述命令的实战例子:
[root@www.linuxidc.com bufbomb]# gdb bufbomb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-RedHat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /root/Temp/bufbomb/bufbomb...done. (gdb) b getbuf Breakpoint 1 at 0x8048ad6 (gdb) run -t cdai Starting program: /root/Temp/bufbomb/bufbomb -t cdai Team: cdai Cookie: 0x5e5ee04e Breakpoint 1, 0x08048ad6 in getbuf () Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.149.el6_6.4.i686 (gdb) bt #0 0x08048ad6 in getbuf () #1 0x08048db2 in test () #2 0x08049085 in launch () #3 0x08049257 in main () (gdb) info frame 0 Stack frame at 0xffffb540: eip = 0x8048ad6 in getbuf; saved eip 0x8048db2 called by frame at 0xffffb560 Arglist at 0xffffb538, args: Locals at 0xffffb538, Previous frame's sp is 0xffffb540 Saved registers: ebp at 0xffffb538, eip at 0xffffb53c (gdb) info registers eax 0xc 12 ecx 0xffffb548 -19128 edx 0xc8c340 13157184 ebx 0x0 0 esp 0xffffb510 0xffffb510 ebp 0xffffb538 0xffffb538 esi 0x804b018 134524952 edi 0xffffffff -1 eip 0x8048ad6 0x8048ad6 <getbuf+6> eflags 0x282 [ SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x63 99 (gdb) x/10x $sp 0xffffb510: 0xf7ffc6b0 0x00000001 0x00000001 0xffffb564 0xffffb520: 0x08048448 0x0804a12c 0xffffb548 0x00c8aff4 0xffffb530: 0x0804b018 0xffffffff (gdb) si 0x08048ad9 in getbuf () (gdb) si 0x08048adc in getbuf () (gdb) si 0x080489c0 in Gets () (gdb) n Single stepping until exit from function Gets, which has no line number information. Type string:123 0x08048ae1 in getbuf () (gdb) si 0x08048ae2 in getbuf () (gdb) c Continuing. Dud: getbuf returned 0x1 Better luck next time Program exited normally. (gdb) quit
逆向调试
GDB 7.0后加入了Reversal Debugging功能。具体来说,比如我在getbuf()和main()上设置了断点,当启动程序时会停在main()函数的断点上。此时敲入record后continue到下一断点getbuf(),GDB就会记录从main()到getbuf()的运行时信息。现在用rn就可以逆向地从getbuf()调试到main()。就像《X战警:逆转未来》里一样,挺神奇吧!
这种方式适合从bug处反向去找引起bug的代码,实用性因情况而异。当然,它也是有局限性的。像程序假如有I/O输出等外部条件改变时,GDB是没法“逆转”的。
[root@www.linuxidc.com bufbomb]# gdb bufbomb GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6) Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /root/Temp/bufbomb/bufbomb...done. (gdb) b getbuf Breakpoint 1 at 0x8048ad6 (gdb) b main Breakpoint 2 at 0x80490c6 (gdb) run -t cdai The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/Temp/bufbomb/bufbomb -t cdai Breakpoint 2, 0x080490c6 in main () (gdb) record (gdb) c Continuing. Team: cdai Cookie: 0x5e5ee04e Breakpoint 1, 0x08048ad6 in getbuf () (gdb) rn Single stepping until exit from function getbuf, which has no line number information. 0x08048dad in test () (gdb) rn Single stepping until exit from function test, which has no line number information. 0x08049080 in launch () (gdb) rn Single stepping until exit from function launch, which has no line number information. 0x08049252 in main ()
VSCode+GDB+Qemu调试ARM64 linux内核
linux kernel是一个非常复杂的系统,初学者会很难入门。如果有一个方便的调试环境,学习效率至少能有5-10倍的提升。
为了学习linux内核,通常有这两个需要:
- 可以摆脱硬件,方便的编译和运行linux
- 可以使用图形化的工具来调试linux
笔者使用VSCode+GDB+Qemu完成了这两个需求:
- qemu作为虚拟机,用来启动linux。
- VSCode+GDB作为调试工具,用来图形化地DEBUG。
最终效果大致如下:
qemu运行界面:
vscode调试界面:
下面将一步一步介绍如何搭建上述环境。本文所有操作都在Vmware Ubuntu16虚拟机上进行。
安装编译工具链
由于Ubuntu是X86架构,为了编译arm64的文件,需要安装交叉编译工具链
sudo apt-get install gcc-aarch64-linux-gnu sudo apt-get install libncurses5-dev build-essential git bison flex libssl-dev
制作根文件系统
linux的启动需要配合根文件系统,这里我们利用busybox来制作一个简单的根文件系统
编译busybox
wget https://busybox.net/downloads/busybox-1.33.1.tar.bz2 tar -xjf busybox-1.33.1.tar.bz2 cd busybox-1.33.1
打开静态库编译选项
make menuconfig Settings ---> [*] Build static binary (no shared libs)
指定编译工具
export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu-
编译
make make install
编译完成,在busybox目录下生成_install目录
定制文件系统
为了init进程能正常启动, 需要再额外进行一些配置
根目录添加etc、dev和lib目录
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17] $ mkdir etc dev lib # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install [1:02:17] $ ls bin dev etc lib linuxrc sbin usr
在etc分别创建文件:
# bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:13] $ cat profile #!/bin/sh export HOSTNAME=bryant export USER=root export HOME=/home export PS1="[$USER@$HOSTNAME \W]\# " PATH=/bin:/sbin:/usr/bin:/usr/sbin LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH export PATH LD_LIBRARY_PATH # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:16] $ cat inittab ::sysinit:/etc/init.d/rcS ::respawn:-/bin/sh ::askfirst:-/bin/sh ::ctrlaltdel:/bin/umount -a -r # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:19] $ cat fstab #device mount-point type options dump fsck order proc /proc proc defaults 0 0 tmpfs /tmp tmpfs defaults 0 0 sysfs /sys sysfs defaults 0 0 tmpfs /dev tmpfs defaults 0 0 debugfs /sys/kernel/debug debugfs defaults 0 0 kmod_mount /mnt 9p trans=virtio 0 0 # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:26] $ ls init.d rcS # bryant @ ubuntu in ~/Downloads/busybox-1.33.1/_install/etc [1:06:30] $ cat init.d/rcS mkdir -p /sys mkdir -p /tmp mkdir -p /proc mkdir -p /mnt /bin/mount -a mkdir -p /dev/pts mount -t devpts devpts /dev/pts echo /sbin/mdev > /proc/sys/kernel/hotplug mdev -s