Skip to content
Edmunt Pienkowsky edited this page May 24, 2024 · 8 revisions

This is a continuation of README from rpi0-cross-compile project.

Two-step build

Let's investigate more. Build hello-world application in two steps (compilation+linking):

arm-linux-gnueabihf-gcc -c -marm -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -o hello-world.o hello-world.c
arm-linux-gnueabihf-gcc -marm -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -o hello-world hello-world.o

and examine object file:

$ arm-linux-gnueabihf-readelf -A hello-world.o
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "6KZ"
  Tag_CPU_arch: v6KZ
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-1
  Tag_FP_arch: VFPv2
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align_needed: 8-byte
  Tag_ABI_align_preserved: 8-byte, except leaf SP
  Tag_ABI_enum_size: int
  Tag_ABI_VFP_args: VFP registers
  Tag_ABI_optimization_goals: Aggressive Debug
  Tag_CPU_unaligned_access: v6
  Tag_Virtualization_use: TrustZone

The CPU architecture of object file is (almost) correct but architecture of final executable is wrong:

$ arm-linux-gnueabihf-readelf -A hello-world
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "7-A"
  Tag_CPU_arch: v7
  Tag_CPU_arch_profile: Application
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-2
  Tag_FP_arch: VFPv3-D16
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_rounding: Needed
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align_needed: 8-byte
  Tag_ABI_align_preserved: 8-byte, except leaf SP
  Tag_ABI_enum_size: int
  Tag_ABI_VFP_args: VFP registers
  Tag_CPU_unaligned_access: v6
  Tag_Virtualization_use: TrustZone

Listing libraries and objects used by linker

We can easily list all object files and libraries used by linker by passing --trace option:

$ arm-linux-gnueabihf-gcc -marm -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -Wl,--trace -o hello-world hello-world.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/../../../../arm-linux-gnueabihf/lib/Scrt1.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/../../../../arm-linux-gnueabihf/lib/crti.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/crtbeginS.o
hello-world.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/libgcc.a
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/libgcc_s.so
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/../../../../arm-linux-gnueabihf/lib/libgcc_s.so.1
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/libgcc.a
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/../../../../arm-linux-gnueabihf/lib/libc.so
/usr/arm-linux-gnueabihf/lib/libc.so.6
/usr/arm-linux-gnueabihf/lib/libc_nonshared.a
/usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3
/usr/arm-linux-gnueabihf/lib/libc_nonshared.a
/usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/libgcc.a
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/libgcc_s.so
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/../../../../arm-linux-gnueabihf/lib/libgcc_s.so.1
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/libgcc.a
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/crtendS.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/11/../../../../arm-linux-gnueabihf/lib/crtn.o

and then investigate crtbeginS.o for example:

$ arm-linux-gnueabihf-readelf -A /usr/lib/gcc-cross/arm-linux-gnueabihf/11/crtbeginS.o
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "7-A"
  Tag_CPU_arch: v7
  Tag_CPU_arch_profile: Application
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-2
  Tag_FP_arch: VFPv3-D16
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align_needed: 8-byte
  Tag_ABI_align_preserved: 8-byte, except leaf SP
  Tag_ABI_enum_size: int
  Tag_ABI_VFP_args: VFP registers
  Tag_ABI_optimization_goals: Aggressive Speed
  Tag_CPU_unaligned_access: v6

As we see the linker uses object files with wrong CPU architecture. Consequently the final executable has wrong CPU architecture also 😕.

Sysroot

The solution is to use proper libraries and object files taken from Raspberry Pi OS and then specify path to these files using --sysroot option.

There're many ways to do this. Here we're using files from Docker image. See Preparing SYSROOT page for more info.

Assuming that sysroot is prepared in /home/vscode/sysroot directory:

