glibc 2.3 之crt1.o分析

本文详细解读了glibc2.3中.crt1.o文件的组成与功能,包括start.o、abi-note.o、init.o等源文件的作用,以及它们如何共同构建启动代码并调用用户自定义的main函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

glibc 2.3 之crt1.o分析

1.crt1.o文件的组成
crt1.o在csu目录下(C StartUp code的缩写)

crt1.o是由以下命令生成的
gcc -nostdlib -nostartfiles -r -o crt1.o start.o abi-note.o init.o

start.o对应的源文件是sysdeps/i386/elf/start.S
abi-note.o对应的源文件是csu/abi-note.S
init.o对应的源文件是csu/init.c

2.start.S

/* Startup code compliant to the ELF i386 ABI.
   Copyright (C) 1995,1996,1997,1998,2000,2001 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   02111-1307 USA.  */

/* This is the canonical entry point, usually the first thing in the text
   segment.  The SVR4/i386 ABI (pages 3-31, 3-32) says that when the entry
   point runs, most registers' values are unspecified, except for:

   %edx        Contains a function pointer to be registered with `atexit'.
        This is how the dynamic linker arranges to have DT_FINI
        functions called for shared libraries that have been loaded
        before this code runs.

   %esp        The stack contains the arguments and environment:
        0(%esp)            argc
        4(%esp)            argv[0]
        ...
        (4*argc)(%esp)        NULL
        (4*(argc+1))(%esp)    envp[0]
        ...
                    NULL
*/

#include "bp-sym.h"//提供宏 # define _BP_SYM(name) name

    .text
    .globl _start
    .type _start,@function
_start:
    /* Clear the frame pointer.  The ABI suggests this be done, to mark
       the outermost frame obviously. 按规定清除%ebp */
    xorl %ebp, %ebp

    /* Extract the arguments as encoded on the stack and set up
       the arguments for `main': argc, argv. 解析argc和argv参数 envp will be determined
       later in __libc_start_main. envp由__libc_start_main准备 */
    popl %esi        /* Pop the argument count. 取argc */
    movl %esp, %ecx        /* argv starts just at the current stack top.取argv */

    /* Before pushing the arguments align the stack to a 16-byte
    (SSE needs 16-byte alignment) boundary to avoid penalties from
    misaligned accesses.  Thanks to Edward Seidl <seidl@janed.com>
    for pointing this out.  */
    andl $0xfffffff0, %esp//向下对齐到16字节边界
    pushl %eax        /* Push garbage because we allocate
28 more bytes. 后面总共7个push指令,28字节,补上4字节,凑成32字节,组成一个cache line?*/

    /* Provide the highest stack address to the user code (for stacks
       which grow downwards).  最高栈址*/
    pushl %esp

    pushl %edx        /* Push address of the shared library
                   termination function.  共享库结束函数*/

    /* Push address of our own entry points to .fini and .init.  */
    pushl $_fini
    pushl $_init

    pushl %ecx        /* Push second argument: argv.  */
    pushl %esi        /* Push first argument: argc.  */

    pushl $BP_SYM (main)//main函数地址入栈

    /* Call the user's main function, and exit with its value.
       But let the libc call main.    */
    call BP_SYM (__libc_start_main)

    hlt            /* Crash if somehow `exit' does return.  __libc_start_main不会返回*/

/* To fulfill the System V/i386 ABI we need this symbol.规定需要这个符号  Yuck, it's so
   meaningless since we don't support machines < 80386.  */
    .section .rodata
    .globl _fp_hw
_fp_hw:    .long 3
    .size _fp_hw, 4
    .type _fp_hw,@object

/* Define a symbol for the first piece of initialized data.数据段开始  */
    .data
    .globl __data_start
__data_start:
    .long 0
    .weak data_start
    data_start = __data_start


3.__libc_start_main函数

在sysdeps/generic/libc-start.c中
函数原型是
extern int BP_SYM (__libc_start_main) (int (*main) (int, char **, char **),
                       int argc,
                       char *__unbounded *__unbounded ubp_av,
                       void (*init) (void),
                       void (*fini) (void),
                       void (*rtld_fini) (void),
                       void *__unbounded stack_end)
     __attribute__ ((noreturn));

c语言函数调用使用从后向前的方式入栈,如果要调用__libc_start_main,则
形参入栈顺序是
|<-高地址
stack_end
rtld_fini
fini
init
ubp_av
argc
main
|<-低地址

和前面的push指令正好可以对上

__libc_start_main的执行流程是
如果init不空,调用init
调用main
如果fini不空,调用fini

4.abi-note.S

