测试平台:
─➤ uname -a
Linux aliyunserevr 5.4.0-99-generic #112-Ubuntu SMP Thu Feb 3 13:50:55 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
使用汇编输出 hello world
heelo.asm
;; hello.asm
;; nasm -f elf hello.asm; will output hello.o
;; ld -s -o hello hello.o
;; section, same to segment
segment .data ; 数据段声明, 下列代码将放在数据段中
msg db "Hello, world!", 0xA ; 要输出的字符串
len equ $ - msg ; 字串长度
section .text ; 代码段声明,下列代码将放入代码段中
global _start ; 指定入口函数,global修饰是为了让外部可以引用_start
_start: ; 在屏幕上显示一个字符串
mov edx, len ; 参数三:字符串长度
mov ecx, msg ; 参数二:要显示的字符串
mov ebx, 1 ; 参数一:文件描述符(stdout)
mov eax, 4 ; 系统调用号(sys_write)
int 0x80 ; 调用内核功能
; 退出程序
mov ebx, 0 ; 参数一:退出代码
mov eax, 1 ; 系统调用号(sys_exit)
int 0x80 ; 调用内核功能
编译
nasm -f elf64 hello.asm
ld -s -o hello hello.o
运行
strace ./hello
输出如下:
execve("./hello", ["./hello"], 0x7ffc083e5060 /* 36 vars */) = 0
strace: [ Process PID=4717 runs in 32 bit mode. ]
write(1, "Hello, world!\n", 14Hello, world!
) = 14
exit(0) = ?
+++ exited with 0 +++
一共四个系统调用
使用 c 语言输出 hello world
c_hello.c
#include <stdio.h>
int main(void)
{
printf("hello world\n");
return 0;
}
编译
gcc -o c_hello c_hello.c
运行
strace ./c_hello
输出如下:
execve("./c_hello", ["./c_hello"], 0x7fff69f3eaa0 /* 36 vars */) = 0
brk(NULL) = 0x56185f27a000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc138ff1c0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=35708, ...}) = 0
mmap(NULL, 35708, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f959c1c0000
close(3) = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"..., 832) = 832
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
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f959c1be000
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
pread64(3, "\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0", 32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\t\233\222%\274\260\320\31\331\326\10\204\276X>\263"..., 68, 880) = 68
mmap(NULL, 2036952, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f959bfcc000
mprotect(0x7f959bff1000, 1847296, PROT_NONE) = 0
mmap(0x7f959bff1000, 1540096, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f959bff1000
mmap(0x7f959c169000, 303104, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) = 0x7f959c169000
mmap(0x7f959c1b4000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f959c1b4000
mmap(0x7f959c1ba000, 13528, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f959c1ba000
close(3) = 0
arch_prctl(ARCH_SET_FS, 0x7f959c1bf540) = 0
mprotect(0x7f959c1b4000, 12288, PROT_READ) = 0
mprotect(0x56185f1f1000, 4096, PROT_READ) = 0
mprotect(0x7f959c1f6000, 4096, PROT_READ) = 0
munmap(0x7f959c1c0000, 35708) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x3), ...}) = 0
brk(NULL) = 0x56185f27a000
brk(0x56185f29b000) = 0x56185f29b000
write(1, "hello world\n", 12hello world
) = 12
exit_group(0) = ?
+++ exited with 0 +++
一共 n 个系统调用
因为 C 语言在进入到 main 函数之前就已经做了很多事了,譬如初始化一些全局变量,为多线程的安全做一些准备工作等等,在你的 main 结束后它还会做一些释放类的工作。
举个例子,在标准 C 库里面用于保存函数错误码的 errno,这是个全局的变量,编译器并没有智能到检测你所有的代码,发现你没有使用 errno,然后把 errno 定义优化掉的能力。所以它按默认初始化了 errno,但你很可能根本没用到,这部分的代码对你来说就没用。
而你用汇编写代码的时候,这些都不会有,当然就简短的多。