$ arm-linux-gnueabihf-gcc --sysroot=/home/vscode/sysroot -L=/usr/lib -L=/usr/lib/gcc -marm -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -Wl,--trace -o hello-world hello-world.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/lib/Scrt1.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/lib/crti.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/12/crtbeginS.o
hello-world.o
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/gcc/libgcc_s.so
/home/vscode/sysroot/usr/lib/libgcc_s.so.1
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/libc.so
/home/vscode/sysroot/lib/arm-linux-gnueabihf/libc.so.6
/home/vscode/sysroot/usr/lib/arm-linux-gnueabihf/libc_nonshared.a
/home/vscode/sysroot/lib/ld-linux-armhf.so.3
/home/vscode/sysroot/usr/lib/arm-linux-gnueabihf/libc_nonshared.a
/home/vscode/sysroot/lib/ld-linux-armhf.so.3
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/gcc/libgcc_s.so
/home/vscode/sysroot/usr/lib/libgcc_s.so.1
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/usr/lib/gcc-cross/arm-linux-gnueabihf/12/crtendS.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/lib/crtn.o

Options -L=/usr/lib -L=/usr/lib/gcc specifies library search paths relative to sysroot.

All libraries are taken from sysroot but startup files aren't 😥.

Dealing with startup files

There's no easy way - such as command line option - to specify custom startup files. Due to searching procedure of startup files we have to rename or move preinstalled ones to another location. See Startup files explained page for more info. The safest way to do this is to use dpkg-divert command:

dpkg-divert --rename /usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/lib/Scrt1.o
dpkg-divert --rename /usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/lib/crti.o
dpkg-divert --rename /usr/lib/gcc-cross/arm-linux-gnueabihf/12/crtbeginS.o
dpkg-divert --rename /usr/lib/gcc-cross/arm-linux-gnueabihf/12/crtendS.o
dpkg-divert --rename /usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/lib/crtn.o

Important

Use sudo command to invoke dpkg-divert.

Now we may try to compile hello-world again:

$ arm-linux-gnueabihf-gcc --sysroot=/home/vscode/sysroot -L=/usr/lib -L=/usr/lib/gcc -marm -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -Wl,--trace -o hello-world hello-world.o
/home/vscode/sysroot/lib/arm-linux-gnueabihf/Scrt1.o
/home/vscode/sysroot/lib/arm-linux-gnueabihf/crti.o
/home/vscode/sysroot/lib/arm-linux-gnueabihf/crtbeginS.o
hello-world.o
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/gcc/libgcc_s.so
/home/vscode/sysroot/usr/lib/libgcc_s.so.1
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/libc.so
/home/vscode/sysroot/lib/arm-linux-gnueabihf/libc.so.6
/home/vscode/sysroot/usr/lib/arm-linux-gnueabihf/libc_nonshared.a
/home/vscode/sysroot/lib/ld-linux-armhf.so.3
/home/vscode/sysroot/usr/lib/arm-linux-gnueabihf/libc_nonshared.a
/home/vscode/sysroot/lib/ld-linux-armhf.so.3
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/gcc/libgcc_s.so
/home/vscode/sysroot/usr/lib/libgcc_s.so.1
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/lib/arm-linux-gnueabihf/crtendS.o
/home/vscode/sysroot/lib/arm-linux-gnueabihf/crtn.o

As we see. Now all files are taken from sysroot and CPU architecture of generated binary is finally correct 😃:

$ arm-linux-gnueabihf-readelf -A hello-world
Attribute Section: aeabi
File Attributes
  Tag_CPU_name: "6KZ"
  Tag_CPU_arch: v6KZ
  Tag_ARM_ISA_use: Yes
  Tag_THUMB_ISA_use: Thumb-1
  Tag_FP_arch: VFPv2
  Tag_ABI_PCS_wchar_t: 4
  Tag_ABI_FP_rounding: Needed
  Tag_ABI_FP_denormal: Needed
  Tag_ABI_FP_exceptions: Needed
  Tag_ABI_FP_number_model: IEEE 754
  Tag_ABI_align_needed: 8-byte
  Tag_ABI_align_preserved: 8-byte, except leaf SP
  Tag_ABI_enum_size: int
  Tag_ABI_VFP_args: VFP registers
  Tag_CPU_unaligned_access: v6
  Tag_Virtualization_use: TrustZone

hello-world is ready to work on real hardware. You may also use QEMU to test it:

$ qemu-arm-static -cpu arm1176 -L /home/vscode/sysroot ./hello-world
Hello, World!

Going deeper — the specs

