只有170位元組,最小的64位Hello World程序這樣寫成

2021-01-09 澎湃新聞

機器之心轉載

作者:CJ Ting

最簡單的 C 語言 Hello World 程序,底層到底發生了什麼?如何編寫出最小的 64 位 Hello World 程序?

Hello World 應該是每一位程式設計師的啟蒙程序,出自於 Brian Kernighan 和 Dennis Ritchie 的一代經典著作 The C Programming Language。

// hello.c#include int main() { printf("hello, world\n"); return 0;}

這段代碼我想大家應該都太熟悉了,熟悉到可以默寫出來。雖然是非常簡單的代碼,但是如果細究起來,裡面卻隱含著很多細節:

#include和 #include "stdio.h" 有什麼區別?

stdio.h 文件在哪裡?裡面是什麼內容?

為什麼入口是 main 函數?可以寫一個程序入口不是 main 嗎?

main 的 int 返回值有什麼用?是誰在處理 main 的返回值?

printf 是誰實現的?如果不用 printf 可以做到在終端中列印字符嗎?

上面這些問題其實涉及到程序的編譯、連結和裝載,日常工作中也許大家並不會在意。

現代 IDE 在方便我們開發的同時,也將很多底層的細節隱藏了起來。往往寫完代碼以後,點擊「構建」就行了,至於構建在發生什麼,具體是怎麼構建的,很多人並不關心,甚至根本不知道從原始碼到可執行程序這中間經歷了什麼。

編譯、連結和裝載是一個巨大的話題,不是一篇博客可以覆蓋的。在這篇博客中,我想使用「文件尺寸」作為線索,來介紹從 C 原始碼到可執行程序這個過程中,所經歷的一系列過程。

Tip: 關於編譯、連結和裝載,這裡想推薦一本書《程式設計師的自我修養》。不得不說,這個名字起得非常不好,很有譁眾取寵的味道,但是書的內容是不錯的,值得一看。

我們先來編譯上面的程序:

$ gcc hello.c -o hello$ ./hellohello, world$ ll hello-rwxr-xr-x 1 root root 16712 Nov 24 10:45 hello

Tip: 後續所有的討論都是基於 64 位 CentOS7 作業系統。

我們會發現這個簡單的 hello 程序大小為 16K。在今天看來,16K 真的沒什麼,但是考慮到這個程序所做的事情,它真的需要 16K 嗎?

在 C 誕生的上個世紀 70 年代,PDP-11 的內存為 144K,如果一個 hello world 就要佔 16K,那顯然是不合理的,一定有辦法可以縮減體積。

Tip:

說起 C 語言,我想順帶提一下 UNIX。沒有 C 就沒有 UNIX 的成功,沒有 UNIX 的成功也就沒有 C 的今天。誕生於上個世紀 70 年代的 UNIX 不得不說是一項了不起的創造。

這裡推薦兩份關於 UNIX 的資料:

The UNIX Time-Sharing System 是1974 年由 Dennis Ritchie 和 Ken Thompson 聯合發表的介紹 UNIX 的論文。不要被「論文」二字所嚇到,實際上,這篇文章寫得非常通俗易懂,由 UNIX 的作者們向你娓娓道來 UNIX 的核心設計理念。

The UNIX Operating System 是一段視頻,看身著藍色時尚毛衣的 Kernighan 演示 UNIX 的特性,不得不說,Kernighan 簡直太帥了。

接下來我們來玩一個遊戲,目標是:在 CentOS7 64 位作業系統上,編寫一個體積最小的列印 hello world 的可執行程序。

Executable

我們先來看「可執行程序」這個概念。

什麼是可執行程序?按照字面意思來理解,那就是:可以執行的程序。

ELF

上面用 C 編寫的 hello 當然是可執行程序,毫無疑問。

實際上,我們可以說它是真正的「可執行」程序(區別於後文的腳本),或者說「原生」程序。

因為它裡面包含了可以直接用於 CPU 執行的機器代碼,它的執行無需藉助外部。

hello 的存儲格式叫做 ELF,全稱為 Executable and Linkable Format,看名稱可以知道,它既可以用於存儲目標文件,又可以用於存儲可執行文件。

