这个混淆的 C 代码声称 运行 没有 main(),但它到底做了什么?
This obfuscated C code claims to run without a main(), but what does it really do?
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
这是否间接调用了main
?怎么样?
C语言定义的执行环境分为两类:freestanding和hosted。在这两个执行环境中,一个函数被环境调用用于程序启动。
在 freestanding 环境中程序启动函数可以实现定义,而在 hosted 环境中它应该是 main
。 C 中的任何程序都不能 运行 在定义的环境中没有程序启动功能。
在您的例子中,main
被预处理器定义隐藏了。 begin()
将扩展为 decode(a,n,i,m,a,t,e)
,后者将进一步扩展为 main
。
int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main()
decode(s,t,u,m,p,e,d)
是一个有7个参数的参数化宏。此宏的替换列表是 m##s##u##t
。 m, s, u
和t
分别是第4、第1st、第3rd和替换列表中使用的 2nd 参数。
s, t, u, m, p, e, d
1 2 3 4 5 6 7
其余的没有用(只是为了混淆)。传递给 decode
的参数是 "a,n,i,m,a,t,e" 因此,标识符 m, s, u
和 t
分别替换为参数 m, a, i
和 n
。
m --> m
s --> a
u --> i
t --> n
尝试使用 gcc -E source.c
,输出结束于:
int main()
{
printf("Ha HA see how it is?? ");
}
所以一个main()
函数实际上是由预处理器生成的。
decode(a,b,c,d,[...])
打乱前四个参数并将它们连接起来以获得新的标识符,顺序为 dacb
。 (其余三个参数将被忽略。)例如,decode(a,n,i,m,[...])
给出标识符 main
。请注意,这就是 begin
宏的定义。
因此,begin
宏简单定义为main
。
有人想扮魔术师。
他认为他可以欺骗我们。但是众所周知,c程序的执行是从main()
开始的。
int begin()
将通过一次预处理器阶段替换为 decode(a,n,i,m,a,t,e)
。然后,decode(a,n,i,m,a,t,e)
将再次替换为 m##a##i##n。由于通过宏调用的位置关联,s
将具有字符 a
的值。同样,u
将替换为 'i',而 t
将替换为 'n'。而且,这就是 m##s##u##t
将变成 main
的方式
关于宏扩展中的##
符号,它是预处理运算符,它执行标记粘贴。展开宏时,每个“##”运算符两侧的两个标记合并为一个标记,然后替换宏展开中的“##”和两个原始标记。
如果你不相信我,你可以用 -E
标志编译你的代码。预处理后会停止编译过程,你可以看到标记粘贴的结果。
gcc -E FILENAME.c
由于宏扩展,有问题的程序 确实 调用 main()
,但您的假设是有缺陷的 - 它 不会 ] 根本就得打电话给main()
!
严格来说,您可以拥有一个 C 程序并且能够在没有 main
符号的情况下编译它。 main
是 c library
在完成自己的初始化后期望跳入的内容。通常你从称为 _start
的 libc 符号跳转到 main
。总是有可能有一个非常有效的程序,它只执行汇编,而没有 main。看看这个:
/* This must be compiled with the flag -nostdlib because otherwise the
* linker will complain about multiple definitions of the symbol _start
* (one here and one in glibc) and a missing reference to symbol main
* (that the libc expects to be linked against).
*/
void
_start ()
{
/* calling the write system call, with the arguments in this order:
* 1. the stdout file descriptor
* 2. the buffer we want to print (Here it's just a string literal).
* 3. the amount of bytes we want to write.
*/
asm ("int [=10=]x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
asm ("int [=10=]x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}
用 gcc -nostdlib without_main.c
编译上面的代码,并通过在内联汇编中发出系统调用(中断),看到它在屏幕上打印 Hello World!
。
有关此特定问题的更多信息,请查看 ksplice blog
另一个有趣的问题是,您还可以拥有一个程序,该程序无需 main
符号对应于 C 函数即可编译。例如,您可以将以下代码作为一个非常有效的 C 程序,它只会在您提高警告级别时让编译器发出呜呜声。
/* These values are extracted from the decimal representation of the instructions
* of a hello world program written in asm, that gdb provides.
*/
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
数组中的值是字节,对应于在屏幕上打印 Hello World 所需的指令。有关此特定程序如何工作的更详细说明,请查看此 blog post,这也是我首先阅读它的地方。
我想对这些程序做最后的通知。我不知道它们是否根据 C 语言规范注册为有效的 C 程序,但编译这些和 运行 它们当然是很有可能的,即使它们本身违反了规范。
在您的示例中,main()
函数实际上存在,因为 begin
是一个宏,编译器将其替换为 decode
宏,后者又被表达式 m##s 替换##u##t。使用宏扩展 ##
,您将从 decode
得到单词 main
。这是痕迹:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
main()
只是一个技巧,但是在C语言中,程序的入口函数使用名称main()
是没有必要的。这取决于您的操作系统和作为其工具之一的链接器。
在Windows中,你并不总是使用main()
,而是rather WinMain
or wWinMain
, although you can use main()
, even with Microsoft's toolchain。在 Linux 中,可以使用 _start
.
由链接器作为操作系统工具来设置入口点,而不是语言本身。你甚至可以 set our own entry point, and you can make a library that is also executable!
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)
int begin()
{
printf("Ha HA see how it is?? ");
}
这是否间接调用了main
?怎么样?
C语言定义的执行环境分为两类:freestanding和hosted。在这两个执行环境中,一个函数被环境调用用于程序启动。
在 freestanding 环境中程序启动函数可以实现定义,而在 hosted 环境中它应该是 main
。 C 中的任何程序都不能 运行 在定义的环境中没有程序启动功能。
在您的例子中,main
被预处理器定义隐藏了。 begin()
将扩展为 decode(a,n,i,m,a,t,e)
,后者将进一步扩展为 main
。
int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main()
decode(s,t,u,m,p,e,d)
是一个有7个参数的参数化宏。此宏的替换列表是 m##s##u##t
。 m, s, u
和t
分别是第4、第1st、第3rd和替换列表中使用的 2nd 参数。
s, t, u, m, p, e, d
1 2 3 4 5 6 7
其余的没有用(只是为了混淆)。传递给 decode
的参数是 "a,n,i,m,a,t,e" 因此,标识符 m, s, u
和 t
分别替换为参数 m, a, i
和 n
。
m --> m
s --> a
u --> i
t --> n
尝试使用 gcc -E source.c
,输出结束于:
int main()
{
printf("Ha HA see how it is?? ");
}
所以一个main()
函数实际上是由预处理器生成的。
decode(a,b,c,d,[...])
打乱前四个参数并将它们连接起来以获得新的标识符,顺序为 dacb
。 (其余三个参数将被忽略。)例如,decode(a,n,i,m,[...])
给出标识符 main
。请注意,这就是 begin
宏的定义。
因此,begin
宏简单定义为main
。
有人想扮魔术师。
他认为他可以欺骗我们。但是众所周知,c程序的执行是从main()
开始的。
int begin()
将通过一次预处理器阶段替换为 decode(a,n,i,m,a,t,e)
。然后,decode(a,n,i,m,a,t,e)
将再次替换为 m##a##i##n。由于通过宏调用的位置关联,s
将具有字符 a
的值。同样,u
将替换为 'i',而 t
将替换为 'n'。而且,这就是 m##s##u##t
将变成 main
关于宏扩展中的##
符号,它是预处理运算符,它执行标记粘贴。展开宏时,每个“##”运算符两侧的两个标记合并为一个标记,然后替换宏展开中的“##”和两个原始标记。
如果你不相信我,你可以用 -E
标志编译你的代码。预处理后会停止编译过程,你可以看到标记粘贴的结果。
gcc -E FILENAME.c
由于宏扩展,有问题的程序 确实 调用 main()
,但您的假设是有缺陷的 - 它 不会 ] 根本就得打电话给main()
!
严格来说,您可以拥有一个 C 程序并且能够在没有 main
符号的情况下编译它。 main
是 c library
在完成自己的初始化后期望跳入的内容。通常你从称为 _start
的 libc 符号跳转到 main
。总是有可能有一个非常有效的程序,它只执行汇编,而没有 main。看看这个:
/* This must be compiled with the flag -nostdlib because otherwise the
* linker will complain about multiple definitions of the symbol _start
* (one here and one in glibc) and a missing reference to symbol main
* (that the libc expects to be linked against).
*/
void
_start ()
{
/* calling the write system call, with the arguments in this order:
* 1. the stdout file descriptor
* 2. the buffer we want to print (Here it's just a string literal).
* 3. the amount of bytes we want to write.
*/
asm ("int [=10=]x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
asm ("int [=10=]x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}
用 gcc -nostdlib without_main.c
编译上面的代码,并通过在内联汇编中发出系统调用(中断),看到它在屏幕上打印 Hello World!
。
有关此特定问题的更多信息,请查看 ksplice blog
另一个有趣的问题是,您还可以拥有一个程序,该程序无需 main
符号对应于 C 函数即可编译。例如,您可以将以下代码作为一个非常有效的 C 程序,它只会在您提高警告级别时让编译器发出呜呜声。
/* These values are extracted from the decimal representation of the instructions
* of a hello world program written in asm, that gdb provides.
*/
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
数组中的值是字节,对应于在屏幕上打印 Hello World 所需的指令。有关此特定程序如何工作的更详细说明,请查看此 blog post,这也是我首先阅读它的地方。
我想对这些程序做最后的通知。我不知道它们是否根据 C 语言规范注册为有效的 C 程序,但编译这些和 运行 它们当然是很有可能的,即使它们本身违反了规范。
在您的示例中,main()
函数实际上存在,因为 begin
是一个宏,编译器将其替换为 decode
宏,后者又被表达式 m##s 替换##u##t。使用宏扩展 ##
,您将从 decode
得到单词 main
。这是痕迹:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
main()
只是一个技巧,但是在C语言中,程序的入口函数使用名称main()
是没有必要的。这取决于您的操作系统和作为其工具之一的链接器。
在Windows中,你并不总是使用main()
,而是rather WinMain
or wWinMain
, although you can use main()
, even with Microsoft's toolchain。在 Linux 中,可以使用 _start
.
由链接器作为操作系统工具来设置入口点,而不是语言本身。你甚至可以 set our own entry point, and you can make a library that is also executable!