Are names of startup files hardcoded in GCC? Well, no. You may specify your own filenames via so-called specification strings or just spcecs.

In general specs is a set of rules specyfying arguments for subprocesses (compiler, linker etc.) invoked by GCC. Without going into details let's display default specs by invoking GCC with -dumpspecs option:

arm-linux-gnueabihf-gcc -dumpspecs
*asm:
%{mbig-endian:-EB} %{mlittle-endian:-EL} %(asm_cpu_spec) %{mapcs-*:-mapcs-%*} %(subtarget_asm_float_spec) %{mthumb-interwork:-mthumb-interwork} %{mfloat-abi=*} %{!mfpu=auto: %{mfpu=*}} %(subtarget_extra_asm_spec)

*asm_debug:
%{%:debug-level-gt(0):%{gstabs*:--gstabs;:%{g*:%{%:dwarf-version-gt(4):--gdwarf-5;%:dwarf-version-gt(3):--gdwarf-4;%:dwarf-version-gt(2):--gdwarf-3;:--gdwarf2}}}} %{ffile-prefix-map=*:--debug-prefix-map %*} %{fdebug-prefix-map=*:--debug-prefix-map %*}

*asm_debug_option:


*asm_final:
%{gsplit-dwarf: 
       objcopy --extract-dwo   %{c:%{o*:%*}%{!o*:%w%b%O}}%{!c:%U%O}   %b.dwo 
       objcopy --strip-dwo   %{c:%{o*:%*}%{!o*:%w%b%O}}%{!c:%U%O}     }

*asm_options:
%{-target-help:%:print-asm-header()} %{v} %{w:-W} %{I*} %(asm_debug_option) %{gz|gz=zlib:--compress-debug-sections=zlib} %{gz=none:--compress-debug-sections=none} %{gz=zlib-gnu:--compress-debug-sections=zlib-gnu} %a %Y %{c:%W{o*}%{!o*:-o %w%b%O}}%{!c:-o %d%w%u%O}

*invoke_as:
%{!fwpa*:   %{fcompare-debug=*|fdump-final-insns=*:%:compare-debug-dump-opt()}   %{!S:-o %|.s |
 as %(asm_options) %m.s %A }  }

*cpp:
%(subtarget_cpp_spec)

*cpp_options:
%(cpp_unique_options) %1 %{m*} %{std*&ansi&trigraphs} %{W*&pedantic*} %{w} %{f*} %{g*:%{%:debug-level-gt(0):%{g*} %{!fno-working-directory:-fworking-directory}}} %{O*} %{undef} %{save-temps*:-fpch-preprocess} %(distro_defaults)

*cpp_debug_options:
%<dumpdir %<dumpbase %<dumpbase-ext %{d*} %:dumps()

*cpp_unique_options:
%{!Q:-quiet} %{nostdinc*} %{C} %{CC} %{v} %@{I*&F*} %{P} %I %{MD:-MD %{!o:%b.d}%{o*:%.d%*}} %{MMD:-MMD %{!o:%b.d}%{o*:%.d%*}} %{M} %{MM} %{MF*} %{MG} %{MP} %{MQ*} %{MT*} %{Mmodules} %{Mno-modules} %{!E:%{!M:%{!MM:%{!MT:%{!MQ:%{MD|MMD:%{o*:-MQ %*}}}}}}} %{remap} %{%:debug-level-gt(2):-dD} %{!iplugindir*:%{fplugin*:%:find-plugindir()}} %{H} %C %{D*&U*&A*} %{i*} %Z %i %{E|M|MM:%W{o*}}

*trad_capable_cpp:
cc1 -E %{traditional|traditional-cpp:-traditional-cpp}

*cc1:
%{!mandroid|tno-android-cc:%{profile:-p} %{%:sanitize(address):-funwind-tables} ;:%{profile:-p} %{%:sanitize(address):-funwind-tables} %{!mglibc:%{!muclibc:%{!mbionic: -mbionic}}} %{!fno-pic:%{!fno-PIC:%{!fpic:%{!fPIC: -fPIC}}}}}