ELF 本身並不難理解,/usr/include/elf.h 中含有 ELF 結構的詳細信息。難理解的是由 ELF 所掀開的底層世界,目標文件是什麼?和執行文件有什麼區別?連結在幹什麼?目標文件怎樣變成可執行文件等等等等。

Shebang

接下來我們來看另外一種形式的可執行程序——腳本。

$ cat > hello.sh <#!/bin/bashecho "hello, world"EOF$ chmod +x hello.sh$ ./helo.shhello, world

按照定義,因為這個腳本可以直接從命令行執行,所以它是可執行程序。

那麼 hello 和 hello.sh 的區別在哪裡?

可以發現 hello.sh 的第一行比較奇怪,這是一個叫做 Shebang 的東西 #!/bin/bash,這個東西表明當前文件需要 /bin/bash 程序來執行。

所以,hello 和 hello.sh 的區別就在於:一個可以直接執行不依賴於外部程序,而另一個需要依賴外部程序。

我曾經有一個誤解,認為 Shebang 是 Shell 在處理,當 Shell 執行腳本時,發現第一行是 Shebang,然後調用相應的程序來執行該腳本。

實際上並不是這樣,對 Shebang 的處理是內核在進行。當內核加載一個文件時,會首先讀取文件的前 128 個字節,根據這 128 個字節判斷文件的類型,然後調用相應的加載器來加載。

比如說,內核發現當前是一個 ELF 文件(ELF 文件前四個字節為固定值,稱為魔數),那麼就調用 ELF 加載器。

而內核發現當前文件含有 Shebang,那麼就會啟動 Shebang 指定的程序,將當前路徑作為第一個參數傳入。所以當我們執行 ./hello.sh 時,在內核中會被變為 /bin/bash ./hello.sh。

這裡其實有一個小問題,如果要腳本可以從命令行直接執行,那麼第一行必須是 Shebang。Shebang 的形式固定為 #! 開頭,對於使用 # 字符作為注釋的語言比如 Python, Ruby, Elixir 來說,這自然不是問題。但是對於 # 字符不是注釋字符的語言來說,這一行就是一個非法語句,必然帶來解釋錯誤。

比如 JavaScript,它就不使用 # 作為注釋,我們來寫一個帶 Shebang 的 JS 腳本看看會怎麼樣。

$ cat 並沒有出錯,所以這裡是怎麼回事?按道理來說第一行是非法的 JS 語句,解釋器應該要報錯才對。

如果把第一行的 Shebang 拷貝一份到第二行,會發現報了 SyntaxError,這才是符合預期的。所以必然是 Node 什麼地方對第一行的 Shebang 做了特別處理,否則不可能不報錯。

大家可以在 Node 的代碼裡面找一找,看看在什麼地方 ��

答案是什麼地方都沒有,或者說在最新的 Node 中,已經沒有地方在處理 Shebang 了。

