Initial klint integration
cover Initial klint integration
From: Gary Guo <[email protected]>
This RFC series introduce klint integration into the kernel.
Klint is a linting tool (in the form of a custom Rust compiler driver, similar to how Clippy works) that I have been working on since 2022. It was initially created to help lint on code that can trigger panics on allocation failure; this feature has been long obsolete after we started vendoring alloc crate and subsequently replacing it with a custom implementation, but over time it also gains some other useful lints.
Lints that are considered ready currently:
* Items available via kernel prelude but imported via other means
* build_error/build_assert calls not being optimized out (klint will provide you a backtrace to ease analysis, as opposed to just a linker error).
* Stack frames larger than desired (configured via CONFIG_FRAME_WARN).
The kernel is not currently lint clean. I've already sent out a series to fix the prelude imports; there is a true-positive with Nova-core's Cmdq::new method's excessive stack usage which needs fixing.
Many of you may have also heard about klint's ability to perform locking rule checks, however I did not consider it ready (due to false positives and inability to analysis some code patterns), so it is not enabled in the series.
The source code is available at https://github.com/Rust-for-Linux/klint.
It is licensed in "MIT OR Apache-2.0", the same license as the Rust compiler, as the tool is heavy dependant on the Rust compiler internals. Currently, there's no pre-compiled binary available; it should be easy to build for rustup and Nix users; if there are enough demands I can also consider providing pre-built binaries.
Gary Guo (2):
kbuild: rust: register "klint" as a tool
kbuild: rust: use klint to provide CONFIG_FRAME_WARN
rust/Makefile | 5 ++++- scripts/Makefile.build | 6 ++++-- scripts/Makefile.warn | 1 + 3 files changed, 9 insertions(+), 3 deletions(-)
base-commit: 615aad0f61e0c7a898184a394dc895c610100d4f
Signature · 1 line
2.51.2
RFC PATCH 1/2 kbuild: rust: register "klint" as a tool
From: Gary Guo <[email protected]>
Register "klint" as a tool so that rustc understands that #[klint::attr] is a tool attribute and klint::lint is a tool lint.
This makes use of the register_tool feature which has been implemented as a nightly feature for long time and has an active RFC. The feature does not change any functionality, simply make it possible for Rust to recognize extra tools in addition to the built-in hardcoded ones (clippy & rustdoc).
--- rust/Makefile | 5 ++++- scripts/Makefile.build | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-)
rust/Makefile +4 -1
@@ -320,7 +320,9 @@ quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $< mkdir -p $(objtree)/$(obj)/test/doctests/kernel; \ OBJTREE=$(abspath $(objtree)) \ $(RUSTDOC) --test $(filter-out --remap-path-prefix=%,$(rust_flags)) \ - -L$(objtree)/$(obj) --extern ffi --extern pin_init \ + -L$(objtree)/$(obj) \ + -Zcrate-attr='feature(register_tool)' -Zcrate-attr='register_tool(klint)' \ + --extern ffi --extern pin_init \ --extern kernel --extern build_error --extern macros \ --extern bindings --extern uapi \ --no-run --crate-name kernel -Zunstable-options \
@@ -562,6 +564,7 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L $(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) \ $(filter-out $(skip_flags),$(rust_flags)) $(rustc_target_flags) \ --emit=dep-info=$(depfile) --emit=obj=$@ \ + -Zcrate-attr='feature(register_tool)' -Zcrate-attr='register_tool(klint)' \ --emit=metadata=$(dir $@)$(patsubst %.o,lib%.rmeta,$(notdir $@)) \ --crate-type rlib -L$(objtree)/$(obj) \ --crate-name $(patsubst %.o,%,$(notdir $@)) $< \
scripts/Makefile.build +4 -2
@@ -312,12 +312,13 @@ $(obj)/%.lst: $(obj)/%.c FORCE # - Stable since Rust 1.82.0: `feature(asm_const)`, # `feature(offset_of_nested)`, `feature(raw_ref_op)`. # - Stable since Rust 1.87.0: `feature(asm_goto)`. -# - Expected to become stable: `feature(arbitrary_self_types)`. +# - Expected to become stable: `feature(arbitrary_self_types)`, +# `feature(register_tool)`. # - To be determined: `feature(used_with_arg)`. # # Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on # the unstable features in use. -rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,used_with_arg +rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,register_tool,used_with_arg # `--out-dir` is required to avoid temporaries being created by `rustc` in the # current working directory, which may be not accessible in the out-of-tree
@@ -328,6 +329,7 @@ rust_common_cmd = \ -Zallow-features=$(rust_allowed_features) \ -Zcrate-attr=no_std \ -Zcrate-attr='feature($(rust_allowed_features))' \ + -Zcrate-attr='register_tool(klint)' \ -Zunstable-options --extern pin_init --extern kernel \ --crate-type rlib -L $(objtree)/rust/ \ --crate-name $(basename $(notdir $@)) \
Signature · 1 line
2.51.2
RFC PATCH 2/2 kbuild: rust: use klint to provide CONFIG_FRAME_WARN
In reply to Gary GuoFrom: Gary Guo <[email protected]>
klint is able to analyze frame size and warn on frames that are too big.
With register_tool(klint) support, we can simply instruct rust to turn on klint::stack_frame_too_large. This is a no-op if normal rustc (or Clippy) is invoked, but will perform the check if klint is used.
This is an example of a bug caught using this option:
warning: stack size of gsp::cmdq::Cmdq::new is 8216 bytes, exceeds the 2048-byte limit
--> drivers/gpu/nova-core/gsp/cmdq.rs:453:5
|
453 | pub(crate) fn new(dev: &device::Device<device::Bound>) -> Result<Cmdq> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: the stack size is inferred from instruction `sub $0x2018,%rsp` at .text+32778--- scripts/Makefile.warn | 1 + 1 file changed, 1 insertion(+)
scripts/Makefile.warn +1 -0
@@ -23,6 +23,7 @@ KBUILD_CFLAGS += -Wmissing-prototypes ifneq ($(CONFIG_FRAME_WARN),0) KBUILD_CFLAGS += -Wframe-larger-than=$(CONFIG_FRAME_WARN) +KBUILD_RUSTFLAGS += -Wklint::stack-frame-too-large endif KBUILD_CFLAGS-$(CONFIG_CC_NO_ARRAY_BOUNDS) += -Wno-array-bounds
Signature · 1 line
2.51.2
Re: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
Quoted 12 lines · Gary Guo
From: Gary Guo <[email protected]>
klint is able to analyze frame size and warn on frames that are too big.
With
register_tool(klint)support, we can simply instruct rust to turn onklint::stack_frame_too_large. This is a no-op if normal rustc (or Clippy) is invoked, but will perform the check if klint is used.This is an example of a bug caught using this option:
warning: stack size of
gsp::cmdq::Cmdq::newis 8216 bytes, exceeds the 2048-byte limit
-->drivers/gpu/nova-core/gsp/cmdq.rs:453:5
Quoted 7 lines
| 453 | pub(crate) fn new(dev: &device::Device<device::Bound>) -> Result<Cmdq> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the stack size is inferred from instruction `sub $0x2018,%rsp` at .text+32778Signed-off-by Gary Guo <[email protected]>
Is the limit customizable?
Quoted 19 lines
--- scripts/Makefile.warn | 1 + 1 file changed, 1 insertion(+)scripts/Makefile.warn +1
-0@@ -23,6 +23,7 @@ KBUILD_CFLAGS += -Wmissing-prototypes ifneq ($(CONFIG_FRAME_WARN),0) KBUILD_CFLAGS += -Wframe-larger-than=$(CONFIG_FRAME_WARN) +KBUILD_RUSTFLAGS += -Wklint::stack-frame-too-large endif KBUILD_CFLAGS-$(CONFIG_CC_NO_ARRAY_BOUNDS) += -Wno-array-boundsSignature · 1 line
2.51.2
Gary Guo
reviewerRe: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
Quoted 19 lines · Nathan Chancellor
Quoted 18 lines · Gary Guo
From: Gary Guo <[email protected]>
klint is able to analyze frame size and warn on frames that are too big.
With
register_tool(klint)support, we can simply instruct rust to turn onklint::stack_frame_too_large. This is a no-op if normal rustc (or Clippy) is invoked, but will perform the check if klint is used.This is an example of a bug caught using this option:
warning: stack size of
gsp::cmdq::Cmdq::newis 8216 bytes, exceeds the 2048-byte limit
-->drivers/gpu/nova-core/gsp/cmdq.rs:453:5| 453 | pub(crate) fn new(dev: &device::Device<device::Bound>) -> Result<Cmdq> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the stack size is inferred from instruction `sub $0x2018,%rsp` at .text+32778
Quoted 5 lines
Quoted 1 line
Signed-off-by Gary Guo <[email protected]>Is the limit customizable?
Lints in Rust do not take arguments, so there's no way to represent something similar to -Wframe-larger-than=foo. However, I've made klint search [1] for CONFIG_FRAME_WARN and use whatever value is there.
Best,
Gary
Quoted 20 lines
Quoted 20 lines
--- scripts/Makefile.warn | 1 + 1 file changed, 1 insertion(+)scripts/Makefile.warn +1
-0@@ -23,6 +23,7 @@ KBUILD_CFLAGS += -Wmissing-prototypes ifneq ($(CONFIG_FRAME_WARN),0) KBUILD_CFLAGS += -Wframe-larger-than=$(CONFIG_FRAME_WARN) +KBUILD_RUSTFLAGS += -Wklint::stack-frame-too-large endif KBUILD_CFLAGS-$(CONFIG_CC_NO_ARRAY_BOUNDS) += -Wno-array-boundsSignature · 1 line
2.51.2
Re: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
Quoted 6 lines · Gary Guo
Lints in Rust do not take arguments, so there's no way to represent something similar to
-Wframe-larger-than=foo. However, I've made klint search [1] forCONFIG_FRAME_WARNand use whatever value is there.
I would have suggested a .klint.toml like Clippy, but with Clippy we also want to eventually have a flag-based configuration instead of a static file (or attributes in the source code which means probably using conditional compilation in some cases).
So, even if the lint is toggled with -[AWDF] style flags, could the limit be configured in a separate non--W Klint-only flag? Or doesn't register_tool allow for that?
Thanks!
Cheers,
Miguel
Gary Guo
reviewerRe: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
Quoted 8 lines · Miguel Ojeda
Quoted 6 lines · Gary Guo
Lints in Rust do not take arguments, so there's no way to represent something similar to
-Wframe-larger-than=foo. However, I've made klint search [1] forCONFIG_FRAME_WARNand use whatever value is there.
Quoted 8 lines
I would have suggested a
.klint.tomllike Clippy, but with Clippy we also want to eventually have a flag-based configuration instead of a static file (or attributes in the source code which means probably using conditional compilation in some cases).So, even if the lint is toggled with
-[AWDF]style flags, could the limit be configured in a separate non--WKlint-only flag? Or doesn'tregister_toolallow for that?
No, there is no tool-specific flags.
I *could* add new flags by intercepting the parsing of command line in klint, but doing so would require conditional passing of command line based on if the compiler driver is klint, making it not a drop-in replacement for rustc, which is something that I want to avoid.
I think the best path forward is `--crate-attr klint::attr', but that is gated behind custom_inner_attributes feature which is further away from stability.
A shorter term solution might be to add a cfg specific to klint? For example, --cfg klint_stack_frame_size_limit=foo. This way the interface at least is specific and not relying on Kbuild internals.
Best,
Gary
Alexandre Courbot
reviewerRe: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
In reply to Gary GuoQuoted 10 lines · Gary Guo
From: Gary Guo <[email protected]>
klint is able to analyze frame size and warn on frames that are too big.
With
register_tool(klint)support, we can simply instruct rust to turn onklint::stack_frame_too_large. This is a no-op if normal rustc (or Clippy) is invoked, but will perform the check if klint is used.This is an example of a bug caught using this option:
Quoted 7 lines
warning: stack size of
gsp::cmdq::Cmdq::newis 8216 bytes, exceeds the 2048-byte limit
-->drivers/gpu/nova-core/gsp/cmdq.rs:453:5| 453 | pub(crate) fn new(dev: &device::Device<device::Bound>) -> Result<Cmdq> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the stack size is inferred from instruction `sub $0x2018,%rsp` at .text+32778
Initially this error puzzled me a bit since the objects directly built by Cmdq::new are not larger than ~40 bytes, but then I dug into the constructors it called and found that PteArray, a large struct, is built on the stack! We need to convert it to an Init I guess.
Super valuable tool!
Gary Guo
reviewerRe: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
Quoted 19 lines · Alexandre Courbot
Quoted 10 lines · Gary Guo
From: Gary Guo <[email protected]>
klint is able to analyze frame size and warn on frames that are too big.
With
register_tool(klint)support, we can simply instruct rust to turn onklint::stack_frame_too_large. This is a no-op if normal rustc (or Clippy) is invoked, but will perform the check if klint is used.This is an example of a bug caught using this option:
Quoted 7 lines
warning: stack size of
gsp::cmdq::Cmdq::newis 8216 bytes, exceeds the 2048-byte limit
-->drivers/gpu/nova-core/gsp/cmdq.rs:453:5| 453 | pub(crate) fn new(dev: &device::Device<device::Bound>) -> Result<Cmdq> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the stack size is inferred from instruction `sub $0x2018,%rsp` at .text+32778
Quoted 6 lines
Initially this error puzzled me a bit since the objects directly built by
Cmdq::neware not larger than ~40 bytes, but then I dug into the constructors it called and found thatPteArray, a large struct, is built on the stack! We need to convert it to anInitI guess.Super valuable tool!
Yeah, I've already created a Zulip which mentions that the PTE array is likely the root cause. This specific lint only concerns the final stack size and is not aware of what's creating them.
It might be possible to do more detailed analysis by checking what is using the stack (e.g. perhaps recovery variable -> stack slots using DWARF if full debuginfo is enabled, or find out the instruction that first touches the specific stack location and map it back like that), but doing so is quite tricky and the marginal benefit is less than pointing out there's an issue to be investigated in the first place.
Best,
Gary
Nathan Chancellor
maintainerRe: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
Quoted 3 lines · Gary Guo
It might be possible to do more detailed analysis by checking what is using the stack (e.g. perhaps recovery variable -> stack slots using DWARF if full debuginfo is enabled, or find out the instruction that first touches the
Yeah Nick wrote a Python script to use debug info to give a better idea of stack usage to help us debug these warnings from clang:
https://github.com/ClangBuiltLinux/frame-larger-than
It is rather hacky and feels like there are a number of corner cases where things don't look quite right but it is better than nothing.
Quoted 3 lines
specific stack location and map it back like that), but doing so is quite tricky and the marginal benefit is less than pointing out there's an issue to be investigated in the first place.
Does Rust have the equivalent of '-Rpass-analysis' from clang? There was '-Rpass-analysis=stack-frame-layout' that was added in LLVM 16:
https://github.com/llvm/llvm-project/commit/557a5bc336ffb9b03c53d4d13fd8f0bc9418ec96
I assume that could be used from Rust as well since it is in the LLVM backend? It does not necessarily show exactly where the stack usage comes from aside from variables and spills but still, some information (especially accurate information at this level) is better than nothing.
Cheers,
Nathan
Gary Guo
reviewerRe: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
Quoted 9 lines · Nathan Chancellor
Quoted 3 lines · Gary Guo
It might be possible to do more detailed analysis by checking what is using the stack (e.g. perhaps recovery variable -> stack slots using DWARF if full debuginfo is enabled, or find out the instruction that first touches the
Yeah Nick wrote a Python script to use debug info to give a better idea of stack usage to help us debug these warnings from clang:
Tried to run this on nova-core.o, but it didn't print anything apart from function name.
I've made this change to allow it to recognize mangled names but this doesn't seem to be sufficient:
---
@@ -58,7 +58,9 @@
def get_name(DIE):
- if 'DW_AT_name' in DIE.attributes:
+ if 'DW_AT_linkage_name' in DIE.attributes:
+ return DIE.attributes['DW_AT_linkage_name'].value.decode('UTF-8')
+ elif 'DW_AT_name' in DIE.attributes:
return DIE.attributes['DW_AT_name'].value.decode('UTF-8')
else:
return '{anonymous}'---
Looking at the LoC it doesn't seem to be too complicated so I could potentially integrate some similar into klint. But as you said, it's hacky.
Quoted 10 lines
It is rather hacky and feels like there are a number of corner cases where things don't look quite right but it is better than nothing.
Quoted 3 lines
specific stack location and map it back like that), but doing so is quite tricky and the marginal benefit is less than pointing out there's an issue to be investigated in the first place.
Does Rust have the equivalent of '-Rpass-analysis' from clang? There was '-Rpass-analysis=stack-frame-layout' that was added in LLVM 16:
No, Rust doesn't expose such mechanism. As a matter of fact, klint is backend agnostic so in theory it is supposed to work with rustc_codegen_gcc, too.
So far I don't have plan on adding LLVM specific analyses as doing so is quite tricky with the modular backend design in Rust (I would effectively need to replace the entire codegen backend with a patched rustc_codegen_llvm).
Best,
Gary
Quoted 10 lines
https://github.com/llvm/llvm-project/commit/557a5bc336ffb9b03c53d4d13fd8f0bc9418ec96
I assume that could be used from Rust as well since it is in the LLVM backend? It does not necessarily show exactly where the stack usage comes from aside from variables and spills but still, some information (especially accurate information at this level) is better than nothing.
Cheers,
Nathan
Nathan Chancellor
maintainerRe: [RFC PATCH 2/2] kbuild: rust: use klint to provide CONFIG_FRAME_WARN
Quoted 9 lines · Gary Guo
Quoted 2 lines
Does Rust have the equivalent of '-Rpass-analysis' from clang? There was '-Rpass-analysis=stack-frame-layout' that was added in LLVM 16:
No, Rust doesn't expose such mechanism. As a matter of fact, klint is backend agnostic so in theory it is supposed to work with
rustc_codegen_gcc, too.So far I don't have plan on adding LLVM specific analyses as doing so is quite tricky with the modular backend design in Rust (I would effectively need to replace the entire codegen backend with a patched
rustc_codegen_llvm).
Ah, I had not thought about klint hooking that pass. I was more thinking that the tool could recommend adding such an option to the problematic target's RUSTFLAGS to get the output of the pass as part of the regular kernel build, like adding '-Rpass-analysis=stack-frame-layout' to a .c file's CFLAGS. If that option does not exist, then that line of questioning is dead.
Cheers,
Nathan
Quoted 28 lines · Gary Guo
From: Gary Guo <[email protected]>
This RFC series introduce klint integration into the kernel.
Klint is a linting tool (in the form of a custom Rust compiler driver, similar to how Clippy works) that I have been working on since 2022. It was initially created to help lint on code that can trigger panics on allocation failure; this feature has been long obsolete after we started vendoring alloc crate and subsequently replacing it with a custom implementation, but over time it also gains some other useful lints.
Lints that are considered ready currently:
* Items available via kernel prelude but imported via other means
* build_error/build_assert calls not being optimized out (klint will provide you a backtrace to ease analysis, as opposed to just a linker error).
* Stack frames larger than desired (configured viaCONFIG_FRAME_WARN).The kernel is not currently lint clean. I've already sent out a series to fix the prelude imports; there is a true-positive with Nova-core's
Cmdq::newmethod's excessive stack usage which needs fixing.Many of you may have also heard about klint's ability to perform locking rule checks, however I did not consider it ready (due to false positives and inability to analysis some code patterns), so it is not enabled in the series.
Quoted 6 lines
The source code is available at https://github.com/Rust-for-Linux/klint.
It is licensed in "MIT OR Apache-2.0", the same license as the Rust compiler, as the tool is heavy dependant on the Rust compiler internals. Currently, there's no pre-compiled binary available; it should be easy to build for rustup and Nix users; if there are enough demands I can also consider providing pre-built binaries.
Thanks Gary, it is great to see Klint coming to upstream after all the efforts you have put into it -- for others that may not be aware, Klint already discovered actual non-trivial bugs in the past!
This initial integration looks straightforward to me -- later on we can add KLINT=1 or similar.
It would be nice to add a short paragraph somewhere in Documentation/rust/ or even better, just start a small klint.rst doc there with something like this introduction and an example command line to run it (RUSTC=... I imagine) and a link to rust-for-linux.com/klint or GitHub directly (unless you really want to keep it only for experts for now). Later on we can add more docs as it gets more used and integrated.
Regarding the pre-built binaries, Nathan kindly agreed to add the klint binary into the LLVM+Rust toolchains if we start doing pre-built binaries, so that is great too for others to easily try and to use in CIs etc.
Cc'ing Kbuild.
Cheers,
Miguel
Quoted 11 lines · Gary Guo
From: Gary Guo <[email protected]>
This RFC series introduce klint integration into the kernel.
Klint is a linting tool (in the form of a custom Rust compiler driver, similar to how Clippy works) that I have been working on since 2022. It was initially created to help lint on code that can trigger panics on allocation failure; this feature has been long obsolete after we started vendoring alloc crate and subsequently replacing it with a custom implementation, but over time it also gains some other useful lints.
Quoted 6 lines
Lints that are considered ready currently:
* Items available via kernel prelude but imported via other means
* build_error/build_assert calls not being optimized out (klint will provide you a backtrace to ease analysis, as opposed to just a linker error).
* Stack frames larger than desired (configured viaCONFIG_FRAME_WARN).
Great work, Gary!
Quoted 1 line · Gary Guo
From: Gary Guo <[email protected]>
...
Quoted 3 lines
The kernel is not currently lint clean. I've already sent out a series to fix the prelude imports; there is a true-positive with Nova-core's
Cmdq::newmethod's excessive stack usage which needs fixing.
Nice find. Stack usage in particular has been a concern, and if klint can actually find some of these cases early, that's extremely valuable.
thanks,
Signature · 1 line
John Hubbard