*cc1_options:
%{pg:%{fomit-frame-pointer:%e-pg and -fomit-frame-pointer are incompatible}} %{!iplugindir*:%{fplugin*:%:find-plugindir()}} %1 %{!Q:-quiet} %(cpp_debug_options) %{m*} %{aux-info*} %{g*} %{O*} %{W*&pedantic*} %{w} %{std*&ansi&trigraphs} %{v:-version} %{pg:-p} %{p} %{f*} %{undef} %{Qn:-fno-ident} %{Qy:} %{-help:--help} %{-target-help:--target-help} %{-version:--version} %{-help=*:--help=%*} %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}} %{fsyntax-only:-o %j} %{-param*} %{coverage:-fprofile-arcs -ftest-coverage} %{fprofile-arcs|fprofile-generate*|coverage:   %{!fprofile-update=single:     %{pthread:-fprofile-update=prefer-atomic}}}

*cc1plus:
%{!mandroid|tno-android-cc:;:%{!fexceptions:%{!fno-exceptions: -fno-exceptions}} %{!frtti:%{!fno-rtti: -fno-rtti}}}

*link_gcc_c_sequence:
%{static|static-pie:--start-group} %G %{!nolibc:%L}    %{static|static-pie:--end-group}%{!static:%{!static-pie:%G}}

*distro_defaults:


*link_ssp:
%{fstack-protector|fstack-protector-all|fstack-protector-strong|fstack-protector-explicit:}

*endfile:
%{Ofast|ffast-math|funsafe-math-optimizations:crtfastmath.o%s} %{!mandroid|tno-android-ld:%{!static:%{fvtable-verify=none:%s;      fvtable-verify=preinit:vtv_end_preinit.o%s;      fvtable-verify=std:vtv_end.o%s}}    %{static:crtend.o%s;      shared|static-pie|!no-pie:crtendS.o%s;      :crtend.o%s} crtn.o%s ;:%{shared: crtend_so%O%s;: crtend_android%O%s}}

*link:
%{!r:--build-id} %{!static|static-pie:--eh-frame-hdr}  %{mcpu=arm8|mcpu=arm810|mcpu=strongarm*|march=armv4|mcpu=fa526|mcpu=fa626:--fix-v4bx}%{!r:%{!mbe32:%:be8_linkopt(%{mlittle-endian:little}          %{mbig-endian:big}          %{mbe8:be8}          %{march=*:arch %*})}}%{!mandroid|tno-android-ld:%{h*}    %{static:-Bstatic}    %{shared:-shared}    %{symbolic:-Bsymbolic}    %{!static:      %{rdynamic:-export-dynamic}      %{!shared:-dynamic-linker %{muclibc:/lib/ld-uClibc.so.0;:%{mbionic:/system/bin/linker;:%{mmusl:/lib/ld-musl-arm%{mbig-endian:eb}%{mfloat-abi=hard:hf}%{mfdpic:-fdpic}.so.1;:%{mfloat-abi=hard:/lib/ld-linux-armhf.so.3}     %{mfloat-abi=soft*:/lib/ld-linux.so.3}     %{!mfloat-abi=*:/lib/ld-linux.so.3}}}}}}    -X    --hash-style=gnu    %{!fsanitize=*:--as-needed}    %{mbig-endian:-EB} %{mlittle-endian:-EL} -m armelf_linux_eabi;:%{h*}    %{static:-Bstatic}    %{shared:-shared}    %{symbolic:-Bsymbolic}    %{!static:      %{rdynamic:-export-dynamic}      %{!shared:-dynamic-linker %{muclibc:/lib/ld-uClibc.so.0;:%{mbionic:/system/bin/linker;:%{mmusl:/lib/ld-musl-arm%{mbig-endian:eb}%{mfloat-abi=hard:hf}%{mfdpic:-fdpic}.so.1;:%{mfloat-abi=hard:/lib/ld-linux-armhf.so.3}     %{mfloat-abi=soft*:/lib/ld-linux.so.3}     %{!mfloat-abi=*:/lib/ld-linux.so.3}}}}}}    -X    --hash-style=gnu    %{!fsanitize=*:--as-needed}    %{mbig-endian:-EB} %{mlittle-endian:-EL} -m armelf_linux_eabi %{shared: -Bsymbolic}}

