diff mbox series

[bpf-next,v3,4/5] bpftool: generated shadow variables for struct_ops maps.

Message ID 20240221012329.1387275-5-thinker.li@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Create shadow types for struct_ops maps in skeletons | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 8 this patch: 8
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 9 maintainers not CCed: quentin@isovalent.com jolsa@kernel.org daniel@iogearbox.net john.fastabend@gmail.com yonghong.song@linux.dev sdf@google.com eddyz87@gmail.com kpsingh@kernel.org haoluo@google.com
netdev/build_clang success Errors and warnings before: 8 this patch: 8
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 8 this patch: 8
netdev/checkpatch warning WARNING: Avoid line continuations in quoted strings WARNING: Avoid unnecessary line continuations WARNING: line length of 88 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-13 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-15 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-18 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-42 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18 and -O2 optimization

Commit Message

Kui-Feng Lee Feb. 21, 2024, 1:23 a.m. UTC
From: Kui-Feng Lee <thinker.li@gmail.com>

Declares and defines a pointer of the shadow type for each struct_ops map.

The code generator will create an anonymous struct type as the shadow type
for each struct_ops map. The shadow type is translated from the original
struct type of the map. The user of the skeleton use pointers of them to
access the values of struct_ops maps.

However, shadow types only supports certain types of fields, such as scalar
types and function pointers. Any fields of unsupported types are translated
into an array of characters to occupy the space of the original
field. Function pointers are translated into pointers of the struct
bpf_program. Additionally, padding fields are generated to occupy the space
between two consecutive fields.

The pointers of shadow types of struct_osp maps are initialized when
*__open_opts() in skeletons are called. For a map called FOO, the user can
access it through the pointer at skel->struct_ops.FOO.

Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
 tools/bpf/bpftool/gen.c | 229 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 228 insertions(+), 1 deletion(-)

Comments

Quentin Monnet Feb. 21, 2024, 11:49 a.m. UTC | #1
2024-02-21 01:23 UTC+0000 ~ thinker.li@gmail.com
> From: Kui-Feng Lee <thinker.li@gmail.com>
> 
> Declares and defines a pointer of the shadow type for each struct_ops map.
> 
> The code generator will create an anonymous struct type as the shadow type
> for each struct_ops map. The shadow type is translated from the original
> struct type of the map. The user of the skeleton use pointers of them to
> access the values of struct_ops maps.
> 
> However, shadow types only supports certain types of fields, such as scalar

Nit: "such as" implies the list may not be exhaustive.

