在main vs _start中返回值

如何解决在main vs _start中返回值

注意,这个问题在这里已经有类似的答案,我想指出:

但是,这个问题询问的更多是它们的返回格式以及它们之间的相互关系(我认为上面的问题并未完全涵盖这些问题)。


_startmain有什么区别?在我看来,ld使用_start,但是gcc使用main作为入口点。我注意到的另一个区别是main似乎返回了%rax中的值,而_start返回了%rbx中的值

以下是我看到这种情况的两种方式的示例:

.globl _start
_start:
    mov $1,%rax
    mov $2,%rbx
    int $0x80

并运行它:

$ as script.s -o script.o; ld script.o -o script; ./script; echo $?
# 2

另一种方式:

.globl main
main:
    mov $3,%rax
    ret

并运行它:

$ gcc script.s -o script; ./script; echo $?
3

这两种方法有什么区别? main是否在某个地方自动调用_start,或者它们之间如何关联?为什么一个在rbx中返回其值,而另一个在rax中返回呢?

解决方法

TL:DR:函数返回值和系统调用参数使用单独的寄存器,因为它们是完全不相关的。


使用gcc进行编译时,它将链接定义_start的CRT启动代码。 _start(间接)调用main,并将main的返回值(主要留在EAX中)传递给exit()库函数 。 (在进行了必要的libc清理(如刷新stdio缓冲区)之后,最终导致退出系统 。)

另请参见Return vs Exit from main function in C-这与您正在执行的操作完全相似,不同之处在于您使用的是_exit(),它绕过了libc清理,而不是exit()Syscall implementation of exit()

根据32位系统调用ABI(An int $0x80 system call),

which you shouldn't be using in 64-bit code在EBX中采用其参数。 这不是函数的返回值,而是过程退出状态 。有关系统调用的更多信息,请参见Hello,world in assembly language with Linux system calls?

请注意,_start不是函数。它不能以这种方式返回,因为堆栈上没有返回地址。 您正在进行诸如“返回操作系统”之类的随意描述,并将其与函数的“返回值”相混淆。可以从{{ 1}}(如果需要),但是您不能exitmain

EAX是函数调用约定中ret大小的值的返回值寄存器。 (因为_start返回int,所以忽略了RAX的高32位。但是,main退出状态只能获取传递给int的值的低8位。 )

相关:

,

_start是二进制文件的入口点。 Main是C代码的入口点。

_start特定于工具链,main()特定于语言。

您不能简单地开始执行已编译的C代码,您需要一个引导程序,一些代码可以预示诸如此类的高级语言所需的最少内容,其他语言则具有更长的要求列表,但是对于C语言,您需要通过加载程序(如果在操作系统或引导程序上,或者在这两者上都使用堆栈指针的解决方案,以便有堆栈),则初始化读/写全局数据(通常称为.data),并将数据清零(通常称为.bss)归零。然后引导程序可以调用main()。

因为大多数代码都在某些操作系统上运行,并且操作系统可以/确实将代码加载到ram中,所以它不需要硬入口点要求,因为您需要启动处理器,例如在存在硬入口的情况下点还是有一个硬向量表地址。因此,gnu足够灵活,某些操作系统也足够灵活,因此代码的入口点不必是二进制文件中的第一个机器代码。现在,这并不意味着_start表示入口点本身,例如,如果您对gnu ld使用链接器脚本,则需要告诉链接器入口点ENTRY(_start)。但是工具确实希望找到一个名为_start的标签,并且如果链接器没有发出警告,它会继续运行,但是会发出警告。

main()特定于C语言作为C入口点,引导程序在完成其工作并准备好运行已编译的C代码后会调用该标签。

如果加载到ram中并且二进制文件格式支持它,并且操作系统的加载器支持它,则二进制文件的入口点可以在二进制文件中的任何位置,如二进制文件中所示。

您可以将_start视为二进制文件的入口点,将main视为已编译的C代码的入口点。