/* Define an ELF note identifying the operating-system ABI that the
   executable was created for.定义一个ELF note用来标识可执行文件使用的操作系统ABI
   (Application Binary Interface)
   .  The ELF note information identifies a
   particular OS or coordinated development effort within which the
   ELF header's e_machine value plus (for dynamically linked programs)
   the PT_INTERP dynamic linker name and DT_NEEDED shared library
   names fully identify the runtime environment required by an
   executable.ELF note信息标识特定OS或相应的开发工具,在遵从这个ABI的OS或开发工具中,
   ELF头部e_machine值加上PT_INTERP动态
   连接器名和DT_NEEDED共享库名完全标志可执行文件需要的运行时环境。

   The general format of ELF notes is as follows.一般的ELF notes格式如下
   Offsets and lengths are bytes or (parenthetical references) to the
   values in other fields.偏移和长度是字节为单位,对于括号中的值,他们代表引用其他字段的值

offset    length    contents   
0    4    length of name
4    4    length of data
8    4    note type
12    (0)    vendor name
        - null-terminated ASCII string, padded to 4-byte alignment
12+(0)    (4)    note data,

   The GNU project and cooperating development efforts (including the
   Linux community) use note type 1 and a vendor name string of "GNU"
   for a note descriptor that indicates ABI requirements.  The note data
   GNU项目和相应的开发工具使用note类型1和和生成商名字串"GNU"指示ABI需求.
   is four 32-bit words.  The first of these is an operating system
   note data是4个32位字。
   number (0=Linux, 1=Hurd, 2=Solaris, ...) and the remaining three
   identify the earliest release of that OS that supports this ABI.
   第一个字代表操作系统(0为linux,1为hurd,2为solaris....),剩下的三字代表
   支持该ABI的最早操作系统版本.
   See abi-tags (top level) for details. */

#include <config.h>
#include <abi-tag.h>        /* OS-specific ABI tag value */
   
/* The linker (GNU ld 2.8 and later) recognizes an allocated section whose
   name begins with `.note' and creates a PT_NOTE program header entry
   pointing at it. */

    .section ".note.ABI-tag", "a"
    .align 4
    .long 1f - 0f        /* name length */
    .long 3f - 2f        /* data length */
    .long  1        /* note type */
0:    .asciz "GNU"        /* vendor name */
1:    .align 4       
2:    .long __ABI_TAG_OS    /* note data: the ABI tag */
    .long __ABI_TAG_VERSION
3:    .align 4        /* pad out section */

这些ABI_TAG值是

[root@mail /var/root/glibc-2.3/csu]# cat abi-tag.h
#define __ABI_TAG_OS 0
#ifndef __ABI_TAG_VERSION
# define __ABI_TAG_VERSION 2,0,0
#endif

5.init.c

#if defined USE_IN_LIBIO && defined __GNUC__ && __GNUC__ >= 2

#include <_G_config.h>

/* This records which stdio is linked against in the application.
记录哪个stdio被连接
*/
const int _IO_stdin_used = _G_IO_IO_FILE_VERSION;//0x20001