> types and function pointers. Any fields of unsupported types are translated
> into an array of characters to occupy the space of the original
> field. Function pointers are translated into pointers of the struct
> bpf_program. Additionally, padding fields are generated to occupy the space
> between two consecutive fields.
> 
> The pointers of shadow types of struct_osp maps are initialized when
> *__open_opts() in skeletons are called. For a map called FOO, the user can
> access it through the pointer at skel->struct_ops.FOO.
> 
> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
> ---
>  tools/bpf/bpftool/gen.c | 229 +++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 228 insertions(+), 1 deletion(-)
> 
> diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
> index a9334c57e859..20c5d5912df7 100644
> --- a/tools/bpf/bpftool/gen.c
> +++ b/tools/bpf/bpftool/gen.c
> @@ -909,6 +909,201 @@ codegen_progs_skeleton(struct bpf_object *obj, size_t prog_cnt, bool populate_li
>  	}
>  }
>  
> +static int walk_st_ops_shadow_vars(struct btf *btf,
> +				   const char *ident,
> +				   const struct bpf_map *map)
> +{
> +	DECLARE_LIBBPF_OPTS(btf_dump_emit_type_decl_opts, opts,
> +			    .indent_level = 3,
> +			    );
> +	const struct btf_type *map_type, *member_type;
> +	__u32 map_type_id, member_type_id;
> +	__u32 offset, next_offset = 0;
> +	const struct btf_member *m;
> +	const char *member_name;
> +	struct btf_dump *d = NULL;
> +	int i, err = 0;
> +	int size, map_size;
> +
> +	map_type_id = bpf_map__btf_value_type_id(map);
> +	if (map_type_id == 0)
> +		return -EINVAL;
> +	map_type = btf__type_by_id(btf, map_type_id);
> +	if (!map_type)
> +		return -EINVAL;
> +
> +	d = btf_dump__new(btf, codegen_btf_dump_printf, NULL, NULL);
> +	if (!d)
> +		return -errno;
> +
> +	for (i = 0, m = btf_members(map_type);
> +	     i < btf_vlen(map_type);
> +	     i++, m++) {
> +		member_type = skip_mods_and_typedefs(btf, m->type,
> +						     &member_type_id);
> +		if (!member_type) {
> +			err = -EINVAL;
> +			goto out;
> +		}
> +
> +		member_name = btf__name_by_offset(btf, m->name_off);
> +		if (!member_name) {
> +			err = -EINVAL;
> +			goto out;
> +		}
> +
> +		offset = m->offset / 8;
> +		if (next_offset != offset) {
> +			printf("\t\t\tchar __padding_%d[%d];\n",
> +			       i - 1, offset - next_offset);
> +		}
> +
> +		switch (btf_kind(member_type)) {
> +		case BTF_KIND_INT:
> +		case BTF_KIND_FLOAT:
> +		case BTF_KIND_ENUM:
> +		case BTF_KIND_ENUM64:
> +			/* scalar type */
> +			printf("\t\t\t");
> +			opts.field_name = member_name;
> +			err = btf_dump__emit_type_decl(d, member_type_id,
> +						       &opts);
> +			if (err)
> +				goto out;
> +			printf(";\n");
> +
> +			size = btf__resolve_size(btf, member_type_id);
> +			if (size < 0) {
> +				err = size;
> +				goto out;
> +			}
> +
> +			next_offset = offset + size;
> +			break;
> +
> +		case BTF_KIND_PTR:
> +			if (resolve_func_ptr(btf, m->type, NULL)) {
> +				/* Function pointer */
> +				printf("\t\t\tconst struct bpf_program *%s;\n",
> +				       member_name);
> +
> +				next_offset = offset + sizeof(void *);
> +				break;
> +			}
> +			fallthrough;

I wouldn't mind a comment about the "fallthrough;" to state explicitly
that only function pointers are supported for now.

> +
> +		default:
> +			/* Unsupported types
> +			 *
> +			 * For unsupported types, we have to generate
> +			 * definitions for them in order to support
> +			 * them. For example, we need to generate a
> +			 * definition for a struct type or a union type. It
> +			 * may cause type conflicts without renaming since
> +			 * the same type may be defined for several
> +			 * skeletons, and the user may include these
> +			 * skeletons in the same compile unit.
> +			 */

This comment could be clearer. "For unsupported types, we have to
generate definitions for them in order to support them". So do we, or do
we not support them? "It may cause type conflicts [...]" -> do we
address these?

My understanding is that this note describes the work to do if we want
to add support in the future, and this could perhaps be more explicit:
"We do not support other types yet. The reason is that ... But when we
generate definitions, we will have to take care of type conflicts
because ...". What do you think?

> +			if (i == btf_vlen(map_type) - 1) {
> +				map_size = btf__resolve_size(btf, map_type_id);
> +				if (map_size < 0)
> +					return -EINVAL;
> +				size = map_size - offset;
> +			} else {
> +				size = (m[1].offset - m->offset) / 8;
> +			}
> +
> +			printf("\t\t\tchar __padding_%d[%d];\n", i, size);
> +
> +			next_offset = offset + size;
> +			break;
> +		}
> +	}
> +
> +out:
> +	btf_dump__free(d);
> +
> +	return err;
> +}
> +
> +/* Generate the pointer of the shadow type for a struct_ops map.
> + *
> + * This function adds a pointer of the shadow type for a struct_ops map.
> + * The members of a struct_ops map can be exported through a pointer to a
> + * shadow type. The user can access these members through the pointer.
> + *
> + * A shadow type includes not all members, only members of some types.
> + * They are scalar types and function pointers. The function pointers are
> + * translated to the pointer of the struct bpf_program. The scalar types
> + * are translated to the original type without any modifiers.
> + *
> + * Unsupported types will be translated to a char array to take the same
> + * space of the original field. However, due to handling padding and
> + * alignments, the user should not access them directly.

What's the risk, and how should users know?

> + */

