首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >main 函数程序入口?充其量是程序员的代码入口!

main 函数程序入口?充其量是程序员的代码入口!

作者头像
码农UP2U
发布2026-03-16 18:28:02
发布2026-03-16 18:28:02
860
举报
文章被收录于专栏:码农UP2U码农UP2U

所谓 “穷” 字,就是在洞穴里卖苦力!当然了,现在不会有人在洞穴里卖苦力了!但是,小小的一间办公室又锁住了多少人的肉与灵呢?

末尾分享一些不错的资源!!


今天想聊一下 main 函数是怎么开始执行的,感觉是一个很无聊的话题。其实的确比较无聊,这不过只是想在上篇文章的介绍中,更直观的感受一下汇编指令 call 和 ret 这两个指令,还有体会一下“栈”中保存函数返回地址这个事情。

上篇文章:你这个程序员懂函数调用么

再上一篇文章:从内存地址 0x7c00 思考程序员的差距

0x01:C 语言的 main 函数

在学习 C 语言的时候,书上告诉我们说 main 函数是程序的主函数,是入口函数!main 函数是我们程序员写代码时的一个入口,也就是说,我们写的 C 语言的程序是从 main 函数开始执行的。注意,是我们写的代码的入口,并不是真正的入口。

那么 main 函数是怎么开始的呢?是由谁调用的呢?我们简单来看一下。

随便写段 C 语言的代码,然后在第一行代码处设置断点,然后调试运行起来,查看调用栈。

上图是断在 main 函数处时的调用栈,调用栈可以反映函数的调用关系。在上面的图中可以看到,在进入 main 函数之前,还是有层层调用的,下面 3 行,可以看到是执行了 ntdll 和 kernel32 这两个 dll 中的代码,在倒数第四行中可以看到,开始调用 mainCRTStartup 函数,然后一层层的到了我们的 main 函数处。

0x02:Linux 操作系统的 main 函数是谁调用的呢?

Linux 是汇编和 C 语言实现的,那么在 Linux 源码中的 C 语言部分是否有 main 函数呢?那是肯定的!那 Linux 源码中的 main 函数是由谁调用的呢?

这里参考的是 Linux 0.11 的源码,在 head.s 源码中,给出了对 main 函数的调用的代码,代码如下:

代码语言:javascript
复制
after_page_tables:
    pushl $0        # These are the parameters to main :-)
    pushl $0
    pushl $0
    pushl $L6       # return address for main, if it decides to.
    pushl $_main
    jmp setup_paging
L6:
    jmp L6          # main should never return here, but
                # just in case, we know what happens.

上面的代码中,连续的 pushl 后面有一个 jmp 指令,我们看一下 setup_paging 部分的代码。

代码语言:javascript
复制
setup_paging:
    movl $1024*5,%ecx       /* 5 pages - pg_dir+4 page tables */
    xorl %eax,%eax
    xorl %edi,%edi          /* pg_dir is at 0x000 */
    cld;rep;stosl
    movl $pg0+7,_pg_dir     /* set present bit/user r/w */
    movl $pg1+7,_pg_dir+4       /*  --------- " " --------- */
    movl $pg2+7,_pg_dir+8       /*  --------- " " --------- */
    movl $pg3+7,_pg_dir+12      /*  --------- " " --------- */
    movl $pg3+4092,%edi
    movl $0xfff007,%eax     /*  16Mb - 4096 + 7 (r/w user,p) */
    std
1:  stosl           /* fill pages backwards - more efficient :-) */
    subl $0x1000,%eax
    jge 1b
    xorl %eax,%eax      /* pg_dir is at 0x0000 */
    movl %eax,%cr3      /* cr3 - page directory start */
    movl %cr0,%eax
    orl $0x80000000,%eax
    movl %eax,%cr0      /* set paging (PG) bit */
    ret         /* this also flushes prefetch-queue */

为什么要看 setup_paging 呢?其实完全不用看!在 setup_paging 中没有栈相关的操作。最后有一个 ret 指令!在上篇文章中应该提过,ret 时会把栈顶位置的值给到指令指针寄存器中。

当前的栈顶的值就是 _main,也就 setup_paging 返回后,直接是执行 _main 函数了。再回看一下上面的代码。

代码语言:javascript
复制
after_page_tables:
    pushl $0        # These are the parameters to main :-)
    pushl $0
    pushl $0
    pushl $L6       # return address for main, if it decides to.
    pushl $_main
    jmp setup_paging
L6:
    jmp L6          # main should never return here, but
                # just in case, we know what happens.

上面的代码中,在不考虑 jmp 的情况下,连续 pushl 可以看做如下的代码:

代码语言:javascript
复制
pushl $0
pushl $0
pushl $0
pushl $L6
call  $_main

前 3 个 pushl 是 main 函数的参数,依次是 env、argv 和 argc,最后的 $L6 是 main 函数的返回地址。看注释 main 函数是不会返回的。所以这个返回地址的标号也比较有意思,哈哈!L6,老 6?不返回就命名为“老六”?!!

0x03:类似的例子

在 Linux 中还有类似的代码吗,是有的,随便拿一处的代码来看一下吧。

代码如下:

代码语言:javascript
复制
#define move_to_user_mode() \
__asm__ ("movl %%esp,%%eax\n\t" \
    "pushl $0x17\n\t" \
    "pushl %%eax\n\t" \
    "pushfl\n\t" \
    "pushl $0x0f\n\t" \
    "pushl $1f\n\t" \
    "iret\n" \
    "1:\tmovl $0x17,%%eax\n\t" \
    "movw %%ax,%%ds\n\t" \
    "movw %%ax,%%es\n\t" \
    "movw %%ax,%%fs\n\t" \
    "movw %%ax,%%gs" \
    :::"ax")

void main(void)     /* This really IS void, no error here. */
{           /* The startup routine assumes (well, ...) this */
    ...

    move_to_user_mode();
    if (!fork()) {      /* we count on this going ok */
        init();
    }

上面的代码还是出自 Linux 0.11 的源码中,在 main.c 这个源码中。

move_to_user_mode() 是从内核态切换到用户态,使用的方式中断。看上面代码的第 8 行,指令是 iret 指令,这是中断返回的指令。在上面的代码中是没有进行中断调用的,那它怎么进行中断返回的呢?也是借助栈顶的值返回。它自己压栈构造了一个中断返回地址,然后调用 iret 来完成中断返回,从而完成了内核态到用户态的切换。

0x04:最后...

终归 main 函数只是程序员写代码的入口,而不是真正的程序的入口,无论是对于 PE 文件、ELF 文件,还是 MachO 的文件,里面都有实际的程序入口的地址。我们的可执行文件在生成时,链接器会提供很多操作系统相关的数据到可执行文件中,便于操作系统的加载。当然了,不同的操作系统的文件格式也是不同,因此这些可执行的二进制文件想要直接跨操作系统执行……不行的!

分享些觉得不错的资源吧!两本电子书吧,里面的内容感觉挺不错的!

阶层跃升之道是998元藏经阁精选书籍.pdf https://pan.quark.cn/s/e06b8a7ad805

绝密人性天书.pdf https://pan.quark.cn/s/d75ab25b798b

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-07-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 源代码010 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档