#endif
<think>嗯,用户在使用GCC编译链时遇到了链接错误,提示找不到crt1.o文件。他已经尝试将crt1.o的路径添加到LIBRARY_PATH环境变量中,但问题依旧存在。现在他想知道crt1.o的路径是由谁查找的,以及LIBRARY_PATH的作用是什么。 首先,我需要回忆一下GCC的编译和链接过程。编译过程通常分为预处理、编译、汇编和链接几个阶段。链接阶段需要用到一些启动文件,比如crt1.o、crti.o、crtn.o等,这些文件是C运行时库的一部分,负责程序的初始化和终止处理。 接下来,crt1.o这类文件的查找路径是由谁决定的呢?根据之前的知识,GCC在链接时使用ld链接器,而ld的搜索路径由几个因素决定,包括GCC的默认配置、命令行参数中的-L选项,以及环境变量如LIBRARY_PATH。不过,LIBRARY_PATH主要是用于在链接时查找库文件(.a或.so),而对于像crt1.o这样的对象文件,可能并不是通过LIBRARY_PATH来查找的。 然后,需要确认crt1.o的具体位置。通常,这些文件位于GCC的标准库目录中,比如在Linux系统中可能是/usr/lib或/usr/lib/x86_64-linux-gnu,或者是GCC的特定目录,如/usr/lib/gcc/x86_64-linux-gnu/版本号。用户可能需要使用gcc -print-file-name=crt1.o命令来查找GCC默认的路径。 接下来,LIBRARY_PATH的作用是什么?它用于指定链接期间库文件的搜索路径,影响的是-l选项指定的库,比如-lm会在此路径中查找libm.a或libm.so。但crt1.o是直接链接的对象文件,可能不通过LIBRARY_PATH,而是通过GCC的内部配置路径或另外的环境变量,比如LDFLAGS中的-L选项。 用户的问题可能在于GCC没有找到正确的crt1.o路径,即使设置了LIBRARY_PATH。这时候应该检查GCC的配置是否正确,或者系统是否缺少了相应的开发库,比如libc6-dev。另外,可能还需要检查是否使用了正确的链接器参数,或者是否在交叉编译环境中路径设置有问题。 可能的解决方案包括:安装缺失的开发库,使用gcc命令打印出crt1.o的路径并手动添加到链接命令中,或者检查并设置正确的环境变量如LDFLAGS。此外,检查GCC的安装是否完整,或者是否存在多版本冲突,也可能需要验证系统架构是否匹配(比如32位和64位库混用)。 需要确保用户理解,LIBRARY_PATH主要用于库文件,而crt1.o作为对象文件,其路径可能由其他机制控制。正确的解决步骤应该是先确定crt1.o的位置,然后确保该路径被GCC正确识别,可能需要通过-L选项或调整GCC的默认搜索路径。</think>### 分步解释与解决方案 #### **1. `crt1.o` 的作用与查找机制** `crt1.o` 是 C 运行时(C Runtime)的启动文件,负责程序的初始化工作(如设置栈指针、调用 `main` 函数等)。它的路径由 **GCC 内部配置**决定,具体查找顺序如下: - **GCC 默认内置路径**:GCC 在编译时预定义了标准库和启动文件的路径,通常位于 `/usr/lib`、`/usr/lib/x86_64-linux-gnu` 或 `/usr/lib/gcc/<arch>/<version>/`。 - **链接器参数 `-L`**:可通过 `-L/path/to/dir` 手动指定额外搜索路径。 - **环境变量 `LIBRARY_PATH`**:次要补充路径,但**不直接影响 `crt1.o` 的查找**(见下文)。 #### **2. `LIBRARY_PATH` 的作用** `LIBRARY_PATH` 是一个环境变量,用于**在链接阶段(`-l` 参数)指定库文件(`.a` 或 `.so`)的搜索路径**。例如: - 当使用 `-lm` 时,链接器会依次在 `LIBRARY_PATH` 和默认路径中搜索 `libm.a` 或 `libm.so`。 - **关键限制**:`LIBRARY_PATH` **不用于查找 `.o` 文件(如 `crt1.o`)**,它仅影响库文件。 #### **3. 为什么设置 `LIBRARY_PATH` 无效?** `crt1.o` 是**直接链接的对象文件**,而非通过 `-l` 引用的库文件。GCC 在链接时会通过以下机制查找 `crt1.o`: - **内置的启动文件路径**:由 GCC 编译时配置决定,通常指向系统的 C 库目录(如 `/usr/lib`)。 - **链接器脚本(Linker Script)**:某些系统通过链接器脚本隐式指定 `crt1.o` 的路径,完全绕过 `LIBRARY_PATH`。 #### **4. 解决方案** **步骤 1:确认 `crt1.o` 是否存在** 通过以下命令查找 `crt1.o`: ```bash # 使用 GCC 打印默认路径 gcc -print-file-name=crt1.o # 手动搜索常见路径 find /usr/lib -name "crt1.o" 2>/dev/null find /usr/lib/x86_64-linux-gnu -name "crt1.o" 2>/dev/null ``` 如果未找到,说明系统缺少 C 库开发文件,需安装: ```bash # Debian/Ubuntu sudo apt install libc6-dev # RedHat/CentOS sudo yum install glibc-devel ``` **步骤 2:强制指定链接路径** 若 `crt1.o` 存在但路径未被 GCC 识别,可通过 `-nostdlib` 和手动链接解决: ```bash # 假设 crt1.o 路径为 /usr/lib/x86_64-linux-gnu/crt1.o gcc -nostdlib -o my_program /usr/lib/x86_64-linux-gnu/crt1.o ...其他.o或源文件... -lc ``` 注意:需同时链接其他必要对象文件(如 `crti.o`, `crtn.o`)和 `-lc`(C 库)。 **步骤 3:检查 GCC 配置** 使用 `-v` 参数查看详细链接过程: ```bash gcc -v -o my_program my_source.c ``` 观察输出中的 `LIBRARY_PATH` 和链接器使用的实际路径,确认是否包含 `crt1.o`。 #### **5. 其他可能原因** - **交叉编译环境**:交叉编译工具链的路径可能与主机路径冲突,需检查 `CC`、`CROSS_COMPILE` 等变量。 - **多版本 GCC 冲突**:系统中安装了多个 GCC 版本,导致路径混乱。可通过 `update-alternatives` 切换默认版本。 - **文件权限问题**:确保当前用户对 `crt1.o` 有读取权限。 ### 总结 - `crt1.o` 的查找由 **GCC 内置路径**和**链接器脚本**控制,而非 `LIBRARY_PATH`。 - `LIBRARY_PATH` 仅用于 `-l` 库文件的搜索,不影响 `.o` 文件的链接。 - 解决方案优先级:安装缺失的库 → 检查 GCC 配置 → 手动指定路径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值