[...]

Thanks for this work! The bpftool changes look good. I've got these few
observations above, but notwithstanding:

Reviewed-by: Quentin Monnet <quentin@isovalent.com>

I wonder, did you think of adding a paragraph or an example to the man
page for "bpftool gen"? Your change is for a specific use case, but
otherwise I'm not sure how users will ever know that these shadow types
are available (other than discovering them by luck in a skeleton) or how
to use them if they need to.

Thanks,
Quentin
Kui-Feng Lee Feb. 21, 2024, 5:47 p.m. UTC | #2
On 2/21/24 03:49, Quentin Monnet wrote:
> 2024-02-21 01:23 UTC+0000 ~ thinker.li@gmail.com
>> From: Kui-Feng Lee <thinker.li@gmail.com>
>>
>> Declares and defines a pointer of the shadow type for each struct_ops map.
>>
>> The code generator will create an anonymous struct type as the shadow type
>> for each struct_ops map. The shadow type is translated from the original
>> struct type of the map. The user of the skeleton use pointers of them to
>> access the values of struct_ops maps.
>>
>> However, shadow types only supports certain types of fields, such as scalar
> 
> Nit: "such as" implies the list may not be exhaustive.
> 
>> types and function pointers. Any fields of unsupported types are translated
>> into an array of characters to occupy the space of the original
>> field. Function pointers are translated into pointers of the struct
>> bpf_program. Additionally, padding fields are generated to occupy the space
>> between two consecutive fields.
>>
>> The pointers of shadow types of struct_osp maps are initialized when
>> *__open_opts() in skeletons are called. For a map called FOO, the user can
>> access it through the pointer at skel->struct_ops.FOO.
>>
>> Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
>> ---
>>   tools/bpf/bpftool/gen.c | 229 +++++++++++++++++++++++++++++++++++++++-
>>   1 file changed, 228 insertions(+), 1 deletion(-)
>>
>> diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
>> index a9334c57e859..20c5d5912df7 100644
>> --- a/tools/bpf/bpftool/gen.c
>> +++ b/tools/bpf/bpftool/gen.c
>> @@ -909,6 +909,201 @@ codegen_progs_skeleton(struct bpf_object *obj, size_t prog_cnt, bool populate_li
>>   	}
>>   }
>>   
>> +static int walk_st_ops_shadow_vars(struct btf *btf,
>> +				   const char *ident,
>> +				   const struct bpf_map *map)
>> +{
>> +	DECLARE_LIBBPF_OPTS(btf_dump_emit_type_decl_opts, opts,
>> +			    .indent_level = 3,
>> +			    );
>> +	const struct btf_type *map_type, *member_type;
>> +	__u32 map_type_id, member_type_id;
>> +	__u32 offset, next_offset = 0;
>> +	const struct btf_member *m;
>> +	const char *member_name;
>> +	struct btf_dump *d = NULL;
>> +	int i, err = 0;
>> +	int size, map_size;
>> +
>> +	map_type_id = bpf_map__btf_value_type_id(map);
>> +	if (map_type_id == 0)
>> +		return -EINVAL;
>> +	map_type = btf__type_by_id(btf, map_type_id);
>> +	if (!map_type)
>> +		return -EINVAL;
>> +
>> +	d = btf_dump__new(btf, codegen_btf_dump_printf, NULL, NULL);
>> +	if (!d)
>> +		return -errno;
>> +
>> +	for (i = 0, m = btf_members(map_type);
>> +	     i < btf_vlen(map_type);
>> +	     i++, m++) {
>> +		member_type = skip_mods_and_typedefs(btf, m->type,
>> +						     &member_type_id);
>> +		if (!member_type) {
>> +			err = -EINVAL;
>> +			goto out;
>> +		}
>> +
>> +		member_name = btf__name_by_offset(btf, m->name_off);
>> +		if (!member_name) {
>> +			err = -EINVAL;
>> +			goto out;
>> +		}
>> +
>> +		offset = m->offset / 8;
>> +		if (next_offset != offset) {
>> +			printf("\t\t\tchar __padding_%d[%d];\n",
>> +			       i - 1, offset - next_offset);
>> +		}
>> +
>> +		switch (btf_kind(member_type)) {
>> +		case BTF_KIND_INT:
>> +		case BTF_KIND_FLOAT:
>> +		case BTF_KIND_ENUM:
>> +		case BTF_KIND_ENUM64:
>> +			/* scalar type */
>> +			printf("\t\t\t");
>> +			opts.field_name = member_name;
>> +			err = btf_dump__emit_type_decl(d, member_type_id,
>> +						       &opts);
>> +			if (err)
>> +				goto out;
>> +			printf(";\n");
>> +
>> +			size = btf__resolve_size(btf, member_type_id);
>> +			if (size < 0) {
>> +				err = size;
>> +				goto out;
>> +			}
>> +
>> +			next_offset = offset + size;
>> +			break;
>> +
>> +		case BTF_KIND_PTR:
>> +			if (resolve_func_ptr(btf, m->type, NULL)) {
>> +				/* Function pointer */
>> +				printf("\t\t\tconst struct bpf_program *%s;\n",
>> +				       member_name);
>> +
>> +				next_offset = offset + sizeof(void *);
>> +				break;
>> +			}
>> +			fallthrough;
> 
> I wouldn't mind a comment about the "fallthrough;" to state explicitly
> that only function pointers are supported for now.


Sure

> 
>> +
>> +		default:
>> +			/* Unsupported types
>> +			 *
>> +			 * For unsupported types, we have to generate
>> +			 * definitions for them in order to support
>> +			 * them. For example, we need to generate a
>> +			 * definition for a struct type or a union type. It
>> +			 * may cause type conflicts without renaming since
>> +			 * the same type may be defined for several
>> +			 * skeletons, and the user may include these
>> +			 * skeletons in the same compile unit.
>> +			 */
> 
> This comment could be clearer. "For unsupported types, we have to
> generate definitions for them in order to support them". So do we, or do
> we not support them? "It may cause type conflicts [...]" -> do we
> address these?
> 
> My understanding is that this note describes the work to do if we want
> to add support in the future, and this could perhaps be more explicit:
> "We do not support other types yet. The reason is that ... But when we
> generate definitions, we will have to take care of type conflicts
> because ...". What do you think?


Agree! I will rephrase this comment.

> 
>> +			if (i == btf_vlen(map_type) - 1) {
>> +				map_size = btf__resolve_size(btf, map_type_id);
>> +				if (map_size < 0)
>> +					return -EINVAL;
>> +				size = map_size - offset;
>> +			} else {
>> +				size = (m[1].offset - m->offset) / 8;
>> +			}
>> +
>> +			printf("\t\t\tchar __padding_%d[%d];\n", i, size);
>> +
>> +			next_offset = offset + size;
>> +			break;
>> +		}
>> +	}
>> +
>> +out:
>> +	btf_dump__free(d);
>> +
>> +	return err;
>> +}
>> +
>> +/* Generate the pointer of the shadow type for a struct_ops map.
>> + *
>> + * This function adds a pointer of the shadow type for a struct_ops map.
>> + * The members of a struct_ops map can be exported through a pointer to a
>> + * shadow type. The user can access these members through the pointer.
>> + *
>> + * A shadow type includes not all members, only members of some types.
>> + * They are scalar types and function pointers. The function pointers are
>> + * translated to the pointer of the struct bpf_program. The scalar types
>> + * are translated to the original type without any modifiers.
>> + *
>> + * Unsupported types will be translated to a char array to take the same
>> + * space of the original field. However, due to handling padding and
>> + * alignments, the user should not access them directly.
> 
> What's the risk, and how should users know?

The names of unsupported fields are replaced by "__padding_*", and their
types are "char []".
Changing names and types of fields in a struct can lead to accessing
issues, where users may inadvertently corrupt data due to padding and
field reordering in different versions.

I will include the above explanation in the next version.

> 
>> + */
> 
> [...]
> 
> Thanks for this work! The bpftool changes look good. I've got these few
> observations above, but notwithstanding:
> 
> Reviewed-by: Quentin Monnet <quentin@isovalent.com>
> 
> I wonder, did you think of adding a paragraph or an example to the man
> page for "bpftool gen"? Your change is for a specific use case, but
> otherwise I'm not sure how users will ever know that these shadow types
> are available (other than discovering them by luck in a skeleton) or how
> to use them if they need to.

