Skip to content

Commit deaf22b

Browse files
committed
[X86] Implement -fzero-call-used-regs option
The "-fzero-call-used-regs" option tells the compiler to zero out certain registers before the function returns. It's also available as a function attribute: zero_call_used_regs. The two upper categories are: - "used": Zero out used registers. - "all": Zero out all registers, whether used or not. The individual options are: - "skip": Don't zero out any registers. This is the default. - "used": Zero out all used registers. - "used-arg": Zero out used registers that are used for arguments. - "used-gpr": Zero out used registers that are GPRs. - "used-gpr-arg": Zero out used GPRs that are used as arguments. - "all": Zero out all registers. - "all-arg": Zero out all registers used for arguments. - "all-gpr": Zero out all GPRs. - "all-gpr-arg": Zero out all GPRs used for arguments. This is used to help mitigate Return-Oriented Programming exploits. Reviewed By: nickdesaulniers Differential Revision: https://reviews.llvm.org/D110869
1 parent dc8f4e1 commit deaf22b

28 files changed

+1151
-0
lines changed

clang/include/clang/Basic/Attr.td

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2564,6 +2564,19 @@ def VectorCall : DeclOrTypeAttr {
25642564
let Documentation = [VectorCallDocs];
25652565
}
25662566

2567+
def ZeroCallUsedRegs : InheritableAttr {
2568+
let Spellings = [GCC<"zero_call_used_regs">];
2569+
let Subjects = SubjectList<[Function], ErrorDiag>;
2570+
let Args = [
2571+
EnumArgument<"ZeroCallUsedRegs", "ZeroCallUsedRegsKind",
2572+
["skip", "used-gpr-arg", "used-gpr", "used-arg", "used",
2573+
"all-gpr-arg", "all-gpr", "all-arg", "all"],
2574+
["Skip", "UsedGPRArg", "UsedGPR", "UsedArg", "Used",
2575+
"AllGPRArg", "AllGPR", "AllArg", "All"]>
2576+
];
2577+
let Documentation = [ZeroCallUsedRegsDocs];
2578+
}
2579+
25672580
def Pascal : DeclOrTypeAttr {
25682581
let Spellings = [Clang<"pascal">, Keyword<"__pascal">, Keyword<"_pascal">];
25692582
// let Subjects = [Function, ObjCMethod];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6270,3 +6270,41 @@ pointing to precise locations of the call site in the source.
62706270
}
62716271
}];
62726272
}
6273+
6274+
def ZeroCallUsedRegsDocs : Documentation {
6275+
let Category = DocCatFunction;
6276+
let Content = [{
6277+
This attribute, when attached to a function, causes the compiler to zero a
6278+
subset of all call-used registers before the function returns. It's used to
6279+
increase program security by either mitigating `Return-Oriented Programming`_
6280+
(ROP) attacks or preventing information leakage through registers.
6281+
6282+
The term "`call-used" means registers which are not guaranteed to be preserved
6283+
unchanged for the caller by the current calling convention. This could also be
6284+
described as "caller-saved" or "not callee-saved".
6285+
6286+
The `choice` parameters gives the programmer flexibility to choose the subset
6287+
of the call-used registers to be zeroed:
6288+
6289+
- ``skip`` doesn't zero any call-used registers. This choice overrides any
6290+
command-line arguments.
6291+
- ``used`` only zeros call-used registers used in the function. By ``used``, we
6292+
mean a register whose contents have been set or referenced in the function.
6293+
- ``used-gpr`` only zeros call-used GPR registers used in the funciton.
6294+
- ``used-arg`` only zeros call-used registers used to pass arguments to the
6295+
function.
6296+
- ``used-gpr-arg`` only zeros call-used GPR registers used to pass arguments to
6297+
the function.
6298+
- ``all`` zeros all call-used registers.
6299+
- ``all-gpr`` zeros all call-used GPR registers.
6300+
- ``all-arg`` zeros all call-used registers used to pass arguments to the
6301+
function.
6302+
- ``all-gpr-arg`` zeros all call-used GPR registers used to pass arguments to
6303+
the function.
6304+
6305+
The default for the attribute is contolled by the ``-fzero-call-used-regs``
6306+
flag.
6307+
6308+
.. _Return-Oriented Programming: https://en.wikipedia.org/wiki/Return-oriented_programming
6309+
}];
6310+
}

clang/include/clang/Basic/CodeGenOptions.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,10 @@ ENUM_CODEGENOPT(SwiftAsyncFramePointer, SwiftAsyncFramePointerKind, 2,
464464
/// Whether to skip RAX setup when passing variable arguments (x86 only).
465465
CODEGENOPT(SkipRaxSetup, 1, 0)
466466

467+
/// Whether to zero out caller-used registers before returning.
468+
ENUM_CODEGENOPT(ZeroCallUsedRegs, llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind,
469+
5, llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::Skip)
470+
467471
#undef CODEGENOPT
468472
#undef ENUM_CODEGENOPT
469473
#undef VALUE_CODEGENOPT

clang/include/clang/Driver/Options.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2907,6 +2907,14 @@ def fenable_matrix : Flag<["-"], "fenable-matrix">, Group<f_Group>,
29072907
HelpText<"Enable matrix data type and related builtin functions">,
29082908
MarshallingInfoFlag<LangOpts<"MatrixTypes">>;
29092909

2910+
def fzero_call_used_regs_EQ
2911+
: Joined<["-"], "fzero-call-used-regs=">, Group<f_Group>, Flags<[CC1Option]>,
2912+
HelpText<"Clear call-used registers upon function return.">,
2913+
Values<"skip,used-gpr-arg,used-gpr,used-arg,used,all-gpr-arg,all-gpr,all-arg,all">,
2914+
NormalizedValues<["Skip", "UsedGPRArg", "UsedGPR", "UsedArg", "Used",
2915+
"AllGPRArg", "AllGPR", "AllArg", "All"]>,
2916+
NormalizedValuesScope<"llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind">,
2917+
MarshallingInfoEnum<CodeGenOpts<"ZeroCallUsedRegs">, "Skip">;
29102918

29112919
def fdebug_types_section: Flag <["-"], "fdebug-types-section">, Group<f_Group>,
29122920
HelpText<"Place debug types in their own section (ELF Only)">;

clang/lib/CodeGen/CGCall.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1878,6 +1878,37 @@ void CodeGenModule::getDefaultFunctionAttributes(StringRef Name,
18781878

18791879
if (CodeGenOpts.SpeculativeLoadHardening)
18801880
FuncAttrs.addAttribute(llvm::Attribute::SpeculativeLoadHardening);
1881+
1882+
// Add zero-call-used-regs attribute.
1883+
switch (CodeGenOpts.getZeroCallUsedRegs()) {
1884+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::Skip:
1885+
FuncAttrs.removeAttribute("zero-call-used-regs");
1886+
break;
1887+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::UsedGPRArg:
1888+
FuncAttrs.addAttribute("zero-call-used-regs", "used-gpr-arg");
1889+
break;
1890+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::UsedGPR:
1891+
FuncAttrs.addAttribute("zero-call-used-regs", "used-gpr");
1892+
break;
1893+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::UsedArg:
1894+
FuncAttrs.addAttribute("zero-call-used-regs", "used-arg");
1895+
break;
1896+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::Used:
1897+
FuncAttrs.addAttribute("zero-call-used-regs", "used");
1898+
break;
1899+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::AllGPRArg:
1900+
FuncAttrs.addAttribute("zero-call-used-regs", "all-gpr-arg");
1901+
break;
1902+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::AllGPR:
1903+
FuncAttrs.addAttribute("zero-call-used-regs", "all-gpr");
1904+
break;
1905+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::AllArg:
1906+
FuncAttrs.addAttribute("zero-call-used-regs", "all-arg");
1907+
break;
1908+
case llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind::All:
1909+
FuncAttrs.addAttribute("zero-call-used-regs", "all");
1910+
break;
1911+
}
18811912
}
18821913

18831914
if (getLangOpts().assumeFunctionsAreConvergent()) {
@@ -2166,6 +2197,15 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
21662197
FuncAttrs.addAttribute(llvm::Attribute::SpeculativeLoadHardening);
21672198
if (TargetDecl->hasAttr<NoSplitStackAttr>())
21682199
FuncAttrs.removeAttribute("split-stack");
2200+
if (TargetDecl->hasAttr<ZeroCallUsedRegsAttr>()) {
2201+
// A function "__attribute__((...))" overrides the command-line flag.
2202+
auto Kind =
2203+
TargetDecl->getAttr<ZeroCallUsedRegsAttr>()->getZeroCallUsedRegs();
2204+
FuncAttrs.removeAttribute("zero-call-used-regs");
2205+
FuncAttrs.addAttribute(
2206+
"zero-call-used-regs",
2207+
ZeroCallUsedRegsAttr::ConvertZeroCallUsedRegsKindToStr(Kind));
2208+
}
21692209

21702210
// Add NonLazyBind attribute to function declarations when -fno-plt
21712211
// is used.

clang/lib/CodeGen/CodeGenFunction.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,10 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
981981
CGM.getCodeGenOpts().StackAlignment))
982982
Fn->addFnAttr("stackrealign");
983983

