Skip to content
Go back

minimal helloworld

7 min read

函数的调用

计算机程序其实就是状态机,各个栈帧和全局变量构成状态,执行pc所指语句进行状态转换,调用函数时则把返回地址压入栈然后创建新的栈帧,汉诺塔的非递归实现即是使用显式栈帧管理来模拟递归调用栈。

编译器的优化

只要编译后的程序能够在任何输入下,得到与执行原程序语言等价的结果就行,于是有着很大优化空间。

  • -O0 零级优化,已经有在做一些优化了,直接计算出常量表达式。

在今天并不需要学过所有结构,只要你学过任意一种体系结构或指令集架构,知道大概会有哪些指令,有些做访存、有些做算术、有些做调用,当你遇到一种你没学过的结构,直接让ai用你学过的来类比解释就可以了。

如何写一个最小的hello wrold

如果用objdump -d 查看我们通常第一次接触时写出的helloworld,在汇编代码中你会发现很多除了 printf 以外的指令。

helloworld:     file format elf64-x86-64


Disassembly of section .init:

0000000000001000 <_init>:
    1000:       f3 0f 1e fa             endbr64 
    1004:       48 83 ec 08             sub    $0x8,%rsp
    1008:       48 8b 05 d9 2f 00 00    mov    0x2fd9(%rip),%rax        # 3fe8 <__gmon_start__@Base>
    100f:       48 85 c0                test   %rax,%rax
    1012:       74 02                   je     1016 <_init+0x16>
    1014:       ff d0                   call   *%rax
    1016:       48 83 c4 08             add    $0x8,%rsp
    101a:       c3                      ret    

Disassembly of section .plt:

0000000000001020 <.plt>:
    1020:       ff 35 9a 2f 00 00       push   0x2f9a(%rip)        # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x8>
    1026:       f2 ff 25 9b 2f 00 00    bnd jmp *0x2f9b(%rip)        # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x10>
    102d:       0f 1f 00                nopl   (%rax)
    1030:       f3 0f 1e fa             endbr64 
    1034:       68 00 00 00 00          push   $0x0
    1039:       f2 e9 e1 ff ff ff       bnd jmp 1020 <_init+0x20>
    103f:       90                      nop

Disassembly of section .plt.got:

0000000000001040 <__cxa_finalize@plt>:
    1040:       f3 0f 1e fa             endbr64 
    1044:       f2 ff 25 ad 2f 00 00    bnd jmp *0x2fad(%rip)        # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
    104b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

Disassembly of section .plt.sec:

0000000000001050 <printf@plt>:
    1050:       f3 0f 1e fa             endbr64 
    1054:       f2 ff 25 75 2f 00 00    bnd jmp *0x2f75(%rip)        # 3fd0 <printf@GLIBC_2.2.5>
    105b:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

Disassembly of section .text:

0000000000001060 <_start>:
    1060:       f3 0f 1e fa             endbr64 
    1064:       31 ed                   xor    %ebp,%ebp
    1066:       49 89 d1                mov    %rdx,%r9
    1069:       5e                      pop    %rsi
    106a:       48 89 e2                mov    %rsp,%rdx
    106d:       48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    1071:       50                      push   %rax
    1072:       54                      push   %rsp
    1073:       45 31 c0                xor    %r8d,%r8d
    1076:       31 c9                   xor    %ecx,%ecx
    1078:       48 8d 3d ca 00 00 00    lea    0xca(%rip),%rdi        # 1149 <main>
    107f:       ff 15 53 2f 00 00       call   *0x2f53(%rip)        # 3fd8 <__libc_start_main@GLIBC_2.34>
    1085:       f4                      hlt    
    1086:       66 2e 0f 1f 84 00 00    cs nopw 0x0(%rax,%rax,1)
    108d:       00 00 00 

0000000000001090 <deregister_tm_clones>:
    1090:       48 8d 3d 79 2f 00 00    lea    0x2f79(%rip),%rdi        # 4010 <__TMC_END__>
    1097:       48 8d 05 72 2f 00 00    lea    0x2f72(%rip),%rax        # 4010 <__TMC_END__>
    109e:       48 39 f8                cmp    %rdi,%rax
    10a1:       74 15                   je     10b8 <deregister_tm_clones+0x28>
    10a3:       48 8b 05 36 2f 00 00    mov    0x2f36(%rip),%rax        # 3fe0 <_ITM_deregisterTMCloneTable@Base>
    10aa:       48 85 c0                test   %rax,%rax
    10ad:       74 09                   je     10b8 <deregister_tm_clones+0x28>
    10af:       ff e0                   jmp    *%rax
    10b1:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    10b8:       c3                      ret    
    10b9:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