*lib:
%{!mandroid|tno-android-ld:%{pthread:-lpthread} %{shared:-lc}    %{!shared:%{profile:-lc_p}%{!profile:-lc}};:%{shared:-lc}    %{!shared:%{profile:-lc_p}%{!profile:-lc}} %{!static: -ldl}}

*link_gomp:


*libgcc:
%{static|static-libgcc|static-pie:-lgcc -lgcc_eh}%{!static:%{!static-libgcc:%{!static-pie:%{!shared-libgcc:-lgcc --push-state --as-needed -lgcc_s --pop-state}%{shared-libgcc:-lgcc_s%{!shared: -lgcc}}}}}

*startfile:
%{!mandroid|tno-android-ld:%{shared:;      pg|p|profile:%{static-pie:grcrt1.o%s;:gcrt1.o%s};      static:crt1.o%s;      static-pie:rcrt1.o%s;      !no-pie:Scrt1.o%s;      :crt1.o%s} crti.o%s    %{static:crtbeginT.o%s;      shared|static-pie|!no-pie:crtbeginS.o%s;      :crtbegin.o%s}    %{fvtable-verify=none:%s;      fvtable-verify=preinit:vtv_start_preinit.o%s;      fvtable-verify=std:vtv_start.o%s} ;:%{shared: crtbegin_so%O%s;:  %{static: crtbegin_static%O%s;: crtbegin_dynamic%O%s}}}

*cross_compile:
1

*version:
12.2.0

*multilib:
.::arm-linux-gnueabihf ;

*multilib_defaults:


*multilib_extra:


*multilib_matches:


*multilib_exclusions:


*multilib_options:


*multilib_reuse:


*linker:
collect2

*linker_plugin_file:


*lto_wrapper:


*lto_gcc:


*post_link:


*link_libgcc:
%D

*md_exec_prefix:


*md_startfile_prefix:


*md_startfile_prefix_1:


*startfile_prefix_spec:


*sysroot_spec:
--sysroot=%R

*sysroot_suffix_spec:


*sysroot_hdrs_suffix_spec:


*self_spec:


*subtarget_cpp_spec:
%{posix:-D_POSIX_SOURCE} %{pthread:-D_REENTRANT}

*asm_cpu_spec:
 %{mfpu=auto:%<mfpu=auto %:asm_auto_mfpu(%{march=*: arch %*})} %{mcpu=generic-*:-march=%:rewrite_march(%{mcpu=generic-*:%*});   march=*:-march=%:rewrite_march(%{march=*:%*});   mcpu=*:-mcpu=%:rewrite_mcpu(%{mcpu=*:%*}) }

*subtarget_extra_asm_spec:
%{mabi=apcs-gnu|mabi=atpcs:-meabi=gnu;:-meabi=5} %{mcpu=arm8|mcpu=arm810|mcpu=strongarm*|march=armv4|mcpu=fa526|mcpu=fa626:--fix-v4bx}

*subtarget_asm_float_spec:
%{mapcs-float:-mfloat}

