制作一个最小的Hello World程序
环境:
1、WSL for Ubuntu(Linux系统即可)2、build-essential工具
正常的一个Hello World程序。
#include<stdio.h>
int main(){
printf("Hello World!");
reutrn 0;
}
使用 gcc --verbose demo.c
会看到一堆的编译链接信息。
如果不想依赖动态链接库编译,而静态编译使用命令 gcc -static demo.c
,ls -l a.out
发现 a.out 有900多KB(-static 会复制libc),不符合最小的HelloWorld程序。然后使用 objdump -d a.out | less
查看汇编代码,发现很长的一堆汇编代码。
如果compile only gcc -c demo.c && objdump -d demo.o
发现汇编代码很短,然后 ld demo.o
出现警告提示不能找到入口符号 _strat 和一个错误不能找到puts的定义。接下来用hack的手法来躲过去:
#include<stdio.h>
#include<unistd.h>
void _start(){
// printf("Hello World!\n");
}
执行命令 gcc -c demo.c && objdump -d demo.o && ld demo.o
完美避过。通过 size a.out
发现只有90多B,基本符合最小的程序。尝试运行 a.out
出现 Segmentation fault(小尝试:如果在_start里面加入死循环 while(1); 就不会出现。)
为什么呢?接下来就是通过Command gdb a.out
以状态机视角观察程序的状态转移(程序状态:内存Memory和寄存器register)
(gdb) starti #执行到第一条汇编语句
(gdb) layout asm #查看汇编代码调试界面
(gdb) si #汇编级调试下一步
发现下图这条语句执行后不正常退出。
报错信息:
(gdb) si
0x0000000000000001 in ?? ()
问题出在:进程的初始状态不能返回,然后程序异常退出。
有办法让状态机 “停下来” 吗?
- 纯 “计算” 的状态机:不行
- 要么死循环,要么 undefined behavior
解决办法:syscall
#include <sys/syscall.h>
int main() {
syscall(SYS_exit, 42);
}
- 调试代码:syscall 的实现在哪里?
- 坏消息:在 libc 里,不方便直接链接
- 好消息:代码很短,而且似乎看懂了
最小Hello World的代码诞生 minimal.S (.s是纯汇编文件,.S则是预处理+汇编)
#include <sys/syscall.h>
.globl _start
_start:
movq $SYS_write, %rax # write(
movq $1, %rdi # fd=1,
movq $st, %rsi # buf=st,
movq $(ed - st), %rdx # count=ed-st
syscall # );
movq $SYS_exit, %rax # exit(
movq $1, %rdi # status=1
syscall # );
st:
.ascii "\033[01;31mHello, OS World\033[0m\n"
ed:
编译链接运行它 gcc -c minimal.S && ld minimal.o && ./a.out
输出:Hello, OS World
size a.out
发现大小只有74B。
代码阅读可以参考syscall的manual man syscall
,也可以用gdb调试它。
Hint
gcc如果不指定可执行文件名称统一输出为a.out
.i
文件是进行预编译之后生成的文件(可用command gcc -E xxx.c -o xxx.i
生成)。
.o
文件又称object文件,是可执行文件(多个o文件链接成一个程序),.so 是shared object相当于windows的dll。
.s
是纯汇编文件,.S
则是预处理+汇编
ld
(Link eDitor)命令是二进制工具集 GNU Binutils 的一员,是 GNU 链接器,用于将目标文件与库链接为可执行文件或库文件。(example:ld demo.o
#链接demo.o到a.out上)
参考
南京大学2022操作系统-P2 中 最小的HelloWrold