Hi Martin, Please note that after changes for struct_ops map autoload by libbpf, test_loader could be use to test struct_ops related changes. Also, test_loader now supports __xlated macro which allows to verify rewrites applied by verifier. For example, the sample below works: struct st_ops_args; struct bpf_testmod_st_ops { int (*test_prologue)(struct st_ops_args *args); int (*test_epilogue)(struct st_ops_args *args); int (*test_pro_epilogue)(struct st_ops_args *args); struct module *owner; }; __success __xlated("0: *(u64 *)(r10 -8) = r1") __xlated("1: r0 = 0") __xlated("2: r1 = *(u64 *)(r10 -8)") __xlated("3: r1 = *(u64 *)(r1 +0)") __xlated("4: r6 = *(u32 *)(r1 +0)") __xlated("5: w6 += 10000") __xlated("6: *(u32 *)(r1 +0) = r6") __xlated("7: r6 = r1") __xlated("8: call kernel-function") __xlated("9: r1 = r6") __xlated("10: call kernel-function") __xlated("11: w0 *= 2") __xlated("12: exit") SEC("struct_ops/test_epilogue") __naked int test_epilogue(void) { asm volatile ( "r0 = 0;" "exit;" ::: __clobber_all); } SEC(".struct_ops.link") struct bpf_testmod_st_ops st_ops = { .test_epilogue = (void *)test_epilogue, }; (Complete example is in the attachment). test_loader based tests can also trigger program execution via __retval() macro. The only (minor) shortcoming that I see, is that test_loader would load/unload st_ops map multiple times because of the following interaction: - test_loader assumes that each bpf program defines a test; - test_loader re-creates all maps before each test; - libbpf struct_ops autocreate logic marks all programs referenced from struct_ops map as autoloaded. I think that writing tests this way is easier to follow, compared to arithmetic manipulations done currently. What do you think? Thanks, Eduard