C函数的返回值是由C编译器使用的调用约定定义的,编译器作者可以自由地执行他们想要的任何事情,但是在现代,它们通常符合定义的目标(ARM,x86,MIPS等) )定义的约定。为了使C调用约定确切地定义如何根据事物返回什么,因此int main()是int的返回,但float myfun()在约定中可能具有不同的规则。

从二进制文件(甚至可以返回)返回的消息是由独立于高级语言的操作系统或操作环境定义的。因此,在x86处理器上的mac上,该规则可能是x86上Windows上的规则,可能是另一回事,在同一x86上的Ubuntu Linux上,另一条规则可能是bsd,另一条,可能不是Mint Linux,另一条规则等等。

规则和系统调用特定于操作系统,而不是处理器或计算机,或者特定于不是直接与操作系统无关的高级语言(以引导程序或库代码处理,而不以高级语言代码处理)。您应该对其中的许多对象进行系统调用,而不仅仅是返回寄存器中的值,而且显然,对于二进制文件格式错误的操作系统,操作系统必须具有足够的鲁棒性以处理不正确的返回。和/或允许在不退出系统调用的情况下将其作为合法退货,然后在这种情况下,将为不进行系统调用的退货定义规则。

就主要调用_start而言,您自己可以轻松看到此内容:

int main ( void )
{
    return(5);
}

readelf显示:

  Entry point address:               0x500

objdump显示(这里不是整个输出)

Disassembly of section .init:

00000000000004b8 <_init>:
 4b8:   48 83 ec 08             sub    $0x8,%rsp
 4bc:   48 8b 05 25 0b 20 00    mov    0x200b25(%rip),%rax        # 200fe8 <__gmon_start__>
 4c3:   48 85 c0                test   %rax,%rax
 4c6:   74 02                   je     4ca <_init+0x12>
 4c8:   ff d0                   callq  *%rax
 4ca:   48 83 c4 08             add    $0x8,%rsp
 4ce:   c3                      retq   

...

Disassembly of section .text:

00000000000004f0 <main>:
 4f0:   b8 05 00 00 00          mov    $0x5,%eax
 4f5:   c3                      retq   
 4f6:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
 4fd:   00 00 00 

...

0000000000000500 <_start>:
 500:   31 ed                   xor    %ebp,%ebp
 502:   49 89 d1                mov    %rdx,%r9
 505:   5e                      pop    %rsi
 506:   48 89 e2                mov    %rsp,%rdx
 509:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
 50d:   50                      push   %rax
 50e:   54                      push   %rsp
 50f:   4c 8d 05 6a 01 00 00    lea    0x16a(%rip),%r8        # 680 <__libc_csu_fini>
 516:   48 8d 0d f3 00 00 00    lea    0xf3(%rip),%rcx        # 610 <__libc_csu_init>
 51d:   48 8d 3d cc ff ff ff    lea    -0x34(%rip),%rdi        # 4f0 <main>
 524:   ff 15 b6 0a 20 00       callq  *0x200ab6(%rip)        # 200fe0 <__libc_start_main@GLIBC_2.2.5>
 52a:   f4                      hlt    
 52b:   0f 1f 44 00 00          nopl   0x0(%rax,1)

因此您可以看到我上面提到的所有内容。二进制文件的入口点不在二进制文件的开头。 (对于二进制文件)入口点是_start,位于二进制文件中间的某处。在_start之后的某处(不一定像这里看到的那么紧密,可以被其他嵌套调用掩埋)main是从引导代码中调用的。假定在调用C入口点之前,.data和.bss以及堆栈是由加载程序而非引导程序设置的。

因此,在这种情况下,典型的_start是二进制文件的入口点,在为C引导程序启动后的某个地方,它将调用C入口点main()。作为程序员,您可以控制使用哪个链接程序脚本和引导程序,因此不必使用_start作为入口点,就可以创建自己的链接(当然,不能完全是main(),除非您没有完全支持C以及可能与操作系统有关的其他例外。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-