Sure! I will add it in the following version.

> 
> Thanks,
> Quentin
diff mbox series

Patch

diff --git a/tools/bpf/bpftool/gen.c b/tools/bpf/bpftool/gen.c
index a9334c57e859..20c5d5912df7 100644
--- a/tools/bpf/bpftool/gen.c
+++ b/tools/bpf/bpftool/gen.c
@@ -909,6 +909,201 @@  codegen_progs_skeleton(struct bpf_object *obj, size_t prog_cnt, bool populate_li
 	}
 }
 
+static int walk_st_ops_shadow_vars(struct btf *btf,
+				   const char *ident,
+				   const struct bpf_map *map)
+{
+	DECLARE_LIBBPF_OPTS(btf_dump_emit_type_decl_opts, opts,
+			    .indent_level = 3,
+			    );
+	const struct btf_type *map_type, *member_type;
+	__u32 map_type_id, member_type_id;
+	__u32 offset, next_offset = 0;
+	const struct btf_member *m;
+	const char *member_name;
+	struct btf_dump *d = NULL;
+	int i, err = 0;
+	int size, map_size;
+
+	map_type_id = bpf_map__btf_value_type_id(map);
+	if (map_type_id == 0)
+		return -EINVAL;
+	map_type = btf__type_by_id(btf, map_type_id);
+	if (!map_type)
+		return -EINVAL;
+
+	d = btf_dump__new(btf, codegen_btf_dump_printf, NULL, NULL);
+	if (!d)
+		return -errno;
+
+	for (i = 0, m = btf_members(map_type);
+	     i < btf_vlen(map_type);
+	     i++, m++) {
+		member_type = skip_mods_and_typedefs(btf, m->type,
+						     &member_type_id);
+		if (!member_type) {
+			err = -EINVAL;
+			goto out;
+		}
+
+		member_name = btf__name_by_offset(btf, m->name_off);
+		if (!member_name) {
+			err = -EINVAL;
+			goto out;
+		}
+
+		offset = m->offset / 8;
+		if (next_offset != offset) {
+			printf("\t\t\tchar __padding_%d[%d];\n",
+			       i - 1, offset - next_offset);
+		}
+
+		switch (btf_kind(member_type)) {
+		case BTF_KIND_INT:
+		case BTF_KIND_FLOAT:
+		case BTF_KIND_ENUM:
+		case BTF_KIND_ENUM64:
+			/* scalar type */
+			printf("\t\t\t");
+			opts.field_name = member_name;
+			err = btf_dump__emit_type_decl(d, member_type_id,
+						       &opts);
+			if (err)
+				goto out;
+			printf(";\n");
+
+			size = btf__resolve_size(btf, member_type_id);
+			if (size < 0) {
+				err = size;
+				goto out;
+			}
+
+			next_offset = offset + size;
+			break;
+
+		case BTF_KIND_PTR:
+			if (resolve_func_ptr(btf, m->type, NULL)) {
+				/* Function pointer */
+				printf("\t\t\tconst struct bpf_program *%s;\n",
+				       member_name);
+
+				next_offset = offset + sizeof(void *);
+				break;
+			}
+			fallthrough;
+
+		default:
+			/* Unsupported types
+			 *
+			 * For unsupported types, we have to generate
+			 * definitions for them in order to support
+			 * them. For example, we need to generate a
+			 * definition for a struct type or a union type. It
+			 * may cause type conflicts without renaming since
+			 * the same type may be defined for several
+			 * skeletons, and the user may include these
+			 * skeletons in the same compile unit.
+			 */
+			if (i == btf_vlen(map_type) - 1) {
+				map_size = btf__resolve_size(btf, map_type_id);
+				if (map_size < 0)
+					return -EINVAL;
+				size = map_size - offset;
+			} else {
+				size = (m[1].offset - m->offset) / 8;
+			}
+
+			printf("\t\t\tchar __padding_%d[%d];\n", i, size);
+
+			next_offset = offset + size;
+			break;
+		}
+	}
+
+out:
+	btf_dump__free(d);
+
+	return err;
+}
+
+/* Generate the pointer of the shadow type for a struct_ops map.
+ *
+ * This function adds a pointer of the shadow type for a struct_ops map.
+ * The members of a struct_ops map can be exported through a pointer to a
+ * shadow type. The user can access these members through the pointer.
+ *
+ * A shadow type includes not all members, only members of some types.
+ * They are scalar types and function pointers. The function pointers are
+ * translated to the pointer of the struct bpf_program. The scalar types
+ * are translated to the original type without any modifiers.
+ *
+ * Unsupported types will be translated to a char array to take the same
+ * space of the original field. However, due to handling padding and
+ * alignments, the user should not access them directly.
+ */
+static int gen_st_ops_shadow_type(struct btf *btf, const char *ident,
+				  const struct bpf_map *map)
+{
+	int err;
+
+	printf("\t\tstruct {\n");
+
+	err = walk_st_ops_shadow_vars(btf, ident, map);
+	if (err)
+		return err;
+
+	printf("\t\t} *%s;\n", ident);
+
+	return 0;
+}
+
+static int gen_st_ops_shadow(struct btf *btf, struct bpf_object *obj)
+{
+	struct bpf_map *map;
+	char ident[256];
+	int err;
+
+	/* Generate the pointers to shadow types of
+	 * struct_ops maps.
+	 */
+	printf("\tstruct {\n");
+	bpf_object__for_each_map(map, obj) {
+		if (bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS)
+			continue;
+		if (!get_map_ident(map, ident, sizeof(ident)))
+			continue;
+		err = gen_st_ops_shadow_type(btf, ident, map);
+		if (err)
+			return err;
+	}
+	printf("\t} struct_ops;\n");
+
+	return 0;
+}
+
+/* Generate the code to initialize the pointers of shadow types. */
+static void gen_st_ops_shadow_init(struct btf *btf, struct bpf_object *obj)
+{
+	struct bpf_map *map;
+	char ident[256];
+
+	/* Initialize the pointers to_ops shadow types of
+	 * struct_ops maps.
+	 */
+	bpf_object__for_each_map(map, obj) {
+		if (bpf_map__type(map) != BPF_MAP_TYPE_STRUCT_OPS)
+			continue;
+		if (!get_map_ident(map, ident, sizeof(ident)))
+			continue;
+		codegen("\
+			\n\
+				obj->struct_ops.%1$s =			    \n\
+					bpf_map__initial_value(obj->maps.%1$s, NULL);\n\
+			\n\
+			", ident);
+	}
+}
+
 static int do_skeleton(int argc, char **argv)
 {
 	char header_guard[MAX_OBJ_NAME_LEN + sizeof("__SKEL_H__")];
@@ -923,6 +1118,7 @@  static int do_skeleton(int argc, char **argv)
 	struct bpf_map *map;
 	struct btf *btf;
 	struct stat st;
+	int st_ops_cnt = 0;
 
 	if (!REQ_ARGS(1)) {
 		usage();
@@ -1039,6 +1235,8 @@  static int do_skeleton(int argc, char **argv)
 		);
 	}
 