*link_command:
%{!fsyntax-only:%{!c:%{!M:%{!MM:%{!E:%{!S:    %(linker) %{!fno-use-linker-plugin:%{!fno-lto:     -plugin %(linker_plugin_file)     -plugin-opt=%(lto_wrapper)     -plugin-opt=-fresolution=%u.res         %{flinker-output=*:-plugin-opt=-linker-output-known}     %{!nostdlib:%{!nodefaultlibs:%:pass-through-libs(%(link_gcc_c_sequence))}}     }}%{flto|flto=*:%<fcompare-debug*}     %{flto} %{fno-lto} %{flto=*} %l %{static|shared|r:;!no-pie:-pie} %{fuse-ld=*:-fuse-ld=%*}  %{gz|gz=zlib:--compress-debug-sections=zlib} %{gz=none:--compress-debug-sections=none} %{gz=zlib-gnu:--compress-debug-sections=zlib-gnu} %X %{o*} %{e*} %{N} %{n} %{r}    %{s} %{t} %{u*} %{z} %{Z} %{!nostdlib:%{!r:%{!nostartfiles:%S}}}     %{static|no-pie|static-pie:} %@{L*} %(mfwrap) %(link_libgcc) %{fvtable-verify=none:} %{fvtable-verify=std:   %e-fvtable-verify=std is not supported in this configuration} %{fvtable-verify=preinit:   %e-fvtable-verify=preinit is not supported in this configuration} %{!nostdlib:%{!r:%{!nodefaultlibs:%{%:sanitize(address):%{!shared:libasan_preinit%O%s} %{static-libasan:%{!shared:-Bstatic --whole-archive -lasan --no-whole-archive -Bdynamic}}%{!static-libasan:--push-state --no-as-needed -lasan --pop-state}}     %{%:sanitize(hwaddress):%{static-libhwasan:%{!shared:-Bstatic --whole-archive -lhwasan --no-whole-archive -Bdynamic}}%{!static-libhwasan:--push-state --no-as-needed -lhwasan --pop-state}}     %{%:sanitize(thread):%{!shared:libtsan_preinit%O%s} %{static-libtsan:%{!shared:-Bstatic --whole-archive -ltsan --no-whole-archive -Bdynamic}}%{!static-libtsan:--push-state --no-as-needed -ltsan --pop-state}}     %{%:sanitize(leak):%{!shared:liblsan_preinit%O%s} %{static-liblsan:%{!shared:-Bstatic --whole-archive -llsan --no-whole-archive -Bdynamic}}%{!static-liblsan:--push-state --no-as-needed -llsan --pop-state}}}}} %o      %{fopenacc|fopenmp|%:gt(%{ftree-parallelize-loops=*:%*} 1): %:include(libgomp.spec)%(link_gomp)}    %{fgnu-tm:%:include(libitm.spec)%(link_itm)}    %(mflib)  %{fsplit-stack: --wrap=pthread_create}    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} %{!nostdlib:%{!r:%{!nodefaultlibs:%{%:sanitize(address): %{static-libasan|static:%:include(libsanitizer.spec)%(link_libasan)}    %{static:%ecannot specify -static with -fsanitize=address}}    %{%:sanitize(hwaddress): %{static-libhwasan|static:%:include(libsanitizer.spec)%(link_libhwasan)} %{static:%ecannot specify -static with -fsanitize=hwaddress}}    %{%:sanitize(thread): %{static-libtsan|static:%:include(libsanitizer.spec)%(link_libtsan)}    %{static:%ecannot specify -static with -fsanitize=thread}}    %{%:sanitize(undefined):%{static-libubsan:-Bstatic} %{!static-libubsan:--push-state --no-as-needed} -lubsan  %{static-libubsan:-Bdynamic} %{!static-libubsan:--pop-state} %{static-libubsan|static:%:include(libsanitizer.spec)%(link_libubsan)}}    %{%:sanitize(leak): %{static-liblsan|static:%:include(libsanitizer.spec)%(link_liblsan)}}}}}     %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}    %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  
%(post_link) }}}}}}

The syntax of the specs is rather complicated but one thing should be rather clear. All lines like *cpp: or *link: begins something. These are beginings of named spec string definitions. All lines after such directive up to the next directive or blank line are considered to be the text for the spec string. We're focusing here on two definitions only: *startfile: and *endfile:. Below are extracted definitions of these specs. Save them to the specs.txt file.

*startfile:
%{!mandroid|tno-android-ld:%{shared:;      pg|p|profile:%{static-pie:grcrt1.o%s;:gcrt1.o%s};      static:crt1.o%s;      static-pie:rcrt1.o%s;      !no-pie:Scrt1.o%s;      :crt1.o%s} crti.o%s    %{static:crtbeginT.o%s;      shared|static-pie|!no-pie:crtbeginS.o%s;      :crtbegin.o%s}    %{fvtable-verify=none:%s;      fvtable-verify=preinit:vtv_start_preinit.o%s;      fvtable-verify=std:vtv_start.o%s} ;:%{shared: crtbegin_so%O%s;:  %{static: crtbegin_static%O%s;: crtbegin_dynamic%O%s}}}

