From: Donald Hunter <donald.hunter@gmail.com>
To: netdev@vger.kernel.org, Jakub Kicinski <kuba@kernel.org>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Paolo Abeni <pabeni@redhat.com>
Cc: donald.hunter@redhat.com, Donald Hunter <donald.hunter@gmail.com>
Subject: [RFC net-next v1] tools: ynl: Add an strace rendering mode to ynl-gen
Date: Thu, 15 Jun 2023 16:13:36 +0100 [thread overview]
Message-ID: <20230615151336.77589-1-donald.hunter@gmail.com> (raw)
Add --mode strace to ynl-gen-c.py to generate source files for strace
that teach it to understand how to decode genetlink messages defined
in the spec. I successfully used this to add openvswitch message
decoding to strace as I described in:
https://donaldh.wtf/2023/06/teaching-strace-new-tricks/
It successfully generated ovs_datapath and ovs_vport but ovs_flow
needed manual fixes to fix code ordering and forward declarations.
Limitations:
- Uses a crude mechanism to try and emit functions in the right order
which fails for ovs_flow
- Outputs all strace sources to stdout or a single file
- Does not use the right semantic strace decoders for e.g. IP or MAC
addresses because there is no schema information to say what the
domain type is.
This seems like a useful tool to have as part of the ynl suite since
it lowers the cost of getting good strace support for new netlink
families. But I realise that the generated format is dependent on an
out of tree project. If there is interest in having this in-tree then
I can clean it up and address some of the limitations before
submission.
Signed-off-by: Donald Hunter <donald.hunter@gmail.com>
---
tools/net/ynl/ynl-gen-c.py | 286 +++++++++++++++++++++++++++++++++++++
1 file changed, 286 insertions(+)
diff --git a/tools/net/ynl/ynl-gen-c.py b/tools/net/ynl/ynl-gen-c.py
index 71c5e79e877f..efd87d8463ed 100755
--- a/tools/net/ynl/ynl-gen-c.py
+++ b/tools/net/ynl/ynl-gen-c.py
@@ -2268,6 +2268,288 @@ def render_user_family(family, cw, prototype):
cw.block_end(line=';')
+def render_strace(family, cw):
+
+ xlat_headers = []
+
+ # xlat for definitions
+
+ defines = []
+ for const in family['definitions']:
+ if const['type'] != 'const':
+ cw.writes_defines(defines)
+ defines = []
+ cw.nl()
+
+ if const['type'] == 'enum' or const['type'] == 'flags':
+ enum = family.consts[const['name']]
+
+ xlat_name = f"{family.name}_{c_lower(const['name'])}"
+ xlat_headers.append(xlat_name)
+
+ cw.p(f"// For src/xlat/{xlat_name}.in")
+ cw.p('#unconditional')
+ if const['type'] == 'enum':
+ cw.p('#value_indexed')
+
+ name_pfx = const.get('name-prefix', f"{family.name}-{const['name']}-")
+ for entry in enum.entries.values():
+ cw.p(entry.c_name)
+
+ cw.nl()
+ elif const['type'] == 'const':
+ defines.append([c_upper(family.get('c-define-name',
+ f"{family.name}-{const['name']}")),
+ const['value']])
+
+ if defines:
+ cw.writes_defines(defines)
+ cw.nl()
+
+ # xlat for attrs
+
+ for _, attr_set in family.attr_sets.items():
+ if attr_set.subset_of:
+ continue
+
+ xlat_name = c_lower(attr_set.yaml['enum-name'])
+ xlat_headers.append(xlat_name)
+
+ cw.p(f"// For src/xlat/{xlat_name}.in")
+ cw.p('#unconditional')
+ cw.p('#value_indexed')
+
+ for _, attr in attr_set.items():
+ cw.p(attr.enum_name)
+ cw.nl()
+
+ # xlat for commands
+
+ separate_ntf = 'async-prefix' in family['operations']
+
+ xlat_name = f"{family.name}_cmds"
+ xlat_headers.append(xlat_name)
+
+ cw.p(f"// For src/xlat/{xlat_name}.in")
+ cw.p('#unconditional')
+ cw.p('#value_indexed')
+
+ for op in family.msgs.values():
+ if separate_ntf and ('notify' in op or 'event' in op):
+ continue
+
+ cw.p(op.enum_name)
+ cw.nl()
+
+ if separate_ntf:
+ uapi_enum_start(family, cw, family['operations'], enum_name='async-enum')
+ for op in family.msgs.values():
+ if separate_ntf and not ('notify' in op or 'event' in op):
+ continue
+
+ suffix = ','
+ if 'value' in op:
+ suffix = f" = {op['value']},"
+ cw.p(op.enum_name + suffix)
+ cw.block_end(line=';')
+ cw.nl()
+
+ cw.nl()
+ if defines:
+ cw.writes_defines(defines)
+ cw.nl()
+
+ # Bind into netlink_generic.(c|h)
+
+ cw.p('// Add to src/netlink_generic.h')
+ cw.p(f"extern DECL_NETLINK_GENERIC_DECODER(decode_{family.name}_msg);")
+ cw.nl()
+
+ cw.p('// Add to src/netlink_generic.c in genl_decoders[]')
+ cw.p(f"""\t{{ "{family.name}", decode_{family.name}_msg }},""")
+ cw.nl()
+
+ # strace Makefile
+
+ cw.p('// Add to src/Makefile.am in libstrace_a_SOURCES')
+ cw.p(f"\t{family.name}.c \\")
+ cw.nl()
+
+ # Start of C source file
+
+ cw.p(f"// For src/{family.name}.c")
+ cw.nl()
+
+ cw.p('#include "defs.h"')
+ cw.p('#include "netlink.h"')
+ cw.p('#include "nlattr.h"')
+ cw.p('#include <linux/genetlink.h>')
+ cw.p(f"#include <{family['uapi-header']}>")
+ cw.p('#include "netlink_generic.h"')
+ for h in xlat_headers:
+ cw.p(f"#include \"xlat/{h}.h\"")
+ cw.nl()
+
+ # C code for flags, enum and struct decoders
+
+ for defn in family['definitions']:
+ if defn['type'] in [ 'flags', 'enum' ]:
+ prefix = defn.get('name-prefix', f"{family.name}-{defn['name']}-")
+
+ cw.p('static bool')
+ cw.p(f"decode_{c_lower(defn['name'])}(struct tcb *const tcp,")
+ cw.p("\t\tconst kernel_ulong_t addr,")
+ cw.p("\t\tconst unsigned int len,")
+ cw.p("\t\tconst void *const opaque_data)")
+ cw.block_start()
+ cw.block_start("static const struct decode_nla_xlat_opts opts =")
+ cw.p(f"""{family.name}_{c_lower(defn['name'])}, "{c_upper(prefix)}???", .size = 4""")
+ cw.block_end(';')
+ decoder = 'xval' if defn['type'] == 'enum' else 'flags'
+ cw.p(f"return decode_nla_{decoder}(tcp, addr, len, &opts);")
+ cw.block_end()
+
+ elif defn['type'] == 'struct':
+ struct_name = c_lower(defn['enum-name'] if 'enum-name' in defn else defn['name'])
+ var_name = c_lower(defn['name'])
+
+ cw.p('static bool')
+ cw.p(f"decode_{struct_name}(struct tcb *const tcp,")
+ cw.p("\t\tconst kernel_ulong_t addr,")
+ cw.p("\t\tconst unsigned int len,")
+ cw.p("\t\tconst void *const opaque_data)")
+ cw.block_start()
+
+ cw.p(f"struct {struct_name} {var_name};")
+ cw.p(f"umove_or_printaddr(tcp, addr, &{var_name});")
+ cw.nl()
+
+ for m in defn['members']:
+ if m['name'].startswith('pad'):
+ continue
+ cw.p(f"PRINT_FIELD_U({var_name}, {c_lower(m['name'])});")
+ cw.p('tprint_struct_next();')
+
+ cw.p('return true;')
+ cw.block_end()
+
+ cw.nl()
+
+ # C code for attibute set decoders
+
+ for _, attr_set in family.attr_sets.items():
+ if attr_set.subset_of:
+ continue
+
+ # Emit nested attr decoders before referencing them
+
+ for _, attr in attr_set.items():
+ if type(attr) in [ TypeNest, TypeArrayNest ]:
+ decoder = f"decode_{c_lower(attr.enum_name)}"
+ nested_set = family.attr_sets[attr['nested-attributes']]
+ nested_attrs = f"{c_lower(nested_set.yaml['enum-name'])}"
+ name_prefix = nested_set.yaml.get('name-prefix',
+ f"{family.name}-{nested_set.name}-")
+ attr_prefix = f"{c_upper(name_prefix)}"
+ decoder_array = f"{c_lower(nested_set.name)}_attr_decoders"
+ array_nest = "_item" if type(attr) == TypeArrayNest else ""
+
+ cw.p('static bool')
+ cw.p(f"{decoder}{array_nest}(struct tcb *const tcp,")
+ cw.p("\tconst kernel_ulong_t addr,")
+ cw.p("\tconst unsigned int len,")
+ cw.p("\tconst void *const opaque_data)")
+ cw.block_start()
+ cw.p(f"decode_nlattr(tcp, addr, len, {nested_attrs},")
+ cw.p(f"\t\"{attr_prefix}???\",")
+ cw.p(f"\tARRSZ_PAIR({decoder_array}),")
+ cw.p("\tNULL);")
+ cw.p('return true;')
+ cw.block_end()
+ cw.nl()
+
+ if type(attr) == TypeArrayNest:
+ cw.p('static bool')
+ cw.p(f"{decoder}(struct tcb *const tcp,")
+ cw.p("\tconst kernel_ulong_t addr,")
+ cw.p("\tconst unsigned int len,")
+ cw.p("\tconst void *const opaque_data)")
+ cw.block_start()
+ cw.p(f"nla_decoder_t decoder = &{decoder}_item;")
+ cw.p('decode_nlattr(tcp, addr, len, NULL, NULL, &decoder, 0, NULL);')
+ cw.p('return true;')
+ cw.block_end()
+ cw.nl()
+
+ # Then emit the decoders array
+
+ cw.block_start(f"static const nla_decoder_t {c_lower(attr_set.name)}_attr_decoders[] =")
+ for _, attr in attr_set.items():
+ if type(attr) in [ TypeUnused, TypeFlag ]:
+ decoder = 'NULL'
+ elif type(attr) == TypeString:
+ decoder = 'decode_nla_str'
+ elif type(attr) == TypeBinary:
+ decoder = 'NULL'
+ if 'struct' in attr.yaml:
+ defn = family.consts[attr.yaml['struct']]
+ enum_name = c_lower(defn.get('enum-name', defn.name))
+ decoder = f"decode_{enum_name}"
+ elif type(attr) == TypeNest:
+ decoder = f"decode_{c_lower(attr.enum_name)}"
+ elif type(attr) == TypeScalar and 'enum' in attr:
+ decoder = f"decode_{c_lower(attr['enum'])}"
+ else:
+ decoder = f"decode_nla_{attr.type}"
+
+ cw.p(f"[{attr.enum_name}] = {decoder},")
+ cw.block_end(';')
+ cw.nl()
+
+ # C code for top-level decoder
+
+ for op in family.msgs.values():
+ cmd_prefix = c_upper(family.yaml['operations']['name-prefix'])
+ attr_set_name = op.yaml['attribute-set']
+ attr_set = family.attr_sets[attr_set_name]
+ name_prefix = c_upper(attr_set.yaml.get('name-prefix', attr_set_name))
+ enum_name = c_lower(attr_set.yaml['enum-name'])
+
+ cw.block_start(f"DECL_NETLINK_GENERIC_DECODER(decode_{family.name}_msg)")
+
+ if op.fixed_header:
+ defn = family.consts[op.fixed_header]
+ header_name = c_lower(defn['name'])
+ cw.p(f"struct {header_name} header;")
+ cw.p(f"size_t offset = sizeof(struct {header_name});")
+ cw.nl()
+
+ cw.p('tprint_struct_begin();')
+ cw.p(f"""PRINT_FIELD_XVAL(*genl, cmd, {family.name}_cmds, "{cmd_prefix}???");""");
+ cw.p('tprint_struct_next();')
+ cw.p('PRINT_FIELD_U(*genl, version);')
+ cw.p('tprint_struct_next();')
+
+ if op.fixed_header:
+ cw.p('if (umove_or_printaddr(tcp, addr, &header))')
+ cw.p('return;')
+ for m in defn.members:
+ cw.p(f"PRINT_FIELD_U(header, {c_lower(m.name)});")
+ cw.p('tprint_struct_next();')
+ cw.nl()
+ cw.p(f"decode_nlattr(tcp, addr + offset, len - offset,");
+ else:
+ cw.p(f"decode_nlattr(tcp, addr, len,");
+
+ cw.p(f"\t{enum_name},");
+ cw.p(f"\t\"{name_prefix}???\",");
+ cw.p(f"\tARRSZ_PAIR({c_lower(attr_set_name)}_attr_decoders),");
+ cw.p("\tNULL);")
+ cw.p('tprint_struct_end();')
+ cw.block_end()
+ break
+
+
def find_kernel_root(full_path):
sub_path = ''
while True:
@@ -2335,6 +2617,10 @@ def main():
render_uapi(parsed, cw)
return
+ if args.mode == 'strace':
+ render_strace(parsed, cw)
+ return
+
hdr_prot = f"_LINUX_{parsed.name.upper()}_GEN_H"
if args.header:
cw.p('#ifndef ' + hdr_prot)
--
2.39.0
next reply other threads:[~2023-06-15 15:14 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-06-15 15:13 Donald Hunter [this message]
2023-06-15 17:19 ` [RFC net-next v1] tools: ynl: Add an strace rendering mode to ynl-gen Simon Horman
2023-06-16 3:00 ` Jakub Kicinski
2023-06-16 10:17 ` Donald Hunter
2023-06-16 18:11 ` Jakub Kicinski
2023-06-19 10:04 ` Donald Hunter
2023-06-19 19:00 ` Jakub Kicinski
2023-06-23 12:04 ` Donald Hunter
2023-06-23 15:28 ` Jakub Kicinski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20230615151336.77589-1-donald.hunter@gmail.com \
--to=donald.hunter@gmail.com \
--cc=davem@davemloft.net \
--cc=donald.hunter@redhat.com \
--cc=edumazet@google.com \
--cc=kuba@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).