+	btf = bpf_object__btf(obj);
+
 	if (map_cnt) {
 		printf("\tstruct {\n");
 		bpf_object__for_each_map(map, obj) {
@@ -1048,8 +1246,15 @@  static int do_skeleton(int argc, char **argv)
 				printf("\t\tstruct bpf_map_desc %s;\n", ident);
 			else
 				printf("\t\tstruct bpf_map *%s;\n", ident);
+			if (bpf_map__type(map) == BPF_MAP_TYPE_STRUCT_OPS)
+				st_ops_cnt++;
 		}
 		printf("\t} maps;\n");
+		if (st_ops_cnt && btf) {
+			err = gen_st_ops_shadow(btf, obj);
+			if (err)
+				goto out;
+		}
 	}
 
 	if (prog_cnt) {
@@ -1075,7 +1280,6 @@  static int do_skeleton(int argc, char **argv)
 		printf("\t} links;\n");
 	}
 
-	btf = bpf_object__btf(obj);
 	if (btf) {
 		err = codegen_datasecs(obj, obj_name);
 		if (err)
@@ -1133,6 +1337,13 @@  static int do_skeleton(int argc, char **argv)
 			if (err)					    \n\
 				goto err_out;				    \n\
 									    \n\
+		", obj_name);
+
+	if (st_ops_cnt && btf)
+		gen_st_ops_shadow_init(btf, obj);
+
+	codegen("\
+		\n\
 			return obj;					    \n\
 		err_out:						    \n\
 			%1$s__destroy(obj);				    \n\
@@ -1296,6 +1507,7 @@  static int do_subskeleton(int argc, char **argv)
 	struct btf *btf;
 	const struct btf_type *map_type, *var_type;
 	const struct btf_var_secinfo *var;
+	int st_ops_cnt = 0;
 	struct stat st;
 
 	if (!REQ_ARGS(1)) {
@@ -1438,10 +1650,18 @@  static int do_subskeleton(int argc, char **argv)
 			if (!get_map_ident(map, ident, sizeof(ident)))
 				continue;
 			printf("\t\tstruct bpf_map *%s;\n", ident);
+			if (bpf_map__type(map) == BPF_MAP_TYPE_STRUCT_OPS)
+				st_ops_cnt++;
 		}
 		printf("\t} maps;\n");
 	}
 
+	if (st_ops_cnt && btf) {
+		err = gen_st_ops_shadow(btf, obj);
+		if (err)
+			goto out;
+	}
+
 	if (prog_cnt) {
 		printf("\tstruct {\n");
 		bpf_object__for_each_program(prog, obj) {
@@ -1553,6 +1773,13 @@  static int do_subskeleton(int argc, char **argv)
 			if (err)					    \n\
 				goto err;				    \n\
 									    \n\
+		");
+
+	if (st_ops_cnt && btf)
+		gen_st_ops_shadow_init(btf, obj);
+
+	codegen("\
+		\n\
 			return obj;					    \n\
 		err:							    \n\
 			%1$s__destroy(obj);				    \n\