*endfile:
%{Ofast|ffast-math|funsafe-math-optimizations:crtfastmath.o%s} %{!mandroid|tno-android-ld:%{!static:%{fvtable-verify=none:%s;      fvtable-verify=preinit:vtv_end_preinit.o%s;      fvtable-verify=std:vtv_end.o%s}}    %{static:crtend.o%s;      shared|static-pie|!no-pie:crtendS.o%s;      :crtend.o%s} crtn.o%s ;:%{shared: crtend_so%O%s;: crtend_android%O%s}}

You can easly spot names of startup files in these definitions: crtbegin.o%s, crti.o%s, crtend.o%s, crtn.o%s etc. The %s suffix is just indicator to GCC to expand preceding filename to a full path. You may replace these filenames with yours and pass modified specs to GCC using -specs=<specs-file> command line option.

Note

--specs command line option may be used multiple times.

So let's modify our specs.txt file a bit. Prefix rpi- to all names of startup files:

sed -E -i 's/([[:alpha:]]*crt[[:alnum:]_]*)(\.o|%O)(%s)/rpi-\1%O\3/g' specs.txt

Note

The %O suffix is replaced by GCC with .o. E.g. crtn%O will be converted to crtn.o.

Then link hello-world application again using prepared specs file:

$ arm-linux-gnueabihf-gcc -specs=specs.txt --sysroot=/home/vscode/sysroot -L=/usr/lib -L=/usr/lib/gcc -marm -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -o hello-world hello-world.o
/usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/bin/ld: cannot find rpi-Scrt1.o: No such file or directory
/usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/bin/ld: cannot find rpi-crti.o: No such file or directory
/usr/lib/gcc-cross/arm-linux-gnueabihf/12/../../../../arm-linux-gnueabihf/bin/ld: cannot find rpi-crtbeginS.o: No such file or directory
collect2: error: ld returned 1 exit status

Linker complains about missing startup files prefixed with rpi-. So our modified specs definitely were taken into account 👍.

To satisfy linker let's create some rpi-prefixed symlinks:

cd ~/sysroot/usr/lib
ln -sr Scrt1.so rpi-Scrt1.so
ln -sr crti.o rpi-crti.o
ln -sr crtbeginS.o rpi-crtbeginS.o
ln -sr Scrt1.o rpi-Scrt1.o
ln -sr crtendS.o rpi-crtendS.o
ln -sr crtn.o rpi-crtn.o

And then invoke linker again:

$ arm-linux-gnueabihf-gcc -specs=specs.txt --sysroot=/home/vscode/sysroot -L=/usr/lib -L=/usr/lib/gcc -marm -mcpu=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp -Wl,--trace -o hello-world hello-world.o
/home/vscode/sysroot/lib/arm-linux-gnueabihf/rpi-Scrt1.o
/home/vscode/sysroot/lib/arm-linux-gnueabihf/rpi-crti.o
/home/vscode/sysroot/lib/arm-linux-gnueabihf/rpi-crtbeginS.o
hello-world.o
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/gcc/libgcc_s.so
/home/vscode/sysroot/usr/lib/libgcc_s.so.1
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/libc.so
/home/vscode/sysroot/lib/arm-linux-gnueabihf/libc.so.6
/home/vscode/sysroot/usr/lib/arm-linux-gnueabihf/libc_nonshared.a
/home/vscode/sysroot/lib/ld-linux-armhf.so.3
/home/vscode/sysroot/usr/lib/arm-linux-gnueabihf/libc_nonshared.a
/home/vscode/sysroot/lib/ld-linux-armhf.so.3
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/usr/lib/gcc/libgcc_s.so
/home/vscode/sysroot/usr/lib/libgcc_s.so.1
/home/vscode/sysroot/usr/lib/gcc/libgcc.a
/home/vscode/sysroot/lib/arm-linux-gnueabihf/rpi-crtendS.o
/home/vscode/sysroot/lib/arm-linux-gnueabihf/rpi-crtn.o

Now everything is going fine 😆. You have idea now how to specify non-default names of startup files in GCC.

Note

With specs it is possible to use startup files from custom directory also. Just instead of %s suffix specify full path to startup file. In such case consider using getenv function and specify custom directory via environment variable:

%:getenv(RPIDIR /objects/)crtn%O

Documentation

Here we covering only one little aspect of specs in GCC. Please refer to the following links for more information: