研究C语言的hello world输出
- [环境准备 gcc make strace](#环境准备 gcc make strace)
- [C 代码](#C 代码)
- [GCC 编译的4个阶段 预处理|编译|汇编|链接](#GCC 编译的4个阶段 预处理|编译|汇编|链接)
- [使用 make 编译](#使用 make 编译)
- 查看程序运行中的系统调用
环境准备 gcc make strace
bash
$ gcc --version
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
bash
$ make --version
GNU Make 4.3
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2020 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.
bash
$ strace --version
strace -- version 6.8
Copyright (c) 1991-2024 The strace developers <https://strace.io>.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Optional features enabled: stack-trace=libunwind stack-demangle m32-mpers mx32-mpers
C 代码
c
#include <stdio.h>
int main(void)
{
printf("hello world\n");
return 0;
}
GCC 编译的4个阶段 预处理|编译|汇编|链接
预处理
输入: .c 源文件
输出: .i 文件
bash
$ gcc -E hello.c -o hello.i
-E : 仅执行预处理,不进行编译,汇编,链接
-o : 指定输出文件名
- 递归展开头文件 stdio.h等
- 替换宏定义
- 处理条件编译
- 删除所有注释
bash
$ file hello.c
hello.c: C source, ASCII text
$ file hello.i
hello.i: C source, ASCII text
编译
输入: .i 文件 (或者 .c文件)
输出: .s 汇编文件
-S : 仅执行预处理和汇编,不进行汇编和链接
1.此法分析: 将源代码拆分为记号(token)
2.语法分析:构建语法树, 检查语法错误
3.语义分析:检查类型匹配,变量作用域等
4.中间代码生成与优化:生成与机器无关的中间表示
5.目标汇编代码生成
bash
$ gcc -S hello.i -o hello.s
bash
$ file hello.s
hello.s: assembler source, ASCII text
汇编
输入:.s 汇编文件 (或者直接输入.c .s文件)
输出:.o 目标文件(二进制格式,如ELF)
-c : 执行预处理|编译|汇编,但不进行链接
bash
$ gcc -c hello.s -o hello.o
``
```bash
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
链接
输入:.o 文件+库文件(libc.a libc.so)
输出:.out 可执行文件 或共享库
bash
$ gcc hello.o -o hello
bash
$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=eacfacba3c3778df9ac2474ebe9b79dc0cabd865, for GNU/Linux 3.2.0, not stripped
使用 make 编译
bash
$ rm hello
$ make hello
cc hello.o -o hello
使用 ldd(List Dynamic Dependencies) 查看链接了那些库
bash
$ ldd hello
linux-vdso.so.1 (0x00007b5cc46b4000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007b5cc4400000)
/lib64/ld-linux-x86-64.so.2 (0x00007b5cc46b6000)
查看程序运行中的系统调用
bash
$ strace -t -f -o strace_hello.log ./hello
hello world
bash
$ cat strace_hello.log
10162 22:39:37 execve("./hello", ["./hello"], 0x7ffe0f84c8b0 /* 67 vars */) = 0
10162 22:39:37 brk(NULL) = 0x5e2c5ac62000
10162 22:39:37 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ab26b3cd000
10162 22:39:37 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
10162 22:39:37 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
10162 22:39:37 fstat(3, {st_mode=S_IFREG|0644, st_size=60167, ...}) = 0
10162 22:39:37 mmap(NULL, 60167, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ab26b3be000
10162 22:39:37 close(3) = 0
10162 22:39:37 openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
10162 22:39:37 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\220\243\2\0\0\0\0\0"..., 832) = 832
10162 22:39:37 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
10162 22:39:37 fstat(3, {st_mode=S_IFREG|0755, st_size=2125328, ...}) = 0
10162 22:39:37 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
10162 22:39:37 mmap(NULL, 2170256, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ab26b000000
10162 22:39:37 mmap(0x7ab26b028000, 1605632, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7ab26b028000
10162 22:39:37 mmap(0x7ab26b1b0000, 323584, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b0000) = 0x7ab26b1b0000
10162 22:39:37 mmap(0x7ab26b1ff000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1fe000) = 0x7ab26b1ff000
10162 22:39:37 mmap(0x7ab26b205000, 52624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ab26b205000
10162 22:39:37 close(3) = 0
10162 22:39:37 mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ab26b3bb000
10162 22:39:37 arch_prctl(ARCH_SET_FS, 0x7ab26b3bb740) = 0
10162 22:39:37 set_tid_address(0x7ab26b3bba10) = 10162
10162 22:39:37 set_robust_list(0x7ab26b3bba20, 24) = 0
10162 22:39:37 rseq(0x7ab26b3bc060, 0x20, 0, 0x53053053) = 0
10162 22:39:37 mprotect(0x7ab26b1ff000, 16384, PROT_READ) = 0
10162 22:39:37 mprotect(0x5e2c2ad4b000, 4096, PROT_READ) = 0
10162 22:39:37 mprotect(0x7ab26b40d000, 8192, PROT_READ) = 0
10162 22:39:37 prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
10162 22:39:37 munmap(0x7ab26b3be000, 60167) = 0
10162 22:39:37 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x1), ...}) = 0
10162 22:39:37 getrandom("\x5c\x64\x6b\x57\xf2\xe3\x94\x6a", 8, GRND_NONBLOCK) = 8
10162 22:39:37 brk(NULL) = 0x5e2c5ac62000
10162 22:39:37 brk(0x5e2c5ac83000) = 0x5e2c5ac83000
10162 22:39:37 write(1, "hello world\n", 12) = 12
10162 22:39:37 exit_group(0) = ?
10162 22:39:37 +++ exited with 0 +++
第一列:进程或者线程ID
第二列:时间戳 -tt 会显示微秒
第三列:系统调用名称以及参数
"="号之后 : 系统调用的返回值
text
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
和之前 ldd 查看的一致
主要运行的系统调用
text
10162 22:39:37 write(1, "hello world\n", 12) = 12
提取第3列的系统调用
bash
$ awk '{print $3}' strace_hello.log | sed 's/(.*//' > syscalls_c
$ cat syscalls_c
execve
brk
mmap
access
openat
fstat
mmap
close
openat
read
pread64
fstat
pread64
mmap
mmap
mmap
mmap
mmap
close
mmap
arch_prctl
set_tid_address
set_robust_list
rseq
mprotect
mprotect
mprotect
prlimit64
munmap
fstat
getrandom
brk
brk
write
exit_group
+++
python 代码
python
print("hello world")
查看 python 的运行的系统调用
bash
$ strace -t -f -o strace_hello_py.log python3 hello.py
hello world
python 输出的系统调用非常多
bash
$ wc -l strace_hello_py.log
392 strace_hello_py.log
而C语言的非常少
bash
$ wc -l strace_hello.log
36 strace_hello.log
python 输入hello world 也执行了 write 系统调用
text
$ cat strace_hello_py.log | grep write
11529 23:06:07 write(1, "hello world\n", 12) = 12
发现和之前C语言输出的系统调用一致的
text
进程 11529 在 23:06:07 时刻,向标准输出(文件描述符1)写入12字节的内容(hello world + 换行),内核成功完成写入并返回12,表示全部写入成功。这是程序正常输出信息的典型系统调用
参数1 1 文件描述符。在Unix/Linux中,0=标准输入,1=标准输出,2=标准错误。此处 1 表示写入标准输出(通常是终端或控制台)。
参数2 "hello world\n" 要写入的数据内容,这里是一个字符串,包含 hello world 和一个换行符 \n。
参数3 12 要写入的字节数。字符串 "hello world\n" 长度为12字符(11个字母+1个空格+1个换行)。
返回值 12 write 系统调用实际写入的字节数。成功时返回等于请求写入的字节数(12),表示全部写入成功。如果返回 -1 则表示出错,并设置 errno。