This article has been translated from Chinese by ChatGPT, and the wording may not be entirely accurate.
Addressing Linux Kernel Section GC Failure Issues - Part 2
Overview
This article is part of the Addressing Linux Kernel Section GC Failure Issues series.
- Section GC Analysis - Part 1 Introduction to the Principle
- Section GC Analysis - Part 2 Gold Source Code Analysis
- Section GC Analysis - Part 3 Reference Construction Process
- Addressing Linux Kernel Section GC Failure Issues - Part 1
- Addressing Linux Kernel Section GC Failure Issues - Part 2
The previous articles introduced the usage and principles of Section GC, as well as the Section GC failure problem in the Linux kernel.
To thoroughly solve this problem, we need to ensure that .pushsection
can correctly establish reference relationships, avoiding the use of forced retention to prevent dependency inversion.
After reviewing documentation and community discussions, we have summarized two methods that can manually establish reference relationships.
Solutions
Section Group
In the same section group, if one section is retained, then all sections in the group will be kept. This is a powerful tool for solving the Section GC failure.
The section group to which a section belongs can be specified when creating the section in assembly.
.section name , "flags"G, @type, GroupName[, linkage]
Or use the flag ?
to follow the parent section’s group.
.section name , "flags"?
The current idea is:
- Add the section group attribute to the parent function.
- Use flag
?
to make Pushed Sections follow the parent Section’s group.
This way, both sections can be kept simultaneously.
There are two methods to complete the first step of adding the section group attribute to a C function.
- Method 1:
Use the assembly directive .attach_to_group name
to add a group to a section after it has been created.
int fun1() {
asm(".attach_to_group \"MyGroup\"");
asm(".pushsection .test,\"ax?\",@progbits\n"
".string \"hello\"\n"
".popsection");
return 0;
}
int main() {
fun1();
return 0;
}
$ riscv64-linux-gnu-gcc -ffunction-sections -Wl,--gc-sections example.c -c
$ riscv64-linux-gnu-readelf -g example.o
group section [ 1] `.group' [MyGroup] contains 2 sections:
[Index] Name
[ 5] .text.fun1
[ 6] .test
$ riscv64-linux-gnu-gcc -ffunction-sections -Wl,--gc-sections,--print-gc-sections example.c
ld: removing unused section '.rodata.cst4' in file '/usr/riscv64-linux-gnu/usr/lib/Scrt1.o'
ld: removing unused section '.riscv.attributes' in file '/usr/lib/gcc/riscv64-linux-gnu/12.2.0/crti.o'
ld: removing unused section '.riscv.attributes' in file '/usr/lib/gcc/riscv64-linux-gnu/12.2.0/crtn.o'
.test
was not GCed and was retained along with the group’s .text.main
.
- Method 2:
This method is not recommended.
Write assembly within __attribute__((section("section-name")))
, include flag and GroupName, then manually add #
to truncate the subsequent assembly code generated by the compiler in a manner similar to SQL injection.
int __attribute__((section(".text.fun1,\"axG\",@progbits,\"MyGroup\" #")))fun1()
{
asm(".pushsection .test1,\"axG\",@progbits,\"MyGroup\"\n"
" .string \"hello\"\n"
".popsection");
return 0;
}
The generated assembly code is:
.section .text.fun1,"axG",@progbits,"MyGroup" #,"ax",@progbits
The assembler actually processes:
.section .text.fun1,"axG",@progbits,"MyGroup"
Flawed Solutions
As mentioned in the previous article, __ex_table
’s .pushsection
introduced a problem of dependency inversion, which is defined as follows:
// arch/riscv/include/asm/asm-extable.h:14
#define __ASM_EXTABLE_RAW(insn, fixup, type, data) \
".pushsection __ex_table, \"a\"\n" \
".balign 4\n" \
".long (( insn) - .)\n" \
".long (( fixup) - .)\n" \
".short ( type)\n" \
".short ( data)\n" \
".popsection\n"
To retain it with the parent function in a section group, the simplest method is to add .attach_to_group "GroupName"
directly into the macro, and add ?
to the flag in .pushsection
:
#define __ASM_EXTABLE_RAW(insn, fixup, type, data) \
".attach_to_group GroupName" \
".pushsection __ex_table, \"a?\"\n" \
".balign 4\n" \
".long (( insn) - .)\n" \
".long (( fixup) - .)\n" \
".short ( type)\n" \
".short ( data)\n" \
".popsection\n"
This way, no matter where this macro is expanded, a group will be added for the Section Pusher, and the .pushsection
will be in the same group.
Each Section Pusher should be in a different group; otherwise, if one is retained, other unused Section Pushers will also be retained.
What’s more, this macro may expand multiple times within a function, meaning the Section Pusher has called .pushsection
several times, resulting in multiple executions of .attach_to_group
, and linkers will produce a warning. We hope to execute .attach_to_group
only once within a function.
- If only the filename is used as GroupName and ifdefs assist in determining whether the current Section Pusher is already in a group, if not, then
.attach_to_group
. Then, all functions in one file would be added to a group and retained or GCed together, which is not satisfactory. - If the filename and line number are used as GroupName, the macro may still expand multiple times within a function and cannot determine whether the current Section Pusher is within a group.
Therefore, we need a function-level unique string to serve as GroupName. Then we could use ifdefs to determine whether the current Section Pusher is already in a group.
Function name is the most readily accessible method, but neither __func__
nor __FUNCTION__
are macros; they are determined during compilation. Therefore, we cannot use the function name as GroupName. At present, we are left with a format similar to the filename and line number.
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
#ifndef __UNIQUE_ID
# define __UNIQUE_ID __PASTE(__PASTE(__COUNTER__, _), __LINE__)
#endif
#define __ASM_EXTABLE_RAW(insn, fixup, type, data) \
".attach_to_group "__stringify(__UNIQUE_ID_Extable)"\n" \
".pushsection __ex_table, \"a?\"\n" \
".balign 4\n" \
".long (( insn) - .)\n" \
".long (( fixup) - .)\n" \
".short ( type)\n" \
".short ( data)\n" \
".popsection\n"
This way, compilation will produce warnings:
Warning: section .text.main already has a group (GroupName)
The kernel community does not accept code with warnings, and the linker does not provide options to turn off this warning, so although this solution solves the problem, it can not be merged into the mainline.
SHF_LINK_ORDER
Reviewing the documentation for as, you can see the definitions of .pushsection
and .section
.
.pushsection name [, subsection] [, "flags"[, @type[,arguments]]]
.section name [, "flags"[, @type[,flag_specific_arguments]]]
One of the flags
meets our requirements:
o
section references a symbol defined in another section (the linked-to section) in the same file.
If flags contains the o flag, then the type argument must be present along with an additional field like this:
.section name,"flags"o,@type,SymbolName|SectionIndex
We can use this flag to manually specify that this section refers to the Section Pusher and establishes a reference.
void unused_function() {
return;
}
int main() {
asm("Section_Pusher_Symbol:\n");
asm(".pushsection .should_not_GC,\"ao\",@progbits,Section_Pusher_Symbol\n"
".popsection");
return 0;
}
$ riscv64-linux-gnu-gcc -ffunction-sections -Wl,--gc-sections,--print-gc-sections example_shf.c
ld: removing unused section '.rodata.cst4' in file '/usr/lib/gcc-cross/riscv64-linux-gnu/11/../../../../riscv64-linux-gnu/lib/Scrt1.o'
ld: removing unused section '.riscv.attributes' in file '/usr/lib/gcc-cross/riscv64-linux-gnu/11/crti.o'
ld: removing unused section '.text.unused_function' in file '/tmp/cceocy7f.o'
ld: removing unused section '.riscv.attributes' in file '/usr/lib/gcc-cross/riscv64-linux-gnu/11/crtn.o'
In this code, we created a label Section_Pusher_Symbol
in main()
, then used the o
flag in .pushsection
, specified to that symbol.
As seen, after adding the o
flag, the pushed section was not GCed, achieving the project goal.
Verification in the Linux Kernel
Linux Lab has integrated Patches based on the above two solutions. Switch to the section-gc branch of Linux Lab to verify.
git checkout section-gc
Enable the dse feature and compile:
make test b=riscv64/virt f=dse LINUX=v6.6-rc2
Check the number of system calls retained:
$ nm build/riscv64/virt/linux/v6.6-rc2/vmlinux | grep "T __riscv_sys" | grep -v sys_ni_syscall | wc -l
40
The result shows that many system calls were trimmed, verifying the feasibility of the solutions.
Conclusion
In this article, we proposed two methods to solve the Linux Kernel Section GC failure problem. They allow all sections to establish the correct dependency relations without side effects, avoiding the use of KEEP
, and provide more information to linkers.