Skip to content

Commit 2646dcc

Browse files
authoredFeb 19, 2024··
[BOLT] Add support for Linux kernel static calls table (#82072)
Static calls are calls that are getting patched during runtime. Hence, for every such call the kernel runtime needs the location of the call or jmp instruction that will be patched. Instruction locations together with a corresponding key are stored in the static call site table. As BOLT rewrites these instructions it needs to update the table.
1 parent 7970949 commit 2646dcc

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed
 

‎bolt/lib/Rewrite/LinuxKernelRewriter.cpp

+155
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ static cl::opt<bool>
3535
DumpORC("dump-orc", cl::desc("dump raw ORC unwind information (sorted)"),
3636
cl::init(false), cl::Hidden, cl::cat(BoltCategory));
3737

38+
static cl::opt<bool> DumpStaticCalls("dump-static-calls",
39+
cl::desc("dump Linux kernel static calls"),
40+
cl::init(false), cl::Hidden,
41+
cl::cat(BoltCategory));
42+
3843
} // namespace opts
3944

4045
/// Linux Kernel supports stack unwinding using ORC (oops rewind capability).
@@ -116,6 +121,19 @@ class LinuxKernelRewriter final : public MetadataRewriter {
116121
/// Number of entries in the input file ORC sections.
117122
uint64_t NumORCEntries = 0;
118123

124+
/// Section containing static call table.
125+
ErrorOr<BinarySection &> StaticCallSection = std::errc::bad_address;
126+
uint64_t StaticCallTableAddress = 0;
127+
static constexpr size_t STATIC_CALL_ENTRY_SIZE = 8;
128+
129+
struct StaticCallInfo {
130+
uint32_t ID; /// Identifier of the entry in the table.
131+
BinaryFunction *Function; /// Function containing associated call.
132+
MCSymbol *Label; /// Label attached to the call.
133+
};
134+
using StaticCallListType = std::vector<StaticCallInfo>;
135+
StaticCallListType StaticCallEntries;
136+
119137
/// Insert an LKMarker for a given code pointer \p PC from a non-code section
120138
/// \p SectionName.
121139
void insertLKMarker(uint64_t PC, uint64_t SectionOffset,
@@ -152,6 +170,10 @@ class LinuxKernelRewriter final : public MetadataRewriter {
152170
/// Update ORC data in the binary.
153171
Error rewriteORCTables();
154172

173+
/// Static call table handling.
174+
Error readStaticCalls();
175+
Error rewriteStaticCalls();
176+
155177
/// Mark instructions referenced by kernel metadata.
156178
Error markInstructions();
157179

@@ -167,6 +189,9 @@ class LinuxKernelRewriter final : public MetadataRewriter {
167189
if (Error E = readORCTables())
168190
return E;
169191

192+
if (Error E = readStaticCalls())
193+
return E;
194+
170195
return Error::success();
171196
}
172197

@@ -181,6 +206,9 @@ class LinuxKernelRewriter final : public MetadataRewriter {
181206
if (Error E = rewriteORCTables())
182207
return E;
183208

209+
if (Error E = rewriteStaticCalls())
210+
return E;
211+
184212
return Error::success();
185213
}
186214

@@ -793,6 +821,133 @@ Error LinuxKernelRewriter::rewriteORCTables() {
793821
return Error::success();
794822
}
795823

824+
/// The static call site table is created by objtool and contains entries in the
825+
/// following format:
826+
///
827+
/// struct static_call_site {
828+
/// s32 addr;
829+
/// s32 key;
830+
/// };
831+
///
832+
Error LinuxKernelRewriter::readStaticCalls() {
833+
const BinaryData *StaticCallTable =
834+
BC.getBinaryDataByName("__start_static_call_sites");
835+
if (!StaticCallTable)
836+
return Error::success();
837+
838+
StaticCallTableAddress = StaticCallTable->getAddress();
839+
840+
const BinaryData *Stop = BC.getBinaryDataByName("__stop_static_call_sites");
841+
if (!Stop)
842+
return createStringError(errc::executable_format_error,
843+
"missing __stop_static_call_sites symbol");
844+
845+
ErrorOr<BinarySection &> ErrorOrSection =
846+
BC.getSectionForAddress(StaticCallTableAddress);
847+
if (!ErrorOrSection)
848+
return createStringError(errc::executable_format_error,
849+
"no section matching __start_static_call_sites");
850+
851+
StaticCallSection = *ErrorOrSection;
852+
if (!StaticCallSection->containsAddress(Stop->getAddress() - 1))
853+
return createStringError(errc::executable_format_error,
854+
"__stop_static_call_sites not in the same section "
855+
"as __start_static_call_sites");
856+
857+
if ((Stop->getAddress() - StaticCallTableAddress) % STATIC_CALL_ENTRY_SIZE)
858+
return createStringError(errc::executable_format_error,
859+
"static call table size error");
860+
861+
const uint64_t SectionAddress = StaticCallSection->getAddress();
862+
DataExtractor DE(StaticCallSection->getContents(),
863+
BC.AsmInfo->isLittleEndian(),
864+
BC.AsmInfo->getCodePointerSize());
865+
DataExtractor::Cursor Cursor(StaticCallTableAddress - SectionAddress);
866+
uint32_t EntryID = 0;
867+
while (Cursor && Cursor.tell() < Stop->getAddress() - SectionAddress) {
868+
const uint64_t CallAddress =
869+
SectionAddress + Cursor.tell() + (int32_t)DE.getU32(Cursor);
870+
const uint64_t KeyAddress =
871+
SectionAddress + Cursor.tell() + (int32_t)DE.getU32(Cursor);
872+
873+
// Consume the status of the cursor.
874+
if (!Cursor)
875+
return createStringError(errc::executable_format_error,
876+
"out of bounds while reading static calls");
877+
878+
++EntryID;
879+
880+
if (opts::DumpStaticCalls) {
881+
outs() << "Static Call Site: " << EntryID << '\n';
882+
outs() << "\tCallAddress: 0x" << Twine::utohexstr(CallAddress) << '\n'
883+
<< "\tKeyAddress: 0x" << Twine::utohexstr(KeyAddress) << '\n';
884+
}
885+
886+
BinaryFunction *BF = BC.getBinaryFunctionContainingAddress(CallAddress);
887+
if (!BF)
888+
continue;
889+
890+
if (!BC.shouldEmit(*BF))
891+
continue;
892+
893+
if (!BF->hasInstructions())
894+
continue;
895+
896+
MCInst *Inst = BF->getInstructionAtOffset(CallAddress - BF->getAddress());
897+
if (!Inst)
898+
return createStringError(errc::executable_format_error,
899+
"no instruction at call site address 0x%" PRIx64,
900+
CallAddress);
901+
902+
// Check for duplicate entries.
903+
if (BC.MIB->hasAnnotation(*Inst, "StaticCall"))
904+
return createStringError(errc::executable_format_error,
905+
"duplicate static call site at 0x%" PRIx64,
906+
CallAddress);
907+
908+
BC.MIB->addAnnotation(*Inst, "StaticCall", EntryID);
909+
910+
MCSymbol *Label = BC.MIB->getLabel(*Inst);
911+
if (!Label) {
912+
Label = BC.Ctx->createTempSymbol("__SC_");
913+
BC.MIB->setLabel(*Inst, Label);
914+
}
915+
916+
StaticCallEntries.push_back({EntryID, BF, Label});
917+
}
918+
919+
outs() << "BOLT-INFO: parsed " << StaticCallEntries.size()
920+
<< " static call entries\n";
921+
922+
return Error::success();
923+
}
924+
925+
/// The static call table is sorted during boot time in
926+
/// static_call_sort_entries(). This makes it possible to update existing
927+
/// entries in-place ignoring their relative order.
928+
Error LinuxKernelRewriter::rewriteStaticCalls() {
929+
if (!StaticCallTableAddress || !StaticCallSection)
930+
return Error::success();
931+
932+
for (auto &Entry : StaticCallEntries) {
933+
if (!Entry.Function)
934+
continue;
935+
936+
BinaryFunction &BF = *Entry.Function;
937+
if (!BC.shouldEmit(BF))
938+
continue;
939+
940+
// Create a relocation against the label.
941+
const uint64_t EntryOffset = StaticCallTableAddress -
942+
StaticCallSection->getAddress() +
943+
(Entry.ID - 1) * STATIC_CALL_ENTRY_SIZE;
944+
StaticCallSection->addRelocation(EntryOffset, Entry.Label,
945+
ELF::R_X86_64_PC32, /*Addend*/ 0);
946+
}
947+
948+
return Error::success();
949+
}
950+
796951
} // namespace
797952

798953
std::unique_ptr<MetadataRewriter>

‎bolt/test/X86/linux-static-calls.s

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# REQUIRES: system-linux
2+
3+
## Check that BOLT correctly updates the Linux kernel static calls table.
4+
5+
# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-unknown %s -o %t.o
6+
# RUN: %clang %cflags -nostdlib %t.o -o %t.exe \
7+
# RUN: -Wl,--image-base=0xffffffff80000000,--no-dynamic-linker,--no-eh-frame-hdr
8+
9+
## Verify static calls bindings to instructions.
10+
11+
# RUN: llvm-bolt %t.exe --print-normalized -o %t.out --keep-nops=0 \
12+
# RUN: --bolt-info=0 |& FileCheck %s
13+
14+
## Verify the bindings again on the rewritten binary with nops removed.
15+
16+
# RUN: llvm-bolt %t.out -o %t.out.1 --print-normalized |& FileCheck %s
17+
18+
# CHECK: BOLT-INFO: Linux kernel binary detected
19+
# CHECK: BOLT-INFO: parsed 2 static call entries
20+
21+
.text
22+
.globl _start
23+
.type _start, %function
24+
_start:
25+
# CHECK: Binary Function "_start"
26+
nop
27+
.L0:
28+
call foo
29+
# CHECK: callq foo # {{.*}} StaticCall: 1
30+
nop
31+
.L1:
32+
jmp foo
33+
# CHECK: jmp foo # {{.*}} StaticCall: 2
34+
.size _start, .-_start
35+
36+
.globl foo
37+
.type foo, %function
38+
foo:
39+
ret
40+
.size foo, .-foo
41+
42+
43+
## Static call table.
44+
.rodata
45+
.globl __start_static_call_sites
46+
.type __start_static_call_sites, %object
47+
__start_static_call_sites:
48+
.long .L0 - .
49+
.long 0
50+
.long .L1 - .
51+
.long 0
52+
53+
.globl __stop_static_call_sites
54+
.type __stop_static_call_sites, %object
55+
__stop_static_call_sites:
56+
57+
## Fake Linux Kernel sections.
58+
.section __ksymtab,"a",@progbits
59+
.section __ksymtab_gpl,"a",@progbits

0 commit comments

Comments
 (0)
Please sign in to comment.