在 Node v11 中,我們可以看到相應的代碼(https://github.com/nodejs/node/blob/v11.15.0/lib/internal/main/check_syntax.js#L50)。

stripShebang 函數很明顯,它的作用在於啟動 JS 解釋器的時候,將第一行的 Shebang 移除掉。

但是在 Node v12 以後,Node 更新了 JS 引擎 V8 到 7.4,V8 在這個版本中實現一個叫做 Hashbang grammar 的功能,也就是說,從此以後,V8 可以處理 Shebang 了,因此 Node 刪除了相關代碼。

因為 Shebang 是 V8 在處理了,所以我們在瀏覽器中也可以加載帶有 Shebang 的 JS 文件,不會有任何問題~

我們可以得出結論,支持作為腳本使用的語言,如果不使用 # 作為注釋字符,那麼必然要特別處理 Shebang,否則使用起來就太不方便了。

/usr/bin/env

上面的 test.js 文件中,不知道大家是否注意到,解釋器路徑寫的是 /usr/bin/env node。

這樣的寫法如果經常寫腳本,應該不陌生,我之前一直這樣用,但是沒有仔細去想過為什麼。

首先我們來看 /usr/bin/env 這個程序是什麼。

根據 man env 返回的信息:env - run a program in a modified environment.

env 的主要作用是修改程序運行的環境變量,比如說

$ export name=shell$ node> process.env.name'shell'$ env name=env node> process.env.name'env'

通過 env 我們修改了 node 運行時的環境變量。但是這個功能和我們為什麼要在 Shebang 中使用 env 有什麼關係?

在 Shebang 中使用 env 其實是因為另外一個原因,那就是 env 會在 PATH 中搜索程序並執行。

當我們執行 env abc 時,env 會在 PATH 中搜索 abc 然後執行,就和 Shell 一樣。

這就解釋了為什麼我們要在腳本中使用 /usr/bin/env node。對於想要給他人復用的腳本,我們並不清楚他人系統上 node 的路徑在哪裡,但是我們清楚的是,它一定在 PATH 中。

而同時,絕大部分系統上,env 程序的位置是固定的,那就是 /usr/bin/env。所以,通過使用 /usr/bin/env node,我們可以保證不管其他用戶將 node 安裝在何處,這個腳本都可以被執行。

binfmt_misc

前面我們提到過,內核對於文件的加載其實是有一套「多態」機制的,即根據不同的類型來選擇不同的加載器。

那麼這個過程我們可以自己定製嗎?

當然可以,內核中有一個加載器叫做 binfmt_misc,看名字可以知道,這個加載器用於處理各種各樣非標準的其他類型。

通過一套語法,我們可以告知 binfmt_misc 加載規則,實現自定義加載。

比如我們可以通過 binfmt_misc 實現直接運行 Go 文件。

# 運行 Go 文件的指令是 `go run`,不是一個獨立的程序# 所以,我們先要寫一個腳本包裝一下$ cat /proc/sys/fs/binfmt_misc/register# 現在我們就可以直接運行 Go 文件了$ cat << EOF > test.gopackage mainimport "fmt"func main() { fmt.Println("hello, world")}EOF$ chmod +x test.go$ ./test.gohello, world

Tiny Script

根據上面的知識,如果我們想要編寫一個體積最小的列印 hello world 的腳本,我們要在這兩方面著手:

解釋器路徑要儘量短;

腳本本身用於列印的代碼要儘量短。

解釋器的路徑很好處理,我們可以使用連結。

腳本本身的代碼要短,這就很考驗知識了,我一開始想到的是 Ruby,puts "hello, world" 算是非常短的代碼了,沒有一句廢話。但是後來 Google 才發現,還有更短的,那就是 PHP ��

PHP 中 列印 hello world 的代碼就是 hello, world,對的,你沒看錯,連引號都不用。

所以,最終我們的結果如下:

# 假設 php 在 /usr/local/bin/php$ cd /$ ln -s /usr/local/bin/php p$ cat 在腳本模式下,我們的成績是 18 個字節,使用的解釋器是 PHP。

其實在腳本模式下編寫最小的 hello world 沒有太大意義,因為我們完全可以自己寫一個輸出 hello world 的程序作為解釋器,然後腳本裡面只要 #!/x 就行了。

Tiny Native

上面的腳本只是拋磚引玉,接下來我們進入正題,怎樣編寫一個體積最小的列印 hello world 的原生可執行程序?

網上有很多關於這個話題的討論,但基本都是針對 x86 的。現如今 64 位機器早就普及了,所以我們這裡針對的是 64 位的 x64。

Tip: 64 位機器可以執行 32 位的程序,比如我們可以使用 gcc -m32 來編譯 32 位程序。但這只是一個後向兼容,並沒有充分利用 64 位機器的能力。

Step0

首先,我們使用上文提到的 hello.c 作為基準程序。

// hello.c#include int main() { printf("hello, world\n"); return 0;}

gcc hello.c -o hello.out 編譯以後,它的大小是 16712 個字節。

Step1: Strip Symbols

第一步,也是最容易想到的一步,剔除符號表。

符號是連結器工作的的基本元素,原始碼中的函數、變量等被編譯以後,都變成了符號。

如果經常從事 C 開發,一定遇到過 ld: symbol not found 的錯誤,往往是忘記連結了某個庫導致的。

使用 nm 我們可以查看一個二進位程序中含有哪些符號。

Tip:

nm 是「窺探」二進位的一個有力工具。記得之前有一次蘋果調整了 iOS 的審核策略,不再允許使用了 UIWebView 的 App 提交。我們的 IPA 裡面不知道哪個依賴使用了 UIWebView,導致蘋果一直審核不過,每次都要二分注釋、打包、提交審核,然後等待蘋果的自動檢查郵件告知結果,非常痛苦。

後來我想到了一個辦法,就是使用 nm 查看編譯出來的可執行程序,看看裡面是否有 UIWebView 相關的 symbol,這大大簡化了調試流程,很快就定位到問題了。

對 step0 中的 hello.out 程序使用 nm,輸出如下:

可以看到有一個符號叫做 main,這個對應的就是我們的 main 函數。但是很奇怪沒有看到 printf,而是出現了一個叫做 puts@@GLIBC_2.2.5 的符號。

這裡其實是 GCC 做的一個優化,如果沒有使用格式字符串調用 printf,GCC 會將它換成 puts。

這些符號都存儲在了 ELF 中,主要用於連結,對於可執行文件來說,符號並沒有什麼太大作用,所以我們首先可以通過剔除符號表來節省空間。

有兩個方法,第一是通過 strip,第二是通過 GCC 參數。

這裡我們使用第二個方法,gcc -s hello.c -o hello.out 得到新的不含符號表的可執行程序,它的大小是 14512 字節。

雖然結果還是很大,但是我們省了 2K 左右,不錯,再接再厲。

Step2: Optimization

第二個比較容易想到的辦法就是優化,開啟優化以後編譯器會生成更加高效的指令,從而減小文件體積。

使用 gcc -O3 編譯我們的程序,然後會發現,結果沒有任何變化��。

其實也非常合理,因為這個程序太簡單了,沒什麼好優化的。

看來要再想想別的辦法。

Step3: Remove Startup Files

之前我們提到過一個問題,是誰在調用 main 函數?

實際上我們編寫的程序都會被默認連結到 GCC 提供的 C 運行時庫,叫做 crt。

通過 gcc --verbose 我們可以查看編譯連結的詳細日誌。

可以發現我們的程序連結了 crt1.o, crti.o, crtbegin.o, crtend.o 以及 crtn.o。

其中 crt1.o 裡面提供的 _start 函數是程序事實上的入口,這個函數負責準備 main 函數需要的參數,調用 main 函數以及處理 main 函數的返回值。

上面這些 crt 文件統稱為 Start Files。所以,現在我們的思路是,可不可以不用這些啟動文件?

_start 函數主要功能有兩個,第一是準備參數,我們的 main 不使用任何參數,所以這一部分可以忽略。

第二是處理返回值,具體的處理方式是使用 main 函數的返回值調用 exit 系統調用進行退出。

所以如果我們不使用啟動文件的話,只需要自己使用系統調用退出即可。

因為我們現在不使用 _start 了,自然我們的主函數也沒必要一定要叫做 main,這裡我們改個名字突出一下這個事實。

#include #includeintnomain(){ printf("hello, world\n"); _exit(0);}

unistd.h 裡面提供系統調用的相關函數,這裡我們使用的是 _exit。為什麼是 _exit 而不是 exit?可以參考這個回答「What is the difference between using _exit() & exit() in a conventional Linux fork-exec?」。

通過 gcc -e nomain -nostartfiles 編譯我們的程序,其中 -e 指定入口,--nostartfiles 作用很明顯,告訴 GCC 不必連結啟動文件了。

我們得到的結果是 13664 個字節,不錯,又向前邁進了一步。

Step4: Remove Standard Library

現在我們已經不使用啟動文件了,但是我們還在使用標準庫,printf 和 _exit 函數都是標準庫提供的。

可不可以不使用標準庫?

當然也可以。

這裡就要說到系統調用,用戶程序和作業系統的交互通過一系列稱為「系統調用」的過程來完成。

比如 syscall_64 是 64 位 Linux 的系統調用表,裡面列出了 Linux 提供的所有系統調用。

系統調用工作在最底層,通過約定的寄存器傳遞參數,然後使用一條特別的指令,比如 32 位 Linux 是 int 80h,64 位 Linux 是 syscall 進入系統調用,最後通過約定的寄存器獲取結果。

C 標準庫裡面封裝了相關函數幫助我們進行系統調用,一般我們不用關心調用細節。

現在如果我們不想使用標準庫,那麼就需要自己去完成系統調用,在 hello 程序中我們使用了兩個系統調用:

write: 向終端列印字符實際上就是向終端對應的文件寫入數據

exit: 退出程序

因為要訪問寄存器,所以必須要使用內聯彙編。

最終代碼如下,在 C 中內聯彙編的語法可以參考這篇文檔(https://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html)。

char *str = "hello, world\n";

voidmyprint(){ asm("movq $1, %%rax \n" "movq $1, %%rdi \n" "movq %0, %%rsi \n" "movq $13, %%rdx \n" "syscall \n" : // no output : "r"(str) : "rax", "rdi", "rsi", "rdx");}

voidmyexit(){ asm("movq $60, %rax \n" "xor %rdi, %rdi \n" "syscall \n");}

intnomain(){ myprint(); myexit();}

使用 gcc -nostdlib 編譯我們的程序,結果是 12912 字節。

能去的我們都去掉了,為什麼還是這麼大???

Step5: Custom Linker Script

我們先來看上一步得到的結果。

$ readelf -S -W step4/hello.outSection Headers: [Nr] Name Type Address Off Size ES Flg Lk Inf Al [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 0000000000401000 001000 00006e 00 AX 0 0 16 [ 2] .rodata PROGBITS 0000000000402000 002000 00000e 01 AMS 0 0 1 [ 3] .eh_frame_hdr PROGBITS 0000000000402010 002010 000024 00 A 0 0 4 [ 4] .eh_frame PROGBITS 0000000000402038 002038 000054 00 A 0 0 8 [ 5] .data PROGBITS 0000000000404000 003000 000008 00 WA 0 0 8 [ 6] .comment PROGBITS 0000000000000000 003008 000022 01 MS 0 0 1 [ 7] .shstrtab STRTAB 0000000000000000 00302a 000040 00 0 0 1

可以發現 Size 很小但是 Off 的值非常大,也就是說每個 Section 的體積很小,但是偏移量很大。

使用 xxd 查看文件內容,會發現裡面有大量的 0。所以情況現在很明朗,有人在對齊。

這裡其實是默認的 Linker Script 連結腳本在做對齊操作。

控制連結器行為的腳本叫做 Linker Script,連結器內置了一個默認腳本,正常情況下我們使用默認的就好。

我們先來看看默認的腳本是什麼內容。

$ ld --verboseGNU ld (GNU Binutils) 2.34... . = ALIGN(CONSTANT (MAXPAGESIZE));... . = ALIGN(CONSTANT (MAXPAGESIZE));...

可以看到裡面有使用 ALIGN 來對齊某些 Section,使得他們的地址是 MAXPAGESIZE 的倍數,這裡 MAXPAGESIZE 是 4K。

這就解釋了為什麼我們的程序那麼大。

所以現在解決方案也就很清晰了,我們不使用默認的連結腳本,自行編寫一個。

$ cat > link.lds tiny : { *(.text) *(.data) *(.rodata*) }

/DISCARD/ : { *(*) }}EOF

使用 gcc -T link.lds 編譯程序以後,我們得到了 584 字節,巨大的進步!��

Step6: Assembly

還有什麼辦法能進一步壓縮嗎?

上面我們是在 C 中使用內聯彙編,為什麼不直接使用彙編,完全拋棄 C?

我們來試試看,其實上面的 C 代碼轉換成彙編非常直接。

section .datamessage: db "hello, world", 0xa

section .text

global nomainnomain: mov rax, 1 mov rdi, 1 mov rsi, message mov rdx, 13 syscall mov rax, 60 xor rdi, rdi syscall

這裡我們使用 nasm 彙編器,我喜歡它的語法~

nasm -f elf64 彙編我們的程序,然後使用 ld 配合上面的自定義連結腳本連結以後得到可執行程序。

最後的結果是 440 字節,離終點又進了一步了✌~

Step7: Handmade Binary

還能再進一步嗎?還有什麼是我們沒控制的?

所有的代碼都已經由我們精確掌控了,但是最終的 ELF 文件依舊是由工具生成的。

所以,最後一步,我們來手動生成 ELF 文件,精確地控制可執行文件的每一個字節。

BITS 64 org 0x400000

ehdr: ; Elf64_Ehdr db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident times 8 db 0 dw 2 ; e_type dw 0x3e ; e_machine dd 1 ; e_version dq _start ; e_entry dq phdr - $$ ; e_phoff dq 0 ; e_shoff dd 0 ; e_flags dw ehdrsize ; e_ehsize dw phdrsize ; e_phentsize dw 1 ; e_phnum dw 0 ; e_shentsize dw 0 ; e_shnum dw 0 ; e_shstrndxehdrsize equ $ - ehdr

phdr: ; Elf64_Phdr dd 1 ; p_type dd 5 ; p_flags dq 0 ; p_offset dq $$ ; p_vaddr dq $$ ; p_paddr dq filesize ; p_filesz dq filesize ; p_memsz dq 0x1000 ; p_alignphdrsize equ $ - phdr

_start: mov rax, 1 mov rdi, 1 mov rsi, message mov rdx, 13 syscall mov rax, 60 xor rdi, rdi syscall

message: db "hello, world", 0xa

filesize equ $ - $$

還是使用 nasm,不過這一次,我們使用 nasm -f bin 直接得到二進位程序。

最終結果是 170 個字節,這 170 字節的程序發送給任意的 x64 架構的 64 位 Linux,都可以列印出 hello world。

結束了,塵埃落定。

Tip: 其實還可以繼續,還有一些技巧可以進一步減小體積,因為非常的「Hack」,這裡不打算說明了。有興趣的朋友可以參考《A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux》。

Final Binary Anatomy

最後我們來看一下這 170 字節中每一個字節是什麼,在做什麼,真正地做到對每一個字節都瞭然於胸。

# ELF Header00: 7f 45 4c 46 02 01 01 00 # e_ident08: 00 00 00 00 00 00 00 00 # reserved10: 02 00 # e_type12: 3e 00 # e_machine14: 01 00 00 00 # e_version18: 78 00 40 00 00 00 00 00 # e_entry20: 40 00 00 00 00 00 00 00 # e_phoff28: 00 00 00 00 00 00 00 00 # e_shoff30: 00 00 00 00 # e_flags34: 40 00 # e_ehsize36: 38 00 # e_phentsize38: 01 00 # e_phnum3a: 00 00 # e_shentsize3c: 00 00 # e_shnum3e: 00 00 # e_shstrndx

# Program Header40: 01 00 00 00 # p_type44: 05 00 00 00 # p_flags48: 00 00 00 00 00 00 00 00 # p_offset50: 00 00 40 00 00 00 00 00 # p_vaddr58: 00 00 40 00 00 00 00 00 # p_paddr60: aa 00 00 00 00 00 00 00 # p_filesz68: aa 00 00 00 00 00 00 00 # p_memsz70: 00 10 00 00 00 00 00 00 # p_align

# Code78: b8 01 00 00 00 # mov $0x1,%eax7d: bf 01 00 00 00 # mov $0x1,%edi82: 48 be 9d 00 40 00 00 00 00 00 # movabs $0x40009d,%rsi8c: ba 0d 00 00 00 # mov $0xd,%edx91: 0f 05 # syscall93: b8 3c 00 00 00 # mov $0x3c,%eax98: 48 31 ff # xor %rdi,%rdi9b: 0f 05 # syscall9d: 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 0a # "hello, world\n"

可以發現 ELF Header 是 64 個字節,Program Header 是 56 字節,代碼 37 個字節,最後 13 個字節是 hello, world\n 這個字符串數據。

從上面的反彙編中我們可以看出 x86-64 和 ARM 比起來一個顯著的特點就是 x86-64 是變長指令集,每條指令的長度並不相等。長一點的 movabs 是 10 個字節,而短一點的 syscall 只有 2 個字節。

關於 x86-64,Intel 官方的手冊 Intel® 64 and IA-32 Architectures Software Developer Manuals 十分十分詳細,是每一個底層愛好者居家旅行的必備之物。

tiny-x64-helloworld 倉庫中有上面每一步的代碼和編譯指令,供大家參考~

最後,編譯、連結和裝載網際網路上有很多資料,這篇博客的目的並不是想要詳細地去介紹這裡面的知識,更多地是想作為一個楔子,幫助大家建立一個整體的認識,從而挑選自己感興趣的部分去深入學習,祝大家 Happy Coding~

原文連結:https://cjting.me/2020/12/10/tiny-x64-helloworld/

本周六,WAIC開發者·2020上海臨港人工智慧開發者大會將震撼來襲。

全球頂尖AI專家齊聚主論壇,解讀智能化轉型的技術之道。4場技術工作坊分享智慧城市、智能出行、華為開源生態與邊緣智能引擎等話題,2場黑客松等你來戰。

原標題:《只有170位元組,最小的64位Hello World程序這樣寫成》

閱讀原文

相關焦點

  • 詳解對ELF64之手動脫殼的逆向分析
    64位的程序?readelf -a upx_linux,下圖可以看到為64位程序;上圖為ELF文件頭,前四個字節為標識:7f 45 4c 46,意思就是:ELF,下圖:第5個字節標識:為1,則32位;為2,則為64位;這裡建議用010editor這個編輯器,它提供了很多腳本可以自動地識別出文件格式,能對當前欄位進行說明。
  • 字節跳動稱騰訊封禁飛書系列產品 飛書小程序審核被卡兩個月
    字節跳動稱騰訊封禁飛書系列產品 飛書小程序審核被卡兩個月時間:2021-01-07 22:55   來源:豹變   責任編輯:毛青青 川北在線核心提示:原標題:字節跳動稱騰訊封禁飛書系列產品 飛書小程序審核被卡兩個月 1月7日,字節跳動副總裁謝欣發布微頭條稱,由於微信開放平臺的不開放,飛書文檔微信小程序已經在審核流程上被卡將近兩個月了
  • 不懼蘋果 三星Galaxy 5也將搭載64位獵戶座處理器
    在安卓廠商們還琢磨著怎麼往手機裡多塞幾個處理器核心的時候,蘋果iPhone 5s突然以64bit為概念殺出重圍,讓眾廠商們措手不及。不過,從三星方面公布的消息來看,三星在64位競賽中已經不會落後太久。
  • 字節跳動的陽謀
    很長一段時間裡,字節跳動的高幹團隊都極為神秘且低調,直到外媒在去年揭開14位核心管理層的面紗後,字節跳動的組織圖景才變得清晰起來,外界關注目光也更多投射向這家網際網路新貴的內部肌理。不過,在所謂「APP工廠」的標籤背後,字節跳動的此輪人事調整有何「陽謀」?而當網際網路內外競爭日趨白熱化時,字節跳動的「新八年抗戰」又將如何打響?
  • 蜘蛛俠 | hello,Parker,welcome to the real world.
    對超級英雄來說,平凡人的小確幸是最珍貴的。還好,這四個反派都被一個號稱來自異次元的超級英雄——神秘客乾脆地解決掉。小蜘蛛又能安安穩穩過暑假了。如果這部電影只有前半部,那它不要叫《蜘蛛俠》了,乾脆直接叫《少年蜘蛛的煩惱》吧。
  • hello的由來
    hello 是使用最頻繁的打招呼用語。據說,在英語中除了I(我)之外,使用最多的就是hello 了。
  • 從「717抖音奇妙好物市集」看今日頭條母公司「字節」的電商野心
    自此開始了與頭條變現有關的電商合作2017年9月,今日頭條上線「放心購」業務,2018年3月,抖音與淘寶購物車打通,從抖音跳轉到淘寶做導流2018年5月,抖音上線紅人自有店鋪入口,開始建立抖音自己的電商店鋪;2018年12月,抖音公布了10家購物車運營服務商,進一步完善電商生態;2019年4月,抖音上線了「小米商城」、「京東好物街」、等多款電商小程序
  • 字節跳動稱騰訊封禁飛書系列產品
    近日,字節跳動副總裁謝欣在社交媒體上發文表示,騰訊封禁飛書系列產品。謝欣發文稱,由於微信開放平臺的不開放,「飛書文檔」微信小程序已經在審核流程上被卡將近兩個月了。在這個過程中,騰訊沒有給出任何回應和理由,只是說,「此應用在安全審核中」,不做進一步處理。
  • 小程序「是」什麼?娛樂型小程序是如何鶴立雞群的?
    來源:創業邦伴隨著小程序漸漸深入開發和推廣,我們身邊的小程序類目越多越多,且不乏一些轟動產品市場的明星款。然而這些作品卻不是你我所為,究竟是什麼原因造成了這些差距呢?我想:主要是因為我們不能做到真正了解小程序。小程序「是」什麼呢?
  • 想開發程序進入編程世界,下面的東西你不知道,你不過是紙上談兵
    軟體是一組同時執行多個任務的程序。作業系統也是幫助人類與計算機系統交互的軟體(系統軟體)。程序是給計算機執行特定操作的一組指令。或計算機是一種計算設備,用於在電腦程式控制下處理數據,在執行程序時,原始數據被處理成所需的輸出格式。這些電腦程式是用高級語言編寫的。高級語言幾乎是比計算機可理解的語言更複雜的人類語言,稱為機器語言或低級語言。
  • 巨頭再起爭端,字節跳動高管喊話騰訊:微信無理由「封殺」飛書
    今日(1月7日),字節跳動副總裁謝欣發布微頭條稱,微信開放平臺無理由封禁和限制了多款飛書小程序,包括「飛書」、「飛書會議」和「飛書文檔」等。謝欣控訴微信「封殺」多款飛書小程序謝欣在微頭條中表示,由於微信開放平臺的不開放,「飛書文檔」微信小程序已經在審核流程上被卡將近兩個月了。
  • 字節跳動的失意版圖
    來源 |深燃(shenrancaijing)作者 | 李秋涵B站董事長兼CEO陳睿曾這樣評價張一鳴,「《獅子王》中有一句話,太陽照得到的地方,都是我的疆土。我認為張一鳴真正的夢想是做一個SuperCompany,一個突破人類過去商業史所有邊界和格局的SuperCompany。」這樣的形容不可謂不形象。
  • 字節跳動高管炮轟騰訊:停止無理由封殺!
    來源:中國證券報於蒙蒙1月7日,字節跳動副總裁謝欣在微頭條發文稱,微信開放平臺無理由封禁和限制了多款飛書小程序,包括「飛書」、「飛書會議」和「飛書文檔」等。公司的另外兩款微信小程序「飛書會議」和「飛書」,就遭受了這樣的待遇。謝欣指出,飛書域名下的常用連結在微信端至今無法穩定訪問,造成用戶正常登錄、下載或使用飛書受到影響。「從2020年初,飛書宣布為中小企業提供三年免費服務以來,騰訊微信就開始無理由地全面封殺飛書。」謝欣表示,最近很多企業因為疫情反覆又緊張了起來,遠程辦公再次成為一種重要的工作模式。
  • LCD12864液晶顯示的SHT11溫溼度傳感器程序
    DATA=1;return error; //error=1 通訊錯誤}//讀字節程序,高字節 (MSB)*(p_value+1)=s_read_byte(ACK); //讀第二個字節,低字節 (LSB)*p_checksum =s_read_byte(noACK); //read CRC校驗碼return error; // error=1 通訊錯誤}
  • 《看門狗2》被感染的字節任務詳細攻略
    被感染的字節支線任務過法: 劇情概覽: hadock的「殭屍二代」病毒正通過ctOS的移動設... 被感染的字節《看門狗2》中的支線任務,很多玩家對其完成方法不是很清楚,那麼一起來通過下文了解一下吧。