Fix a Build Breakage on Inline Function
I’ve done a lot of work on u-boot-2010.09 for MINI2440 to make
development process more comfortable, unfortunately, the archived u-boot images
were deleted by accident, so I try to do a fresh build, also the arm-linux-gcc
previously used was also lost (coincidence?), and I decided to use the one I get
from Linaro’s website, the version is 7.5.0:
$ arm-linux-gnueabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-linux-gnueabi-gcc
COLLECT_LTO_WRAPPER=/usr/local/arm/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabi/bin/../libexec/gcc/arm-linux-gnueabi/7.5.0/lto-wrapper
Target: arm-linux-gnueabi
Configured with: '/home/tcwg-buildslave/workspace/tcwg-make-release_0/snapshots/gcc.git~linaro-7.5-2019.12/configure' SHELL=/bin/bash --with-mpc=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu --with-mpfr=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gmp=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu --with-gnu-as --with-gnu-ld --disable-libmudflap --enable-lto --enable-shared --without-included-gettext --enable-nls --with-system-zlib --disable-sjlj-exceptions --enable-gnu-unique-object --enable-linker-build-id --disable-libstdcxx-pch --enable-c99 --enable-clocale=gnu --enable-libstdcxx-debug --enable-long-long --with-cloog=no --with-ppl=no --with-isl=no --disable-multilib --with-float=soft --with-mode=thumb --with-tune=cortex-a9 --with-arch=armv7-a --enable-threads=posix --enable-multiarch --enable-libstdcxx-time=yes --enable-gnu-indirect-function --with-build-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/sysroots/arm-linux-gnueabi --with-sysroot=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu/arm-linux-gnueabi/libc --enable-checking=release --disable-bootstrap --enable-languages=c,c++,fortran,lto --build=x86_64-unknown-linux-gnu --host=x86_64-unknown-linux-gnu --target=arm-linux-gnueabi --prefix=/home/tcwg-buildslave/workspace/tcwg-make-release_0/_build/builds/destdir/x86_64-unknown-linux-gnu
Thread model: posix
gcc version 7.5.0 (Linaro GCC 7.5-2019.12)
I expect it build without any problem, as it is proved to be working perfectly date back to year 2015 (the last commit I submitted).
But life is not always in sunshine, that’s the way we learn from our experience, the following failures are shown during the build process:
#1
board.c:112:6: error: ‘coloured_LED_init’ aliased to external symbol ‘__coloured_LED_init’
void coloured_LED_init (void) __attribute__((weak, alias("__coloured_LED_init")));
#2
main.c:51:6: error: ‘show_boot_progress’ aliased to external symbol ‘__show_boot_progress’
void show_boot_progress (int val) __attribute__((weak, alias("__show_boot_progress")));
^~~~~~~~~~~~~~~~~~
#3
drivers/mtd/nand/libnand.a(nand_base.o): In function `__raw_writesw':
/tmp/u-boot-2010.09/include/asm/io.h:86: multiple definition of `__raw_writesw'
arch/arm/cpu/arm920t/s3c24x0/libs3c24x0.a(interrupts.o):/tmp/u-boot-2010.09/include/asm/io.h:86: first defined here
The first two breakages are the same, and the last one is a little bit different,
and is also inline
related, at first I don’t want to spending too much time on
it, so I googled and found the following two patches in the list:
- ARM: fix build error with gcc-4.4.2 about inline function declared weak
- Remove inline qualifier from show_boot_progress()
The error messages are not the same, I assume it is because the GCC version is different, these two patches fixed the first two issues, the first one without any explanation, and the second one sent by Emil Medve tells the reason as follows:
The ‘inline’ is conflicting with the semantic of ‘weak’ attribute and with the way the show_boot_progress() function is used.
It’s inline related, but I have no idea about what the confliction is, as inline
is used to direct GCC to make calls to that function faster, for my case these two
functions is not used, so removing inline
is acceptable for me, but if they are
used, the speed advantage is lost, so do we have other solutions for this kind of
issues.
Finding Nemo
Let’s get started with the keyword inline
, according to the GCC manual,
there are two implementations regarding this scenario with different semantics,
the third one is for c++, which will not be discussed here:
- -std=gnu89
- -std=c99, -std=c11, -std=gnu99 or -std=gnu11
At this point I guess the GCC will always use the latest available standard which makes the two versions of GCC I used using different standard by default, my guess is right, the default standard for GCC varies between versions, the two versions used here, it is gnu89 and gnu11 respectively:
GCC | Standards |
---|---|
4.3.2 | gnu89 |
7.5.0 | gnu11 |
Hint: search for The default, if no C language dialect options are given.
The GCC manual (both version) also described that there are two cases
which both types of inlining behave similarly, the first case is as its example,
when the inline
keyword is used on a static function:
static inline int
inc (int *a)
{
return (*a)++;
}
This is the relevant code in U-Boot is:
void inline __show_boot_progress (int val) {}
void show_boot_progress (int val) __attribute__((weak, alias("__show_boot_progress")));
So try to define show_boot_progress
as static to see the result (#2), both version
works without any problem:
# 7.5.0
$ arm-linux-gnueabi-gcc -c board.c -o board.o && arm-linux-gnueabi-nm board.o
00000000 t __show_boot_progress
00000000 W show_boot_progress
# 4.3.2
$ arm-linux-gcc -c board.c -o board.o && arm-linux-nm board.o
00000000 t __show_boot_progress
00000000 W show_boot_progress
The other one is when a function is first declared without using the inline
keyword
and then is defined with inline
, like this:
extern int inc (int *a);
inline int
inc (int *a)
{
return (*a)++;
}
This also works for both version (see #3)
# 7.5.0
$ arm-linux-gnueabi-gcc -c board.c -o board.o && arm-linux-gnueabi-nm board.o
00000000 T __show_boot_progress
00000000 W show_boot_progress
# 4.3.2
$ arm-linux-gcc -c board.c -o board.o && arm-linux-nm board.o
00000000 T __show_boot_progress
00000000 W show_boot_progress
As this is caused by using different standard in GCC, here comes another solution
(#4), use gnu89
for new GCC:
$ arm-linux-gnueabi-gcc -std=gnu89 -c board.c -o board.o && arm-linux-gnueabi-nm board.o
Finding Dory
As for now, there are three different solutions for this issue, but the question is still unanswered:
- what’s the difference between the two standard on non-static inline functions?
- What is the confliction between
inline
andweak
semantics?
The Wikipedia Inline function page tells the differences between inline semantic between these two standard.
In C99, a function defined inline will never emit an externally visible function. and a function defined extern inline will always emit an externally visible function.
In gnu89, a function defined inline will always emit an externally visible function. and a function defined extern inline will never emit an externally visible function.
Taking the following for example:
cat foo.c
inline void foo() {}
# gnu99, nm shows nothing:
$ arm-linux-gnueabi-gcc -c foo.c -o foo.o && arm-linux-gnueabi-nm foo.o
# gnu89
$ arm-linux-gcc -c foo.c -o foo.o && arm-linux-nm foo.o
00000000 T foo
We cannot find symbol foo
, let’s make it more clearer with below example:
void inline foo(int val) { }
int main() { foo(0xdeee); }
The results of readlef and nm are the same, foo does not exist:
$ arm-linux-gnueabi-readelf -s foo.o
Symbol table '.symtab' contains 12 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS foo.c
2: 00000000 0 SECTION LOCAL DEFAULT 1
3: 00000000 0 SECTION LOCAL DEFAULT 3
4: 00000000 0 SECTION LOCAL DEFAULT 4
5: 00000000 0 NOTYPE LOCAL DEFAULT 1 $a
6: 00000024 0 NOTYPE LOCAL DEFAULT 1 $d
7: 00000000 0 SECTION LOCAL DEFAULT 6
8: 00000000 0 SECTION LOCAL DEFAULT 5
9: 00000000 0 SECTION LOCAL DEFAULT 7
10: 00000000 40 FUNC GLOBAL DEFAULT 1 main
11: 00000000 0 NOTYPE GLOBAL DEFAULT UND foo
$ arm-linux-gnueabi-nm -s foo.o
U foo
00000000 T main
Now let’s see the extern
case:
extern inline void foo() {}
# gnu99
$ arm-linux-gnueabi-gcc -c foo.c -o foo.o && arm-linux-gnueabi-nm foo.o
00000000 T foo
# gnu89 shows nothing
$ arm-linux-gcc -c foo.c -o foo.o && arm-linux-nm foo.o
This is exactly what’s described in Wikipedia.
The meaning of letters before the symbol are described in nm
manual:
If lowercase, the symbol is usually local; if uppercase, the symbol is global (external). There are however a few lowercase symbols that are shown for special global symbols (“u”, “v” and “w”).
For the second keyword weak, the only information seems useful in GCC manual is this:
The weak attribute causes the declaration to be emitted as a weak symbol rather than a global.
The last is alias
, the alias attribute is just as it indicates, it give a symbol
another name, according to section 6.31.1 Common Function Attributes of
GCC manual, the target (__show_boot_progress
for this case)
must be in the same translation unit.
This seems conflict with the inline in C99, and matches the error message, rebuild
board.c with weak
attribute remove report the same error, so this is the root
cause of this problem.
ARM Compiler toolchain Compiler Reference also confirms this requirement:
Where a function is defined in the current translation unit, the alias call is replaced by a call to the function, and the alias is emitted alongside the original name.
Where a function is not defined in the current translation unit, the alias call is replaced by a call to the real function.
Where a function is defined as static, the function name is replaced by the alias name and the function is declared external if the alias name is declared external.
Terms referred in this section:
- Translation unit roughly consists of a source file after it has been processed by the C preprocessor, meaning that header files listed in #include directives are literally included, sections of code within #ifndef may be included, and macros have been expanded.
Solutions
#1 The one in the list:
void __show_boot_progress (int val) {}
void show_boot_progress (int val) __attribute__((weak, alias("__show_boot_progress")));
#2 Use inline on static function
static void inline __show_boot_progress (int val) {}
void show_boot_progress (int val) __attribute__((weak, alias("__show_boot_progress")));
#3 Declare function without inline
, then define it with inline
:
extern void __show_boot_progress (int val);
void inline __show_boot_progress (int val) { }
void show_boot_progress (int val) __attribute__((weak, alias("__show_boot_progress")));
#4 Change GCC to use gnu89
standard:
$ arm-linux-gnueabi-gcc -std=gnu89 -c board.c -o board.o && arm-linux-gnueabi-nm board.o
00000000 T __show_boot_progress
00000000 W show_boot_progress
# To only affect inlining, use -fgnu89-inline
$ arm-linux-gnueabi-gcc -fgnu89-inline -c board.c -o board.o && arm-linux-gnueabi-nm board.o
00000000 T __show_boot_progress
00000000 W show_boot_progress
References
- Nemo’s Answer to Is “inline” without “static” or “extern” ever useful in C99?
- Lundin shared the history of c standard in stackoverflow.
- Jan Faigl has made a presentation on the differences between C89 and C99.
- GNU inline vs. C99 inline
- Myth and reality about inline in C99
- C99 Rationale V5.10