00000000000010c0 <register_tm_clones>:
    10c0:       48 8d 3d 49 2f 00 00    lea    0x2f49(%rip),%rdi        # 4010 <__TMC_END__>
    10c7:       48 8d 35 42 2f 00 00    lea    0x2f42(%rip),%rsi        # 4010 <__TMC_END__>
    10ce:       48 29 fe                sub    %rdi,%rsi
    10d1:       48 89 f0                mov    %rsi,%rax
    10d4:       48 c1 ee 3f             shr    $0x3f,%rsi
    10d8:       48 c1 f8 03             sar    $0x3,%rax
    10dc:       48 01 c6                add    %rax,%rsi
    10df:       48 d1 fe                sar    %rsi
    10e2:       74 14                   je     10f8 <register_tm_clones+0x38>
    10e4:       48 8b 05 05 2f 00 00    mov    0x2f05(%rip),%rax        # 3ff0 <_ITM_registerTMCloneTable@Base>
    10eb:       48 85 c0                test   %rax,%rax
    10ee:       74 08                   je     10f8 <register_tm_clones+0x38>
    10f0:       ff e0                   jmp    *%rax
    10f2:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)
    10f8:       c3                      ret    
    10f9:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

0000000000001100 <__do_global_dtors_aux>:
    1100:       f3 0f 1e fa             endbr64 
    1104:       80 3d 05 2f 00 00 00    cmpb   $0x0,0x2f05(%rip)        # 4010 <__TMC_END__>
    110b:       75 2b                   jne    1138 <__do_global_dtors_aux+0x38>
    110d:       55                      push   %rbp
    110e:       48 83 3d e2 2e 00 00    cmpq   $0x0,0x2ee2(%rip)        # 3ff8 <__cxa_finalize@GLIBC_2.2.5>
    1115:       00 
    1116:       48 89 e5                mov    %rsp,%rbp
    1119:       74 0c                   je     1127 <__do_global_dtors_aux+0x27>
    111b:       48 8b 3d e6 2e 00 00    mov    0x2ee6(%rip),%rdi        # 4008 <__dso_handle>
    1122:       e8 19 ff ff ff          call   1040 <__cxa_finalize@plt>
    1127:       e8 64 ff ff ff          call   1090 <deregister_tm_clones>
    112c:       c6 05 dd 2e 00 00 01    movb   $0x1,0x2edd(%rip)        # 4010 <__TMC_END__>
    1133:       5d                      pop    %rbp
    1134:       c3                      ret    
    1135:       0f 1f 00                nopl   (%rax)
    1138:       c3                      ret    
    1139:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)

0000000000001140 <frame_dummy>:
    1140:       f3 0f 1e fa             endbr64 
    1144:       e9 77 ff ff ff          jmp    10c0 <register_tm_clones>

0000000000001149 <main>:
    1149:       f3 0f 1e fa             endbr64 
    114d:       55                      push   %rbp
    114e:       48 89 e5                mov    %rsp,%rbp
    1151:       48 8d 05 ac 0e 00 00    lea    0xeac(%rip),%rax        # 2004 <_IO_stdin_used+0x4>
    1158:       48 89 c7                mov    %rax,%rdi
    115b:       b8 00 00 00 00          mov    $0x0,%eax
    1160:       e8 eb fe ff ff          call   1050 <printf@plt>
    1165:       b8 00 00 00 00          mov    $0x0,%eax
    116a:       5d                      pop    %rbp
    116b:       c3                      ret    

Disassembly of section .fini:

000000000000116c <_fini>:
    116c:       f3 0f 1e fa             endbr64 
    1170:       48 83 ec 08             sub    $0x8,%rsp
    1174:       48 83 c4 08             add    $0x8,%rsp
    1178:       c3                      ret    

编译器或者说整个工具链处理程序时其实不是从main函数开始的,而是从 _start 开始跳转到一个pc初始化的代码,然后去调用 main 函数而main函数最终会返回,执行系统调用来清理掉这个进程以真正结束程序。因为c程序自己是不能结束程序执行的,操作系统才行。 _start 是在链接阶段由 gcc 加上。

系统调用指令syscall (x86-64), ecall (risc-v), svc (aarch64) 是区别于其他所有指令的特殊指令,执行后由操作系统接管程序,也是唯一打破程序状态边界与其他程序、设备交互的方式。而如果你想要正确地执行某个系统调用,你就要先把正确的值放到正确的寄存器里。

一个最小的helloworld

#include <sys/syscall.h>

    .text
    .global _start

_start:
    # write(1, msg, msg_end - msg)
    mov     $SYS_write, %rax        # rax = syscall number: write
    mov     $1, %rdi                # rdi = fd: 1 stdout
    lea     msg(%rip), %rsi         # rsi = buffer address
    mov     $(msg_end - msg), %rdx  # rdx = length
    syscall

    # exit(0)
    mov     $SYS_exit, %rax         # rax = syscall number: exit
    mov     $0, %rdi                # rdi = exit code
    syscall

    .section .rodata
msg:
    .ascii "HelloWorld\n"
msg_end:

能看到的幕后程序

Linux 系统通常默认的 GNU Coreutils,like ls,cp,mv,cat,sed等

不能看到的幕后程序

守护进程 deamon:在后台长期运行的进程

比如systemd(init系统和服务管理器)、cron、sshd、firewalld等

建立工具体系思维

使用 System call trace (strace) 去观察程序是如何与操作系统交互的,当然现在我们可以直接使用ai来完成

strace xedit时我们可以发现程序在不断的发消息和接收消息,所以应用程序做的事就是计算和使用操作系统api,而操作系统的职责正是提供令应用程序舒适的抽象 (对象 + API)。