代码3:构造最小的HelloWorld程序

Intro:

  • 绪论2:应用视角的操作系统。
  • 目的:通过精简程序,了解整个编译过程。

一、GCC 编译 hello.c 的全过程

.c (源代码) -> .i (预编译源代码) -gcc-> .S (汇编代码) -as-> .o -ld-> a.out

(一)GCC 编译

hello.c 中只有一行 printf("hello,world\n"); 指令(return 由编译器处理,代码中便没有指定)。于是根据上面的编译全流程,使用:

gcc -E hello.c | less # 查看预编译结果
gcc -c hello.c # 生成 hello.o
ld hello.o # 强行链接
objdump -d a.out | less # 查看可执行文件的内容

因为我们的代码中使用了 printf 这样一个外部依赖函数,所以 LD 会报错。如果去掉 printf 这一行则报错消失,并使用 ld hello.o -e main 指定程序的 entrypoint 来消除警告。

(二)GDB 调试

执行 ./a.out 会报错,接下来使用 GDB 单步调试。

gdb a.out # 进入调试命令行
(gdb) starti # 从第一条指令开始执行
(gdb) layout asm # 将汇编语言的 text ui 显示在整个界面上方
(gdb) si # stepi 单步执行

# 一些辅助命令
(gdb) info registers # 查看寄存器中的值
(gdb) p $rsp # 打印 rsp 寄存器,由于它是栈指针,所以结果是内存地址
(gdb) x $rsp # rsp 指针指向的值,相当于 *rsp 解引用

最后我们发现:返回指令导致问题。

二、使用汇编“强行”精简代码

上面尝试精简 C 程序的方法遇到了阻碍,于是课程从汇编角度入手:通过手动执行“系统调用”的方式,结束当前程序、而非异常退出。

movq $SYS_exit,  %rax   # exit(
movq $1,         %rdi   #   status=1
syscall                 # );

上面的程序把 “系统调用” 的参数放到寄存器中,然后执行 syscall,让操作系统接管程序。在此基础上,课程实现了最小的 Hello World 程序:minimal.S

gcc -c minimal.S # 得到 minimal.o
ld minimal.o # 链接
objdump -d a.out | wc -l # 查看行数
strace a.out # 追踪程序的系统调用

得到的可执行程序无论是大小还是行数,都远小于 C 代码版本。

除了使用 objdump 外,像vim, cat, xxd 都可以直接 “查看” 可执行文件:

  • vim 打开后,使用 :%!xxd 将编辑区中的文本传递到 xxd 中。
  • 使用 xxd 可以看到文件中以 \x7f, ELF 开头的标志。
  • vscode 中也有 binary editor 插件。
posted @ 2024-07-03 17:30  7hu95b  阅读(41)  评论(0编辑  收藏  举报