984+
// "main" doesn't need to zero out call-used registers.
985+
if (FD && FD->isMain())
986+
Fn->removeFnAttr("zero-call-used-regs");
987+
984988
llvm::BasicBlock *EntryBB = createBasicBlock("entry", CurFn);
985989

986990
// Create a marker to make it easy to insert allocas into the entryblock

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5888,6 +5888,16 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
58885888
Args.AddLastArg(CmdArgs, options::OPT_fdigraphs, options::OPT_fno_digraphs);
58895889
Args.AddLastArg(CmdArgs, options::OPT_femulated_tls,
58905890
options::OPT_fno_emulated_tls);
5891+
Args.AddLastArg(CmdArgs, options::OPT_fzero_call_used_regs_EQ);
5892+
5893+
if (Arg *A = Args.getLastArg(options::OPT_fzero_call_used_regs_EQ)) {
5894+
// FIXME: There's no reason for this to be restricted to X86. The backend
5895+
// code needs to be changed to include the appropriate function calls
5896+
// automatically.
5897+
if (!Triple.isX86())
5898+
D.Diag(diag::err_drv_unsupported_opt_for_target)
5899+
<< A->getAsString(Args) << TripleStr;
5900+
}
58915901

58925902
// AltiVec-like language extensions aren't relevant for assembling.
58935903
if (!isa<PreprocessJobAction>(JA) || Output.getType() != types::TY_PP_Asm)

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7846,6 +7846,24 @@ static void handleOpenCLAccessAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
78467846
D->addAttr(::new (S.Context) OpenCLAccessAttr(S.Context, AL));
78477847
}
78487848

7849+
static void handleZeroCallUsedRegsAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
7850+
// Check that the argument is a string literal.
7851+
StringRef KindStr;
7852+
SourceLocation LiteralLoc;
7853+
if (!S.checkStringLiteralArgumentAttr(AL, 0, KindStr, &LiteralLoc))
7854+
return;
7855+
7856+
ZeroCallUsedRegsAttr::ZeroCallUsedRegsKind Kind;
7857+
if (!ZeroCallUsedRegsAttr::ConvertStrToZeroCallUsedRegsKind(KindStr, Kind)) {
7858+
S.Diag(LiteralLoc, diag::warn_attribute_type_not_supported)
7859+
<< AL << KindStr;
7860+
return;
7861+
}
7862+
7863+
D->dropAttr<ZeroCallUsedRegsAttr>();
7864+
D->addAttr(ZeroCallUsedRegsAttr::Create(S.Context, Kind, AL));
7865+
}
7866+
78497867
static void handleSYCLKernelAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
78507868
// The 'sycl_kernel' attribute applies only to function templates.
78517869
const auto *FD = cast<FunctionDecl>(D);
@@ -8630,6 +8648,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
86308648
case ParsedAttr::AT_InternalLinkage:
86318649
handleInternalLinkageAttr(S, D, AL);
86328650
break;
8651+
case ParsedAttr::AT_ZeroCallUsedRegs:
8652+
handleZeroCallUsedRegsAttr(S, D, AL);
8653+
break;
86338654

86348655
// Microsoft attributes:
86358656
case ParsedAttr::AT_LayoutVersion:

0 commit comments

Comments
 (0)