编程与软件工程jroweboy.github.io
main 通常是一个函数——但何时不是?
Main is usually a function – when is it not?
2015 — 2021,它一次次回到首页
黑点是上首页(≥ 50 分)的那次投稿,空心点是没冲上去的投稿,点越大分数越高。
导读
这篇博客通过反汇编和链接脚本,探索 C 程序中 main 到底是什么:作者展示了如何把 main 定义成数组甚至字符串,让链接器直接把数据当作机器码执行,最终实现一个没有真正函数入口的"Hello World"程序。文章层层深入,揭示了编译、链接和程序入口点背后的机制。
为什么经久不衰:它把"main 是函数"这一人人默认的常识拆开,展示了 C/汇编/链接器底层的运作方式,这种对系统底层的好奇心和技术趣味跨越多年仍吸引读者。
HN 评论精选
认为文章没有正面回答标题问题,但欣赏这种把程序结构彻底拆解的探索精神
不确定标题的问题最后是否被回答了,结尾他只是说"一直以来我们都忽略了 main 不是函数的警告 :)"。我记得 20 年前第一次写 C/C++ 内嵌汇编时的震撼——我完全掌控了这台机器!其实一旦开始拆这些线头,整个结构都会散架:源代码可以长成任何你想要的样子,这很酷。
DanielBMarkham查看英文原文
Not sure the title's question was answered. At the end he just said "And all this time we’ve been ignoring the warning message about main not being a function :)" I remember 20 years or ago writing my first C/C++ function where I popped out to inline ASM. I was like, wow! I'm in complete control of this box! (It was mostly copied code. My ASM-foo is weak) I really liked the fact that I could either code in these really abstract types -- or be concerned with what the stack looks like on entering a function. So I _think_ I know the answer to the question. Instead of ignoring the error, I was thinking that you could just mangle the link file and make the entry point whatever you want, right? I could have a program that consisted of "void x", then continue along the lines of the articles with the number list. Heck, once you start pulling the threads apart like this, the entire structure falls apart. Your source code could look like anything you want it to look like. And that's cool.
指出这种手法早在 1984 年 IOCCC 获奖作品中就出现过
第一届国际混淆 C 代码大赛(1984 年)的一个获奖作品就用过这种技术:short main[] = {...},直接把机器码以 short 数组的形式放在 main 里。
_kst_查看英文原文
One of the winners of the 1st International Obfuscated C Code Contest (1984) used this technique. https://www.ioccc.org/1984/mullender/mullender.c https://www.ioccc.org/1984/mullender/hint.text short main[] = { 277, 04735, -4129, 25, 0, 477, 1019, 0xbef, 0, 12800, -113, 21119, 0x52d7, -1006, -7151, 0, 0x4bc, 020004, 14880, 10541, 2056, 04010, 4548, 3044, -6716, 0x9, 4407, 6, 5568, 1, -30460, 0, 0x9, 5570, 512, -30419, 0x7e82, 0760, 6, 0, 4, 02400, 15, 0, 4, 1280, 4, 0, 4, 0, 0, 0, 0x8, 0, 4, 0, ',', 0, 12, 0, 4, 0, '#', 0, 020, 0, 4, 0, 30, 0, 026, 0, 0x6176, 120, 25712, 'p', 072163, 'r', 29303, 29801, 'e' };
分享 call/pop 技巧获取内嵌数据地址,并提到这是老式恶意软件的常用手段
获取数据地址并不难,在 32 位寻址下只需把数据内嵌在汇编里:call continue 后跟字符串,continue 处 pop eax——因为 call 相当于 push eip 再跳转,弹出后 eax 就是字符串地址。老式恶意软件常用这招实现地址无关代码并迷惑反汇编器。
ipython查看英文原文
It's not hard to get the address of data in 32-bit addressing. You just interleave the data inside your assembly, something like the following (pseudo code I haven't done this in a while): ... call continue .db "Hello World!\n\0" continue: pop eax ... Since 'call' just turns into a 'push eip; jmp target' (simplified, sorry), the address of the string is now pushed onto the stack. Popping off the top, now eax contains the address of the string "Hello World!\n\0". Since in 32-bit ABI most parameters are passed on the stack, many times you don't even need to 'pop' the address off the stack, it'll just be part of your arguments to the function. Old school malware used this a lot to 1) run regardless of the memory base address it was loaded at and 2) confuse some disassemblers (you can use silly conditionals that are always true or false to control whether you execute the 'call' instruction or not, forcing the disassembler to try and 'disassemble' the string into valid x86 opcodes)
提出可以通过修改链接脚本自定义入口点,进一步突破 main 的限制
与其忽略错误,不如直接改写链接文件,把入口点设成任何你想要的东西,对吧?我可以有一个只由 "void x" 组成的程序,然后按文章的思路继续下去。
jpegqs查看英文原文
Can be both, a string and a function: char main [/*x86*/] __attribute__ ((section(".text")) )="WTYH)9Zj8_j7H)9]R" "H)9^\350\0\0\0\0H)1^R" "H))Z8<2u\366j<)9Xj9" ")9j9VY)<$[S_H\xbd^[" "H$@\xcd\200-XP\xf" "\5XP_j<W\xeb\xe2]" "Hello World!\n"; /*Linux_Only!*/
历次讨论
同类还值得读
开源应用架构
The Architecture of Open Source Applications
《开源软件架构》(AOSA)是一本在线免费书,邀请知名开源项目的作者亲自剖析其内部设计,涵盖 SQLAlchemy、LLVM、Audacity、Apache、Bash 等系统。
编程与软件工程上首页 9 次 · 2011–2024aosabook.org编辑精选编程语言简史:简短、不完整且多半错误
A Brief, Incomplete, and Mostly Wrong History of Programming Languages
一篇以戏谑、故意不准确的方式回顾编程语言史的博客文章,用夸张轶事和讽刺定义串起 Lisp、C、C++、Java、JavaScript、PHP 等语言。
编程与软件工程上首页 8 次 · 2009–2021james-iry.blogspot.comSQLite 是如何测试的
How SQLite is tested
SQLite 官方文档页,系统介绍 SQLite 的测试体系:多种测试方法(TCL 测试、模糊测试、回归测试、静态分析等)和海量测试用例,展示了这个小型开源项目如何靠极致测试获得…
编程与软件工程上首页 8 次 · 2009–2025sqlite.org编辑精选高级编程语言(2009)
Advanced programming languages (2009)
Matt Might 的经典文章,按用途推荐“高级编程语言”:如果追求就业就选主流语言,如果追求表达力、学术研究或创业,则推荐 Haskell、OCaml、Scala 等函数式语…
编程与软件工程上首页 8 次 · 2009–2022matt.might.net我的 Forth 与栈机器经历
Yosefk - My history with Forth & stack machines
作者以亲身经历回顾了使用 Forth 语言和栈式机器的历程:既赞叹其简洁优雅、代码量小、可自举的优点,也坦率指出栈操作在实际编程中的痛苦,以及 Forth 社区文化中的问题。
编程与软件工程上首页 8 次 · 2010–2024yosefk.com编辑精选