Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support BTF-based pretty printing #2651

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

chengshuyi
Copy link
Contributor

@chengshuyi chengshuyi commented Jun 21, 2023

A short example: bpftrace -e 'kprobe:kfree_skb {printb((struct sk_buff *)arg0)}'. It will display complete struct sk_buff information, which can improve debugging efficiency. Part of the output is shown below:

(struct sk_buff){
        (union){
                (struct){
                        .next = (struct sk_buff *)(nil),
                        .prev = (struct sk_buff *)(nil),
                        (union){
                                .dev = (struct net_device *)(nil),
                                .dev_scratch = (long unsigned int)0,
                        },
                },
...
Checklist
  • Language changes are updated in man/adoc/bpftrace.adoc and if needed in docs/reference_guide.md
  • User-visible and non-trivial changes updated in CHANGELOG.md
  • The new behaviour is covered by tests

add the keyword `printb` which means BTF-based pretty printing.

Signed-off-by: Shuyi Cheng <chengshuyi@linux.alibaba.com>
Add `struct BTFDump` to save `struct btf_dump` information. In general, one BTF corresponds to one `struct BTFDump`. In addition, a new `MessageType::printb` has been added for printing formatted structure data.

Signed-off-by: Shuyi Cheng <chengshuyi@linux.alibaba.com>
`Type::printb` is the type of the printb function. `PrintBTF` is the private information of this type, used to store dump_id, type_id and size information.

- dump_id: indicates the BTFDump corresponding to the printed structure
- type_id: indicates the id of the structure in BTF
- size: indicates the size of the structure

Signed-off-by: Shuyi Cheng <chengshuyi@linux.alibaba.com>
The main checks are:
- Refer to non-map print, printb also only accepts one parameter
- The parameter type received by printb must be a pointer type, and the pointer points to the record type

In addition, the maximum value size of the percpu map that needs to be created is also recorded.

Signed-off-by: Shuyi Cheng <chengshuyi@linux.alibaba.com>
The resources involved are:

- save the parameter of printb
- create percpu map

Signed-off-by: Shuyi Cheng <chengshuyi@linux.alibaba.com>
- Refer to CreateGetJoinMap, add CreateGetPrintBTFMap to get percpu memory.
- Created new async event type `struct PrintBTF`.

Signed-off-by: Shuyi Cheng <chengshuyi@linux.alibaba.com>
Signed-off-by: Shuyi Cheng <chengshuyi@linux.alibaba.com>
@viktormalik
Copy link
Contributor

This seems closely related to #1834. That one also suggest to use the bpf_snprintf_btf helper which produces practically the same output as your implementation here. In addition, it doesn't need async approach so I'd be inclined towards it more.

@chengshuyi
Copy link
Contributor Author

chengshuyi commented Jun 21, 2023

This seems closely related to #1834. That one also suggest to use the bpf_snprintf_btf helper which produces practically the same output as your implementation here. In addition, it doesn't need async approach so I'd be inclined towards it more.

bpf_snprintf_btf also needs an async approach, because bpf_snprintf_btf also needs to allocate a large piece of memory through map to store formatted data.

I use the current approach for several reasons:

  1. Better compatibility, can run on the older kernel.
  2. The size of the required map value is determined, but bpf_snprintf_btf cannot be determined
  3. This method is also implemented in BCC, see https://github.com/iovisor/bcc/blob/master/libbpf-tools/ksnoop.c#L756
  4. The original binary data transmitted, compared with string data, the amount of transmitted data is smaller

Thanks!

@viktormalik
Copy link
Contributor

This seems closely related to #1834. That one also suggest to use the bpf_snprintf_btf helper which produces practically the same output as your implementation here. In addition, it doesn't need async approach so I'd be inclined towards it more.

bpf_snprintf_btf also needs an async approach, because bpf_snprintf_btf also needs to allocate a large piece of memory through map to store formatted data.

Good point, I didn't realize that.

I use the current approach for several reasons:

  1. Better compatibility, can run on the older kernel.
  2. The size of the required map value is determined, but bpf_snprintf_btf cannot be determined
  3. This method is also implemented in BCC, see https://github.com/iovisor/bcc/blob/master/libbpf-tools/ksnoop.c#L756
  4. The original binary data transmitted, compared with string data, the amount of transmitted data is smaller

These are all valid reasons, thanks! It seems that even the ksnoop tool, which was the original use-case for bpf_snprintf_btf doesn't use it anymore.

I'll try to get to review this soon. Thanks for the work!

return;

Expression *arg = call.vargs->at(0);
if (arg->type.type == Type::pointer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we directly accept a record here? It would require to update the codegen but would be quite a nice feature. In addition, codegen could end up being even simpler than now as printb could directly use any data that it receives (e.g. from the dereference operator).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel pointer could be better. Because from the scripting point of view, it will be a little simpler. For example printb($skb->sk) instead of printb(*($skb->sk)). What do you think ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you go with structs, it will allow to use printb in more use-cases, e.g. @ = *($skb->sk); ... printb(@). Also, it'll be easier implementation-wise b/c now you're always doing proberead to access the memory but that's not always the correct way (e.g. BTF-based probes don't need it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you go with structs, it will allow to use printb in more use-cases, e.g. @ = *($skb->sk); ... printb(@).

In fact, it is now supported. `@=$skb->sk; ... printb(@);.

Also, it'll be easier implementation-wise b/c now you're always doing proberead to access the memory but that's not always the correct way (e.g. BTF-based probes don't need it).

I suddenly remembered why I can't dereference directly, because the general kernel structure is relatively large, and if I dereference it directly, it will occupy the eBPF stack space.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, it is now supported. `@=$skb->sk; ... printb(@);.

True, but that requires kernel to be able to store pointers into maps which hasn't been around for long.

I suddenly remembered why I can't dereference directly, because the general kernel structure is relatively large, and if I dereference it directly, it will occupy the eBPF stack space.

IIUC, proberead will also read the data onto BPF stack so there's no difference on that front.

Since printb is printing the structure, it still feels natural to me to pass directly the structure and not a pointer to the call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, it is now supported. `@=$skb->sk; ... printb(@);.

True, but that requires kernel to be able to store pointers into maps which hasn't been around for long.

I suddenly remembered why I can't dereference directly, because the general kernel structure is relatively large, and if I dereference it directly, it will occupy the eBPF stack space.

IIUC, proberead will also read the data onto BPF stack so there's no difference on that front.

We can directly use proberead to read the data into the map. The prototype of proberead is long bpf_probe_read(void *dst, u32 size, const void *unsafe_ptr), we can set dst as the value pointer of map.

Since printb is printing the structure, it still feels natural to me to pass directly the structure and not a pointer to the call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can directly use proberead to read the data into the map. The prototype of proberead is long bpf_probe_read(void *dst, u32 size, const void *unsafe_ptr), we can set dst as the value pointer of map.

Fair point, I didn't realize that. Could we do the same for BTF-based probes by directly loading into the map?

Also, I'm wondering if LLVM would be able to optimize away the stack usage even if we had it in the codegen. If not, perhaps it'd make sense to support both variants (pointer and non-pointer)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can directly use proberead to read the data into the map. The prototype of proberead is long bpf_probe_read(void *dst, u32 size, const void *unsafe_ptr), we can set dst as the value pointer of map.

Fair point, I didn't realize that. Could we do the same for BTF-based probes by directly loading into the map?

I understand it should be possible, we can even use percpu map instead of eBPF stack. That is, store all the data in the percpu map.

Also, I'm wondering if LLVM would be able to optimize away the stack usage even if we had it in the codegen. If not, perhaps it'd make sense to support both variants (pointer and non-pointer)?

Sorry, I don't quite understand the meaning here.:)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question was that if we have something like this in codegen:

get pointer to skb
proberead skb to BPF stack
store the data (from BPF stack) into printb map

if LLVM optimizations are able to turn it into

get pointer to skb
proberead skb to printb map

If not, then, we'll have to do the optimization on the level of the printb call and then, passing a pointer seems a better option implementation-wise. On the other hand, user-wise, it seems more appropriate to pass a structure so I'm thinking if we could allow both.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what you mean, llvm should not have this optimization, because the two of them are completely independent memory spaces.

Comment on lines +1350 to +1364
if (is_final_pass())
{
auto ids = bpftrace_.btf_->get_dumpid_typeid(ty->GetName());
if (ids.first < 0 || ids.second < 0)
{
LOG(ERROR, call.loc, err_)
<< "Failed to get BTFDump for " << ty->GetName();
return;
}
// extra space for async event header, see `struct PrintBTF`'s
// definition in async_event_types.h
bpftrace_.max_printb_map_size_ = std::max(
bpftrace_.max_printb_map_size_, ty->GetSize() + 8 + 8);
call.type = CreatePrintBTF(ids.first, ids.second, ty->GetSize());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this should be done in ResourceAnalyser instead of here. Also, we don't need a new type as printb doesn't return anything and it cannot be used in expressions, so we should just use the none type here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will do, thanks.

@@ -72,6 +79,12 @@ class BTF

std::pair<int, int> get_btf_id_fd(const std::string& func,
const std::string& mod) const;
// get dump id and type id
std::pair<int, int> get_dumpid_typeid(const std::string& type_name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dump id/type id pair is also used in other places outside of this class. Maybe it'd be worth defining a type for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, will do, thanks!

@danobi
Copy link
Member

danobi commented Jul 7, 2023

Is it necessary to have a new printb() builtin? I realize we can dump structs using print() today so there's symmetry with printb(), but I wonder if it's better to have printf() format string specifiers instead. Those are composable and will come out atomically with other metadata the user might have (rather than possibly being dropped due to ring buffer overflow).

It would feel quite natural to have a specifier for raw struct dump as well as pretty printed dump.

@chengshuyi
Copy link
Contributor Author

Using printf is also acceptable. We can convert it into a string through other builtins, such as printf("%s", btf(skb));. Or, we can use something like {:?} in rust. However, I haven't figured out what symbols should be used in printf yet.

@viktormalik
Copy link
Contributor

Having new printf format specifiers sounds good to me, although I'm not sure if it won't be confusing to users as they'd expect printf to have the "commonly known specifiers" only. OTOH, we already have a few custom specifiers so there's a precedent for it (and it'd be documented). I like it better than adding new builtins.

However, I haven't figured out what symbols should be used in printf yet.

Good question, wdyt @danobi?

Maybe %t (as in "struct type"), optionally followed by an extra modifier, i.e. %tr for raw struct and %th or %tp for pretty-printed struct?

@danobi
Copy link
Member

danobi commented Jul 10, 2023

The kernel seems to dump most of their pointer-y printf extensions under %p prefix: https://www.kernel.org/doc/html/latest/core-api/printk-formats.html#pointer-types

Perhaps we can try to follow that? A lot of kernel devs use bpftrace so it would probably feel natural for them.

@chengshuyi
Copy link
Contributor Author

Now printf uses stack memory, do we need to change printf to use percpu map memory first? Because the stack memory is only 512 bytes, it is definitely not enough.

@danobi
Copy link
Member

danobi commented Jul 12, 2023

@chengshuyi good point. Improving printf() to use percpu map memory would be a nice change. I don't know off the top of my head how much work it'd be, though.

I'd rather not block this PR on a big refactor, as printb() does provide some level of symmetry with printf(). But I think it'd be nice not to add too many more builtin functions.

Mind taking a look at how much effort a printf() refactor is? If it's an obnoxiously large amount of work, maybe we should merge this first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants